diff options
-rw-r--r-- | src/connect.rs | 79 | ||||
-rw-r--r-- | src/convention.rs | 189 | ||||
-rw-r--r-- | src/main.rs | 453 | ||||
-rw-r--r-- | src/nbtdsl.rs | 1 | ||||
-rw-r--r-- | src/player.rs | 48 |
5 files changed, 467 insertions, 303 deletions
diff --git a/src/connect.rs b/src/connect.rs index 1feb03e..cfebb29 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -1,9 +1,14 @@ +use std::future::Future; + use anyhow::Result; -use craftio_rs::{CraftAsyncReader, CraftAsyncWriter, CraftTokioConnection}; -use mcproto_rs::protocol::HasPacketKind; +use craftio_rs::{CraftAsyncReader, CraftAsyncWriter, CraftIo, CraftReader, CraftTokioConnection, CraftWrapper, CraftWriter}; +use mcproto_rs::protocol::{HasPacketKind, PacketDirection, State}; use mcproto_rs::v1_19_3::{Packet761, Packet761Kind, RawPacket761}; -use tokio::io::BufReader; +use tokio::io::{AsyncRead, BufReader}; +use tokio::net::tcp::OwnedReadHalf; use tokio::net::TcpStream; +use tokio::sync::mpsc; +use tokio::task::JoinHandle; #[macro_export] macro_rules! await_packet { @@ -34,32 +39,69 @@ macro_rules! assert_packet { ) }; } +#[derive(Debug)] +enum WriterAction { + Send(Packet761), + SetState(State), +} pub struct MinecraftClient { - pub connection: CraftTokioConnection, + send_handle: JoinHandle<()>, + send_tx: mpsc::UnboundedSender<WriterAction>, + craft_reader: CraftReader<BufReader<OwnedReadHalf>>, } impl MinecraftClient { pub fn from_stream(stream: TcpStream) -> Self { let (read, write) = stream.into_split(); let bufread = BufReader::new(read); + let mut craft_writer = CraftWriter::wrap(write, PacketDirection::ClientBound); + let mut craft_reader = CraftReader::wrap(bufread, PacketDirection::ServerBound); + let (send_tx, mut send_recv) = mpsc::unbounded_channel::<WriterAction>(); + let send_handle = tokio::spawn(async move { + loop { + match send_recv.recv().await { + None => break, + Some(WriterAction::Send(packet)) => { + if let Err(err) = craft_writer.write_packet_async(packet).await { + println!("Failed to send packet {:?}", err); + } + } + Some(WriterAction::SetState(state)) => { + craft_writer.set_state(state); + } + } + } + () + }); MinecraftClient { - connection: CraftTokioConnection::from_async((bufread, write), mcproto_rs::protocol::PacketDirection::ServerBound), + craft_reader, + send_tx, + send_handle, } } + + pub async fn close_and_join(self) -> Result<()> { + drop(self.send_tx); + self.send_handle.await?; + // The reader is closed when the writer half is dropped by the send task. + Ok(()) + } + + pub fn set_state(&mut self, state: State) -> Result<()> { + self.send_tx.send(WriterAction::SetState(state))?; + self.craft_reader.set_state(state); + Ok(()) + } + pub async fn read_next_packet(&mut self) -> Result<Option<Packet761>> { - if let Some(raw) = self.connection.read_packet_async::<RawPacket761>().await? { - println!("Client -> Server: {:?}", raw); + if let Some(raw) = self.craft_reader.read_packet_async::<RawPacket761>().await? { + // println!("Client -> Server: {:?}", raw); Ok(Some(raw)) } else { Ok(None) } } - pub async fn send_packet(&mut self, packet: Packet761) -> Result<()> { - println!("Server -> Client: {:?}", packet); - self.connection.write_packet_async(packet).await?; - Ok(()) - } pub async fn wait_for_packet(&mut self, packet_kind: Packet761Kind) -> Result<Packet761> { loop { if let Some(packet) = self.read_next_packet().await? { @@ -70,4 +112,17 @@ impl MinecraftClient { } } } + + pub fn send_packet(&self, packet: Packet761) -> Result<()> { + // println!("Server -> Client: {:?}", packet); + self.send_tx.send(WriterAction::Send(packet))?; + Ok(()) + } + + pub fn send_all_packets(&self, packets: Vec<Packet761>) -> Result<()> { + for x in packets { + self.send_packet(x)?; + } + Ok(()) + } } diff --git a/src/convention.rs b/src/convention.rs new file mode 100644 index 0000000..ae9a646 --- /dev/null +++ b/src/convention.rs @@ -0,0 +1,189 @@ +use mcproto_rs::nbt::Tag; +use mcproto_rs::types::NamedNbtTag; +use mcproto_rs::v1_19_3::{BookSettings, CommandNode, CommandNodeSpec, CommandsSpec, GameMode, LoginPlaySpec, Packet761, PreviousGameMode, RecipeBookAction, RecipeBookInitSpec, TagType, TypedTagList, UpdateRecipeBookSpec, UpdateRecipesSpec, UpdateTagsSpec}; + +use crate::nbt; +use crate::player::Player; + +pub struct Universe { + pub min_build_height: i32, + pub max_build_height: i32, + pub min_world_section: i32, + pub max_world_section: i32, + pub registry_codex: Tag, +} + +fn convention_biome_spec() -> Tag { + return nbt!( + [ + { + "id" :: (0), + "name" :: ("minecraft:plains"), + "element" :: { + "downfall" :: (0.5), + "precipitation" :: ("none"), + "temperature" :: (0.5), + "effects" :: { + "fog_color" :: (12638463), + "sky_color" :: (12638463), + "water_color" :: (12638463), + "water_fog_color" :: (12638463), + "mood_sound" :: { + "block_search_extent" :: (8), + "offset" :: (2), + "sound" :: ("minecraft:ambient.cave"), + "tick_delay" :: (6000), + }, + } + } + } + ] + ) +} + +fn convention_overworld_spec(min_build_height: i32, world_height: i32) -> Tag { + return nbt! { + "id" :: (0), + "name" :: ("minecraft:overworld"), + "element" :: { + "ambient_light" :: (false), + "bed_works" :: (true), + "coordinate_scale" :: (1), + "effects" :: ("minecraft:overworld"), + "has_ceiling" :: (false), + "has_skylight" :: (true), + "has_raids" :: (false), + "height" :: (world_height), + "infiniburn" :: ("#minecraft:infiniburn_overworld"), + "logical_height" :: (world_height), + "min_y" :: (min_build_height), + "monster_spawn_block_light_limit" :: (0), + "monster_spawn_light_level" :: { + "type" :: ("minecraft:uniform"), + "value" :: { + "max_inclusive" :: (7), + "min_inclusive" :: (0), + }, + }, + "natural" :: (true), + "piglin_safe" :: (false), + "respawn_anchor_works" :: (false), + "ultrawarm" :: (false), + }, + } +} + +impl Universe { + pub fn default_meta( + min_build_height: i32, + max_build_height: i32, + ) -> Universe { + let world_height = max_build_height - min_build_height; + let min_world_section = min_build_height >> 4; + let max_world_section = ((max_build_height - 1) >> 4) + 1; + let biome_spec = convention_biome_spec(); + let overworld_spec = convention_overworld_spec(min_build_height, world_height); + let registry_codex = nbt! { + "minecraft:dimension_type" :: { + "type" :: ("minecraft:dimension_type"), + "value" :: [(overworld_spec)] + }, + "minecraft:worldgen/biome" :: { + "type" :: ("minecraft:worldgen/biome"), + "value" :: (biome_spec), + } + }; + Universe { + min_build_height, + max_build_height, + min_world_section, + max_world_section, + registry_codex, + } + } + + pub fn create_login_play_packet(&self) -> Packet761 { + Packet761::LoginPlay(LoginPlaySpec { + entity_id: 0, + is_hardcore: false, + gamemode: GameMode::Survival, + previous_gamemode: PreviousGameMode::NoPrevious, + dimension_names: vec!["world".into()].into(), + registry_codex: NamedNbtTag { + root: self.registry_codex.clone().with_name("root"), + }, + dimension_type: "minecraft:overworld".into(), + dimension_name: "world".into(), + hashed_seed: 0, + max_players: 10.into(), + view_distance: 10.into(), + simulation_distance: 10.into(), + reduced_debug_info: false, + enable_respawn_screen: false, + is_debug: false, + is_flat: false, + death_location: None, + }) + } + + pub fn init_empty_recipe_book(&self) -> Packet761 { + Packet761::UpdateRecipeBook(UpdateRecipeBookSpec { + action: RecipeBookAction::Init(RecipeBookInitSpec { + book_settings: BookSettings::default(), + known_recipes: vec![].into(), + highlighted_recipes: vec![].into(), + }) + }) + } + + pub fn empty_commands(&self) -> Packet761 { + Packet761::Commands(CommandsSpec { + nodes: vec![ + CommandNodeSpec { + children_indices: vec![].into(), + redirect_node: None, + is_executable: false, + node: CommandNode::Root, + } + ].into(), + root_index: 0.into(), + }) + } + + pub fn create_tags_and_recipes(&self) -> Vec<Packet761> { + return vec![ + Packet761::UpdateRecipes(UpdateRecipesSpec { recipes: vec![].into() }), + Packet761::UpdateTags(UpdateTagsSpec { + tags: vec![ + TypedTagList { + tag_type: TagType::Block, + tags: vec![].into(), + }, + TypedTagList { + tag_type: TagType::Item, + tags: vec![].into(), + }, + TypedTagList { + tag_type: TagType::EntityType, + tags: vec![].into(), + }, + TypedTagList { + tag_type: TagType::GameEvent, + tags: vec![].into(), + }, + TypedTagList { + tag_type: TagType::Fluid, + tags: vec![].into(), + }, + ].into(), + }), + ] + } +} + + + + + + + diff --git a/src/main.rs b/src/main.rs index a113a30..6c1f95b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; use anyhow::Result; -use craftio_rs::CraftIo; use mcproto_rs::nbt::Tag; use mcproto_rs::protocol::State; use mcproto_rs::Serializer; @@ -14,7 +13,7 @@ use mcproto_rs::v1_19_3::{GameMode, HandshakeNextState, LoginPlaySpec, LoginSucc use tokio; use tokio::net::TcpStream; use tokio::signal::unix::{signal, SignalKind}; -use tokio::sync::{broadcast}; +use tokio::sync::broadcast; use tokio::sync::broadcast::Receiver; use tokio::task::JoinHandle; @@ -22,7 +21,8 @@ use crate::connect::MinecraftClient; pub mod connect; pub mod nbtdsl; - +pub mod player; +pub mod convention; #[tokio::main] async fn main() -> Result<()> { @@ -87,292 +87,169 @@ async fn listener(listener: tokio::net::TcpListener, mut receiver: Receiver<()>) async fn handle_conn(mut client: MinecraftClient) -> Result<()> { let hs = assert_packet!(Handshake, client, "Missing packet"); if hs.next_state == HandshakeNextState::Login { - client.connection.set_state(State::Login); - let loginstart = assert_packet!( + return handle_login(client).await; + } else if hs.next_state == HandshakeNextState::Status { + return handle_status(client).await; + } else { + anyhow::bail!("Unknown next status") + } +} + +async fn handle_login(mut client: MinecraftClient) -> Result<()> { + client.set_state(State::Login)?; + let loginstart = assert_packet!( LoginStart, client .read_next_packet() .await? .ok_or(anyhow::anyhow!("Missing packet"))? ); - println!("Login: {:?}", loginstart); - let uuid = UUID4::random(); - client - .send_packet(Packet761::LoginSuccess(LoginSuccessSpec { - uuid, - username: loginstart.name, - properties: vec![].into(), - })) - .await?; - let min_build_height = -64i32; - let max_build_height = 256i32; - let world_height = max_build_height - min_build_height; - let min_world_section = min_build_height >> 4; - let max_world_section = ((max_build_height - 1) >> 4) + 1; - let section_count = max_world_section - min_world_section; - let overworld_spec = nbt! { - "id" :: (0), - "name" :: ("minecraft:overworld"), - "element" :: { - "ambient_light" :: (false), - "bed_works" :: (true), - "coordinate_scale" :: (1), - "effects" :: ("minecraft:overworld"), - "has_ceiling" :: (false), - "has_skylight" :: (true), - "has_raids" :: (false), - "height" :: (world_height), - "infiniburn" :: ("#minecraft:infiniburn_overworld"), - "logical_height" :: (world_height), - "min_y" :: (min_build_height), - "monster_spawn_block_light_limit" :: (0), - "monster_spawn_light_level" :: { - "type" :: ("minecraft:uniform"), - "value" :: { - "max_inclusive" :: (7), - "min_inclusive" :: (0), - }, - }, - "natural" :: (true), - "piglin_safe" :: (false), - "respawn_anchor_works" :: (false), - "ultrawarm" :: (false), - }, - }; - let biome_spec = nbt!( - [ - { - "id" :: (0), - "name" :: ("minecraft:plains"), - "element" :: { - "downfall" :: (0.5), - "precipitation" :: ("none"), - "temperature" :: (0.5), - "effects" :: { - "fog_color" :: (12638463), - "sky_color" :: (12638463), - "water_color" :: (12638463), - "water_fog_color" :: (12638463), - "mood_sound" :: { - "block_search_extent" :: (8), - "offset" :: (2), - "sound" :: ("minecraft:ambient.cave"), - "tick_delay" :: (6000), - }, - } - } - } - ] - ); - let registry_doex = nbt! { - "minecraft:dimension_type" :: { - "type" :: ("minecraft:dimension_type"), - "value" :: [(overworld_spec)] - }, - "minecraft:worldgen/biome" :: { - "type" :: ("minecraft:worldgen/biome"), - "value" :: (biome_spec), + println!("Login: {:?}", loginstart); + let uuid = UUID4::random(); + client + .send_packet(Packet761::LoginSuccess(LoginSuccessSpec { + uuid, + username: loginstart.name.clone(), + properties: vec![].into(), + }))?; + client.set_state(State::Play)?; + let mut player = player::Player { + client, + name: loginstart.name.clone(), + position: Vec3 { + x: 0.0, + y: 100.0, + z: 0.0, + }, + yaw: 0.0, + pitch: 0.0, + }; + let universe = convention::Universe::default_meta(-64, 256); + player.send_packet(universe.create_login_play_packet())?; + + let client_information = await_packet!(ClientInformation, player.client); + println!("Client Information: {:?}", client_information); + + player.send_packet(Packet761::SetHeldItemClient(SetHeldItemClientSpec { + selected_slot: 0, + }))?; + + player.send_all_packets(universe.create_tags_and_recipes())?; + + player.send_packet(Packet761::EntityEvent(EntityEventSpec { + entity_id: 0, + entity_status: 24 /* OP permission level = 0 */, + }))?; + player.send_packet(universe.empty_commands())?; + player.send_packet(universe.init_empty_recipe_book())?; + player.send_packet(player.create_teleport_packet())?; + player.send_packet(Packet761::SetCenterChunk(SetCenterChunkSpec { + chunk_x: 0.into(), + chunk_z: 0.into(), + }))?; + for i in -7..7 { + for j in -7..7 { + let mut heightmap = BlobArray::new(9); + for cx in 0..(16 * 16) { + heightmap.set_entry(cx, 2) } - }; - client.connection.set_state(State::Play); - client - .send_packet(Packet761::LoginPlay(LoginPlaySpec { - entity_id: 0, - is_hardcore: false, - gamemode: GameMode::Survival, - previous_gamemode: PreviousGameMode::NoPrevious, - dimension_names: vec!["world".into()].into(), - registry_codex: NamedNbtTag { - root: registry_doex.with_name("root"), - }, - dimension_type: "minecraft:overworld".into(), - dimension_name: "world".into(), - hashed_seed: 0, - max_players: 10.into(), - view_distance: 10.into(), - simulation_distance: 10.into(), - reduced_debug_info: false, - enable_respawn_screen: false, - is_debug: false, - is_flat: false, - death_location: None, - })) - .await?; - let client_information = await_packet!(ClientInformation, client); - println!("Client Information: {:?}", client_information); - client.send_packet(Packet761::SetHeldItemClient(SetHeldItemClientSpec { - selected_slot: 0, - })).await?; - client.send_packet(Packet761::UpdateRecipes(UpdateRecipesSpec { recipes: vec![].into() })).await?; - client.send_packet(Packet761::UpdateTags(UpdateTagsSpec { - tags: vec![ - TypedTagList { - tag_type: TagType::Block, - tags: vec![].into(), - }, - TypedTagList { - tag_type: TagType::Item, - tags: vec![].into(), - }, - TypedTagList { - tag_type: TagType::EntityType, - tags: vec![].into(), - }, - TypedTagList { - tag_type: TagType::GameEvent, - tags: vec![].into(), - }, - TypedTagList { - tag_type: TagType::Fluid, - tags: vec![].into(), - }, - ].into(), - })).await?; - client.send_packet(Packet761::EntityEvent(EntityEventSpec { - entity_id: 0, - entity_status: 24 /* OP permission level = 0 */, - })).await?; - client.send_packet(Packet761::Commands(CommandsSpec { - nodes: vec![ - CommandNodeSpec { - children_indices: vec![].into(), - redirect_node: None, - is_executable: false, - node: CommandNode::Root, - } - ].into(), - root_index: 0.into(), - })).await?; - client.send_packet(Packet761::UpdateRecipeBook(UpdateRecipeBookSpec { - action: RecipeBookAction::Init(RecipeBookInitSpec { - book_settings: BookSettings::default(), - known_recipes: vec![].into(), - highlighted_recipes: vec![].into(), - }) - })).await?; - client.send_packet(Packet761::SynchronizePlayerPosition(SynchronizePlayerPositionSpec { - position: Vec3 { - x: 0f64, - y: 100f64, - z: 0f64, - }, - yaw: 0.0, - pitch: 0.0, - flags: Default::default(), - teleport_id: 0.into(), - dismount_vehicle: false, - })).await?; - client.send_packet(Packet761::SetCenterChunk(SetCenterChunkSpec { - chunk_x: 0.into(), - chunk_z: 0.into(), - })).await?; - for i in -7..7 { - for j in -7..7 { - let mut heightmap = BlobArray::new(9); - for cx in 0..(16 * 16) { - heightmap.set_entry(cx, 2) - } - let mut chunk_dat_serializer = BytesSerializer::default(); - for i in 0..section_count { - let section = ChunkSection { - block_count: if i < 4 { 16 * 16 * 16 } else { 0 }, - block_state: PalettedContainer { singular_value: if i < 4 { 1.into() } else { 0.into() } }, - biomes: PalettedContainer { singular_value: 0.into() }, - }; - chunk_dat_serializer.serialize_other(§ion)?; - } - let chunk_column_bytes = chunk_dat_serializer.into_bytes(); - client.send_packet(Packet761::ChunkDataAndUpdateLight(ChunkDataAndUpdateLightSpec { - chunk_x: i, - chunk_z: j, - chunk_data: ChunkDataSpec { - heightmaps: NamedNbtTag { - root: nbt! { + let mut chunk_dat_serializer = BytesSerializer::default(); + for i in 0..(universe.max_world_section - universe.min_world_section) { + let section = ChunkSection { + block_count: if i < 4 { 16 * 16 * 16 } else { 0 }, + block_state: PalettedContainer { singular_value: if i < 4 { 1.into() } else { 0.into() } }, + biomes: PalettedContainer { singular_value: 0.into() }, + }; + chunk_dat_serializer.serialize_other(§ion)?; + } + let chunk_column_bytes = chunk_dat_serializer.into_bytes(); + player.send_packet(Packet761::ChunkDataAndUpdateLight(ChunkDataAndUpdateLightSpec { + chunk_x: i, + chunk_z: j, + chunk_data: ChunkDataSpec { + heightmaps: NamedNbtTag { + root: nbt! { "MOTION_BLOCKING" :: (Tag::LongArray(heightmap.data.iter().map(|n| n.to_owned() as _).collect())), "WORLD_SURFACE" :: (Tag::LongArray(heightmap.data.iter().map(|n| n.to_owned() as _).collect())), }.with_name("root") - }, - buffer: chunk_column_bytes.into(), - block_entities: vec![].into(), }, - light_data: LightDataSpec { - trust_edges: true, - sky_y_mask: BitSet::empty(), - block_y_mask: BitSet::empty(), - empty_sky_y_mask: BitSet::empty(), - empty_block_y_mask: BitSet::empty(), - sky_updates: vec![].into(), - block_updates: vec![].into(), - }, - })).await?; - } + buffer: chunk_column_bytes.into(), + block_entities: vec![].into(), + }, + light_data: LightDataSpec { + trust_edges: true, + sky_y_mask: BitSet::empty(), + block_y_mask: BitSet::empty(), + empty_sky_y_mask: BitSet::empty(), + empty_block_y_mask: BitSet::empty(), + sky_updates: vec![].into(), + block_updates: vec![].into(), + }, + }))?; } - client.send_packet(Packet761::InitializeWorldBorder(InitializeWorldBorderSpec { - x: 0.0, - z: 0.0, - old_diameter: 10000000.0, - new_diameter: 10000000.0, - speed: 0.into(), - portal_teleport_boundary: 29999984.into(), - warning_blocks: 0.into(), - warning_time: 0.into(), - })).await?; - client.send_packet(Packet761::SetDefaultSpawnPosition(SetDefaultSpawnPositionSpec { - location: IntPosition { - x: 0, - y: 0, - z: 0, - }, - angle: 0.0, - })).await?; - client.send_packet(Packet761::SynchronizePlayerPosition(SynchronizePlayerPositionSpec { - position: Vec3 { - x: 0f64, - y: 100f64, - z: 0f64, - }, - yaw: 0.0, - pitch: 0.0, - flags: Default::default(), - teleport_id: 0.into(), - dismount_vehicle: false, - })).await?; - client.send_packet(Packet761::SetContainerContent(SetContainerContentSpec { - window_id: 0, - state_id: 0.into(), - slot_data: vec![Some(ItemStack { - item_id: 1.into(), - item_count: 1, - nbt: None, - }); 45].into(), - carried_item: None, - })).await?; - let mut i = 0; - loop { - client.send_packet(Packet761::KeepAliveClientBound(KeepAliveClientBoundSpec { - keep_alive_id: i, - })).await?; - if i == 2 { - let mut brand_encoder = BytesSerializer::default(); - brand_encoder.serialize_other(&String::from("MGASI"))?; + } + player.send_packet(Packet761::InitializeWorldBorder(InitializeWorldBorderSpec { + x: 0.0, + z: 0.0, + old_diameter: 10000000.0, + new_diameter: 10000000.0, + speed: 0.into(), + portal_teleport_boundary: 29999984.into(), + warning_blocks: 0.into(), + warning_time: 0.into(), + }))?; + player.send_packet(Packet761::SetDefaultSpawnPosition(SetDefaultSpawnPositionSpec { + location: IntPosition { + x: 0, + y: 0, + z: 0, + }, + angle: 0.0, + }))?; + player.send_packet(Packet761::SynchronizePlayerPosition(SynchronizePlayerPositionSpec { + position: Vec3 { + x: 0f64, + y: 100f64, + z: 0f64, + }, + yaw: 0.0, + pitch: 0.0, + flags: Default::default(), + teleport_id: 0.into(), + dismount_vehicle: false, + }))?; + player.send_packet(Packet761::SetContainerContent(SetContainerContentSpec { + window_id: 0, + state_id: 0.into(), + slot_data: vec![Some(ItemStack { + item_id: 1.into(), + item_count: 1, + nbt: None, + }); 45].into(), + carried_item: None, + }))?; + let mut i = 0; + loop { + player.send_packet(Packet761::KeepAliveClientBound(KeepAliveClientBoundSpec { + keep_alive_id: i, + }))?; + if i == 2 { + let mut brand_encoder = BytesSerializer::default(); + brand_encoder.serialize_other(&String::from("MGASI"))?; - client.send_packet(Packet761::PluginMessage(PluginMessageSpec { - channel: "minecraft:brand".to_string(), - data: RemainingBytes { data: brand_encoder.into_bytes() }, - })).await?; - } - i += 1; - tokio::time::sleep(Duration::from_secs(10)).await; + player.send_packet(Packet761::PluginMessage(PluginMessageSpec { + channel: "minecraft:brand".to_string(), + data: RemainingBytes { data: brand_encoder.into_bytes() }, + }))?; } - } else if hs.next_state == HandshakeNextState::Status { - return handle_status(client).await; - } else { - anyhow::bail!("Unknown next status") + i += 1; + tokio::time::sleep(Duration::from_secs(10)).await; } } + async fn handle_status(mut client: MinecraftClient) -> Result<()> { - client.connection.set_state(State::Status); + client.set_state(State::Status)?; loop { match client .read_next_packet() @@ -380,28 +257,24 @@ async fn handle_status(mut client: MinecraftClient) -> Result<()> { .ok_or(anyhow::anyhow!("Missing packet"))? { Packet761::StatusRequest(_) => { - client - .send_packet(Packet761::StatusRespone(StatusResponseSpec { - response: StatusSpec { - version: Some(StatusVersionSpec { - name: "1.19.3 mgasi".to_string(), - protocol: 761, - }), - players: StatusPlayersSpec { - max: 100, - online: 0, - sample: vec![], - }, - description: Chat::from_text("hehehe"), - favicon: None, + client.send_packet(Packet761::StatusRespone(StatusResponseSpec { + response: StatusSpec { + version: Some(StatusVersionSpec { + name: "1.19.3 mgasi".to_string(), + protocol: 761, + }), + players: StatusPlayersSpec { + max: 100, + online: 0, + sample: vec![], }, - })) - .await?; + description: Chat::from_text("hehehe"), + favicon: None, + }, + }))?; } Packet761::PingRequest(PingRequestSpec { payload }) => { - client - .send_packet(Packet761::PingResponse(PingResponseSpec { payload })) - .await?; + client.send_packet(Packet761::PingResponse(PingResponseSpec { payload }))?; return Ok(()); } _ => anyhow::bail!("Unexpected packet during status communication"), diff --git a/src/nbtdsl.rs b/src/nbtdsl.rs index bfa9090..86b2116 100644 --- a/src/nbtdsl.rs +++ b/src/nbtdsl.rs @@ -1,6 +1,5 @@ use mcproto_rs::nbt::Tag; - pub trait ShittyToNbt { fn to_nbt(self) -> Tag; } diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..7b99c16 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use mcproto_rs::types::Vec3; +use mcproto_rs::v1_19_3::{Packet761, SynchronizePlayerPositionSpec}; + +use crate::connect::MinecraftClient; + +pub struct Player { + pub client: MinecraftClient, + pub name: String, + pub position: Vec3<f64>, + pub yaw: f32, + pub pitch: f32, +} + + +impl Player { + pub fn create_teleport_packet(&self) -> Packet761 { + Packet761::SynchronizePlayerPosition(SynchronizePlayerPositionSpec { + position: self.position.clone(), + yaw: self.yaw, + pitch: self.pitch, + flags: Default::default(), + teleport_id: 0.into(), // TODO: teleport id ack + dismount_vehicle: false, + }) + } + + pub async fn teleport(&mut self, position: Vec3<f64>, yaw: f32, pitch: f32) -> Result<()> { + self.position = position; + self.yaw = yaw; + self.pitch = pitch; + self.send_packet(self.create_teleport_packet())?; + Ok(()) + } + + pub fn send_packet(&self, packet: Packet761) -> Result<()> { + self.client.send_packet(packet) + } + pub fn send_all_packets(&self, packets: Vec<Packet761>) -> Result<(), anyhow::Error> { + self.client.send_all_packets(packets) + } +} + + + + + + |