From cbc6af81fbdee71f4c1c7d62e1b91f8b17ad23bd Mon Sep 17 00:00:00 2001 From: mat <27899617+mat-1@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:07:52 -0600 Subject: [PATCH] Add PacketEvent (#75) * add PacketEvent * docs and fixes * Event::Packet works --- azalea-client/src/events.rs | 36 +- azalea-client/src/packet_handling.rs | 1656 +++++++++-------- azalea-ecs/azalea-ecs-macros/src/component.rs | 4 +- azalea-ecs/azalea-ecs-macros/src/lib.rs | 10 +- azalea-ecs/azalea-ecs-macros/src/utils/mod.rs | 3 + azalea-ecs/src/lib.rs | 19 +- azalea/examples/testbot.rs | 7 + 7 files changed, 909 insertions(+), 826 deletions(-) diff --git a/azalea-client/src/events.rs b/azalea-client/src/events.rs index 99a47fbe..085f5368 100644 --- a/azalea-client/src/events.rs +++ b/azalea-client/src/events.rs @@ -7,7 +7,7 @@ use azalea_ecs::{ app::{App, Plugin}, component::Component, event::EventReader, - query::{Added, Changed}, + query::Added, system::Query, AppTickExt, }; @@ -21,7 +21,7 @@ use tokio::sync::mpsc; use crate::{ chat::{ChatPacket, ChatReceivedEvent}, packet_handling::{ - AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketReceiver, RemovePlayerEvent, + AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketEvent, RemovePlayerEvent, UpdatePlayerEvent, }, PlayerInfo, @@ -62,6 +62,23 @@ pub enum Event { Chat(ChatPacket), /// Happens 20 times per second, but only when the world is loaded. Tick, + /// We received a packet from the server. + /// + /// ``` + /// # use azalea_client::Event; + /// # use azalea_protocol::packets::game::ClientboundGamePacket; + /// # async fn example(event: Event) { + /// # match event { + /// Event::Packet(packet) => match *packet { + /// ClientboundGamePacket::Login(_) => { + /// println!("login packet"); + /// } + /// _ => {} + /// }, + /// # _ => {} + /// # } + /// # } + /// ``` Packet(Arc), /// A player joined the game (or more specifically, was added to the tab /// list). @@ -133,13 +150,14 @@ fn tick_listener(query: Query<&LocalPlayerEvents>) { } } -fn packet_listener(query: Query<(&LocalPlayerEvents, &PacketReceiver), Changed>) { - for (local_player_events, packet_receiver) in &query { - for packet in packet_receiver.packets.lock().iter() { - local_player_events - .send(Event::Packet(packet.clone().into())) - .unwrap(); - } +fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader) { + for event in events.iter() { + let local_player_events = query + .get(event.entity) + .expect("Non-localplayer entities shouldn't be able to receive add player events"); + local_player_events + .send(Event::Packet(Arc::new(event.packet.clone()))) + .unwrap(); } } diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs index a4d638a4..9f8c5f1a 100644 --- a/azalea-client/src/packet_handling.rs +++ b/azalea-client/src/packet_handling.rs @@ -6,8 +6,8 @@ use azalea_ecs::{ component::Component, ecs::Ecs, entity::Entity, - event::EventWriter, - query::Changed, + event::{EventReader, EventWriter, Events}, + schedule::{StageLabel, SystemStage}, system::{Commands, Query, ResMut, SystemState}, }; use azalea_protocol::{ @@ -42,17 +42,55 @@ use crate::{ ClientInformation, PlayerInfo, }; +/// An event that's sent when we receive a packet. +/// ``` +/// # use azalea_client::packet_handling::PacketEvent; +/// # use azalea_protocol::packets::game::ClientboundGamePacket; +/// # use azalea_ecs::event::EventReader; +/// +/// fn handle_packets(mut events: EventReader) { +/// for PacketEvent { +/// entity, +/// packet, +/// } in events.iter() { +/// match packet { +/// ClientboundGamePacket::LevelParticles(p) => { +/// // ... +/// } +/// _ => {} +/// } +/// } +/// } +/// ``` +#[derive(Debug, Clone)] +pub struct PacketEvent { + /// The client entity that received the packet. + pub entity: Entity, + /// The packet that was actually received. + pub packet: ClientboundGamePacket, +} + pub struct PacketHandlerPlugin; +#[derive(StageLabel)] +pub struct SendPacketEventsStage; + impl Plugin for PacketHandlerPlugin { fn build(&self, app: &mut App) { - app.add_system_to_stage(CoreStage::PreUpdate, handle_packets) - .add_event::() - .add_event::() - .add_event::() - .add_event::() - .add_event::() - .add_event::(); + app.add_stage_before( + CoreStage::PreUpdate, + SendPacketEventsStage, + SystemStage::parallel(), + ) + .add_system_to_stage(SendPacketEventsStage, send_packet_events) + .add_system_to_stage(CoreStage::PreUpdate, process_packet_events) + .init_resource::>() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::(); } } @@ -107,834 +145,841 @@ pub struct PacketReceiver { pub run_schedule_sender: mpsc::UnboundedSender<()>, } -pub fn handle_packets(ecs: &mut Ecs) { - let mut events_owned = Vec::new(); - - { - let mut system_state: SystemState< - Query<(Entity, &PacketReceiver), Changed>, - > = SystemState::new(ecs); - let query = system_state.get(ecs); - for (player_entity, packet_events) in &query { - let mut packets = packet_events.packets.lock(); - if !packets.is_empty() { - events_owned.push((player_entity, packets.clone())); - // clear the packets right after we read them - packets.clear(); +pub fn send_packet_events( + query: Query<(Entity, &PacketReceiver)>, + mut packet_events: ResMut>, +) { + // we manually clear and send the events at the beginning of each update + // since otherwise it'd cause issues with events in process_packet_events + // running twice + packet_events.clear(); + for (player_entity, packet_receiver) in &query { + let mut packets = packet_receiver.packets.lock(); + if !packets.is_empty() { + for packet in packets.iter() { + packet_events.send(PacketEvent { + entity: player_entity, + packet: packet.clone(), + }); } + // clear the packets right after we read them + packets.clear(); } } +} - for (player_entity, packets) in events_owned { - for packet in &packets { - match packet { - ClientboundGamePacket::Login(p) => { - debug!("Got login packet"); +fn process_packet_events(ecs: &mut Ecs) { + let mut events_owned = Vec::new(); + let mut system_state: SystemState> = SystemState::new(ecs); + let mut events = system_state.get_mut(ecs); + for PacketEvent { + entity: player_entity, + packet, + } in events.iter() + { + // we do this so `ecs` isn't borrowed for the whole loop + events_owned.push((*player_entity, packet.clone())); + } + for (player_entity, packet) in events_owned { + match packet { + ClientboundGamePacket::Login(p) => { + debug!("Got login packet"); - #[allow(clippy::type_complexity)] - let mut system_state: SystemState<( - Commands, - Query<( - &mut LocalPlayer, - Option<&mut WorldName>, - &GameProfileComponent, - )>, - ResMut, - )> = SystemState::new(ecs); - let (mut commands, mut query, mut world_container) = system_state.get_mut(ecs); - let (mut local_player, world_name, game_profile) = - query.get_mut(player_entity).unwrap(); + #[allow(clippy::type_complexity)] + let mut system_state: SystemState<( + Commands, + Query<( + &mut LocalPlayer, + Option<&mut WorldName>, + &GameProfileComponent, + )>, + ResMut, + )> = SystemState::new(ecs); + let (mut commands, mut query, mut world_container) = system_state.get_mut(ecs); + let (mut local_player, world_name, game_profile) = + query.get_mut(player_entity).unwrap(); - { - // TODO: have registry_holder be a struct because this sucks rn - // best way would be to add serde support to azalea-nbt + { + // TODO: have registry_holder be a struct because this sucks rn + // best way would be to add serde support to azalea-nbt - let registry_holder = p - .registry_holder - .as_compound() - .expect("Registry holder is not a compound") - .get("") - .expect("No \"\" tag") - .as_compound() - .expect("\"\" tag is not a compound"); - let dimension_types = registry_holder - .get("minecraft:dimension_type") - .expect("No dimension_type tag") - .as_compound() - .expect("dimension_type is not a compound") - .get("value") - .expect("No dimension_type value") - .as_list() - .expect("dimension_type value is not a list"); - let dimension_type = dimension_types - .iter() - .find(|t| { - t.as_compound() - .expect("dimension_type value is not a compound") - .get("name") - .expect("No name tag") - .as_string() - .expect("name is not a string") - == p.dimension_type.to_string() - }) - .unwrap_or_else(|| { - panic!("No dimension_type with name {}", p.dimension_type) - }) - .as_compound() - .unwrap() - .get("element") - .expect("No element tag") - .as_compound() - .expect("element is not a compound"); - let height = (*dimension_type - .get("height") - .expect("No height tag") - .as_int() - .expect("height tag is not an int")) - .try_into() - .expect("height is not a u32"); - let min_y = *dimension_type - .get("min_y") - .expect("No min_y tag") - .as_int() - .expect("min_y tag is not an int"); + let registry_holder = p + .registry_holder + .as_compound() + .expect("Registry holder is not a compound") + .get("") + .expect("No \"\" tag") + .as_compound() + .expect("\"\" tag is not a compound"); + let dimension_types = registry_holder + .get("minecraft:dimension_type") + .expect("No dimension_type tag") + .as_compound() + .expect("dimension_type is not a compound") + .get("value") + .expect("No dimension_type value") + .as_list() + .expect("dimension_type value is not a list"); + let dimension_type = dimension_types + .iter() + .find(|t| { + t.as_compound() + .expect("dimension_type value is not a compound") + .get("name") + .expect("No name tag") + .as_string() + .expect("name is not a string") + == p.dimension_type.to_string() + }) + .unwrap_or_else(|| { + panic!("No dimension_type with name {}", p.dimension_type) + }) + .as_compound() + .unwrap() + .get("element") + .expect("No element tag") + .as_compound() + .expect("element is not a compound"); + let height = (*dimension_type + .get("height") + .expect("No height tag") + .as_int() + .expect("height tag is not an int")) + .try_into() + .expect("height is not a u32"); + let min_y = *dimension_type + .get("min_y") + .expect("No min_y tag") + .as_int() + .expect("min_y tag is not an int"); - let new_world_name = p.dimension.clone(); + let new_world_name = p.dimension.clone(); - if let Some(mut world_name) = world_name { - *world_name = world_name.clone(); - } else { - commands - .entity(player_entity) - .insert(WorldName(new_world_name.clone())); - } - // add this world to the world_container (or don't if it's already - // there) - let weak_world = - world_container.insert(new_world_name.clone(), height, min_y); - // set the partial_world to an empty world - // (when we add chunks or entities those will be in the - // world_container) - - *local_player.partial_world.write() = PartialWorld::new( - local_player.client_information.view_distance.into(), - // this argument makes it so other clients don't update this - // player entity - // in a shared world - Some(player_entity), - ); - local_player.world = weak_world; - - let player_bundle = PlayerBundle { - entity: EntityBundle::new( - game_profile.uuid, - Vec3::default(), - azalea_registry::EntityKind::Player, - new_world_name, - ), - metadata: PlayerMetadataBundle::default(), - }; - // insert our components into the ecs :) + if let Some(mut world_name) = world_name { + *world_name = world_name.clone(); + } else { commands .entity(player_entity) - .insert((MinecraftEntityId(p.player_id), player_bundle)); + .insert(WorldName(new_world_name.clone())); } + // add this world to the world_container (or don't if it's already + // there) + let weak_world = world_container.insert(new_world_name.clone(), height, min_y); + // set the partial_world to an empty world + // (when we add chunks or entities those will be in the + // world_container) - // send the client information that we have set - let client_information_packet: ClientInformation = - local_player.client_information.clone(); - log::debug!( - "Sending client information because login: {:?}", - client_information_packet + *local_player.partial_world.write() = PartialWorld::new( + local_player.client_information.view_distance.into(), + // this argument makes it so other clients don't update this + // player entity + // in a shared world + Some(player_entity), ); - local_player.write_packet(client_information_packet.get()); + local_player.world = weak_world; - // brand - local_player.write_packet( - ServerboundCustomPayloadPacket { - identifier: ResourceLocation::new("brand").unwrap(), - // they don't have to know :) - data: "vanilla".into(), - } - .get(), - ); + let player_bundle = PlayerBundle { + entity: EntityBundle::new( + game_profile.uuid, + Vec3::default(), + azalea_registry::EntityKind::Player, + new_world_name, + ), + metadata: PlayerMetadataBundle::default(), + }; + // insert our components into the ecs :) + commands + .entity(player_entity) + .insert((MinecraftEntityId(p.player_id), player_bundle)); + } - system_state.apply(ecs); - } - ClientboundGamePacket::SetChunkCacheRadius(p) => { - debug!("Got set chunk cache radius packet {:?}", p); - } - ClientboundGamePacket::CustomPayload(p) => { - debug!("Got custom payload packet {:?}", p); - } - ClientboundGamePacket::ChangeDifficulty(p) => { - debug!("Got difficulty packet {:?}", p); - } - ClientboundGamePacket::Commands(_p) => { - debug!("Got declare commands packet"); - } - ClientboundGamePacket::PlayerAbilities(p) => { - debug!("Got player abilities packet {:?}", p); - } - ClientboundGamePacket::SetCarriedItem(p) => { - debug!("Got set carried item packet {:?}", p); - } - ClientboundGamePacket::UpdateTags(_p) => { - debug!("Got update tags packet"); - } - ClientboundGamePacket::Disconnect(p) => { - debug!("Got disconnect packet {:?}", p); - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut disconnect_events = system_state.get_mut(ecs); - disconnect_events.send(DisconnectEvent { - entity: player_entity, - }); - // bye - return; - } - ClientboundGamePacket::UpdateRecipes(_p) => { - debug!("Got update recipes packet"); - } - ClientboundGamePacket::EntityEvent(_p) => { - // debug!("Got entity event packet {:?}", p); - } - ClientboundGamePacket::Recipe(_p) => { - debug!("Got recipe packet"); - } - ClientboundGamePacket::PlayerPosition(p) => { - // TODO: reply with teleport confirm - debug!("Got player position packet {:?}", p); + // send the client information that we have set + let client_information_packet: ClientInformation = + local_player.client_information.clone(); + log::debug!( + "Sending client information because login: {:?}", + client_information_packet + ); + local_player.write_packet(client_information_packet.get()); - let mut system_state: SystemState< - Query<( - &mut LocalPlayer, - &mut Physics, - &mut Position, - &mut LastSentPosition, - )>, - > = SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let Ok((mut local_player, mut physics, mut position, mut last_sent_position)) = + // brand + local_player.write_packet( + ServerboundCustomPayloadPacket { + identifier: ResourceLocation::new("brand").unwrap(), + // they don't have to know :) + data: "vanilla".into(), + } + .get(), + ); + + system_state.apply(ecs); + } + ClientboundGamePacket::SetChunkCacheRadius(p) => { + debug!("Got set chunk cache radius packet {:?}", p); + } + ClientboundGamePacket::CustomPayload(p) => { + debug!("Got custom payload packet {:?}", p); + } + ClientboundGamePacket::ChangeDifficulty(p) => { + debug!("Got difficulty packet {:?}", p); + } + ClientboundGamePacket::Commands(_p) => { + debug!("Got declare commands packet"); + } + ClientboundGamePacket::PlayerAbilities(p) => { + debug!("Got player abilities packet {:?}", p); + } + ClientboundGamePacket::SetCarriedItem(p) => { + debug!("Got set carried item packet {:?}", p); + } + ClientboundGamePacket::UpdateTags(_p) => { + debug!("Got update tags packet"); + } + ClientboundGamePacket::Disconnect(p) => { + debug!("Got disconnect packet {:?}", p); + let mut system_state: SystemState> = + SystemState::new(ecs); + let mut disconnect_events = system_state.get_mut(ecs); + disconnect_events.send(DisconnectEvent { + entity: player_entity, + }); + // bye + return; + } + ClientboundGamePacket::UpdateRecipes(_p) => { + debug!("Got update recipes packet"); + } + ClientboundGamePacket::EntityEvent(_p) => { + // debug!("Got entity event packet {:?}", p); + } + ClientboundGamePacket::Recipe(_p) => { + debug!("Got recipe packet"); + } + ClientboundGamePacket::PlayerPosition(p) => { + // TODO: reply with teleport confirm + debug!("Got player position packet {:?}", p); + + let mut system_state: SystemState< + Query<( + &mut LocalPlayer, + &mut Physics, + &mut Position, + &mut LastSentPosition, + )>, + > = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let Ok((mut local_player, mut physics, mut position, mut last_sent_position)) = query.get_mut(player_entity) else { continue; }; - let delta_movement = physics.delta; + let delta_movement = physics.delta; - let is_x_relative = p.relative_arguments.x; - let is_y_relative = p.relative_arguments.y; - let is_z_relative = p.relative_arguments.z; + let is_x_relative = p.relative_arguments.x; + let is_y_relative = p.relative_arguments.y; + let is_z_relative = p.relative_arguments.z; - let (delta_x, new_pos_x) = if is_x_relative { - last_sent_position.x += p.x; - (delta_movement.x, position.x + p.x) - } else { - last_sent_position.x = p.x; - (0.0, p.x) - }; - let (delta_y, new_pos_y) = if is_y_relative { - last_sent_position.y += p.y; - (delta_movement.y, position.y + p.y) - } else { - last_sent_position.y = p.y; - (0.0, p.y) - }; - let (delta_z, new_pos_z) = if is_z_relative { - last_sent_position.z += p.z; - (delta_movement.z, position.z + p.z) - } else { - last_sent_position.z = p.z; - (0.0, p.z) - }; + let (delta_x, new_pos_x) = if is_x_relative { + last_sent_position.x += p.x; + (delta_movement.x, position.x + p.x) + } else { + last_sent_position.x = p.x; + (0.0, p.x) + }; + let (delta_y, new_pos_y) = if is_y_relative { + last_sent_position.y += p.y; + (delta_movement.y, position.y + p.y) + } else { + last_sent_position.y = p.y; + (0.0, p.y) + }; + let (delta_z, new_pos_z) = if is_z_relative { + last_sent_position.z += p.z; + (delta_movement.z, position.z + p.z) + } else { + last_sent_position.z = p.z; + (0.0, p.z) + }; - let mut y_rot = p.y_rot; - let mut x_rot = p.x_rot; - if p.relative_arguments.x_rot { - x_rot += physics.x_rot; - } - if p.relative_arguments.y_rot { - y_rot += physics.y_rot; + let mut y_rot = p.y_rot; + let mut x_rot = p.x_rot; + if p.relative_arguments.x_rot { + x_rot += physics.x_rot; + } + if p.relative_arguments.y_rot { + y_rot += physics.y_rot; + } + + physics.delta = Vec3 { + x: delta_x, + y: delta_y, + z: delta_z, + }; + // we call a function instead of setting the fields ourself since the + // function makes sure the rotations stay in their + // ranges + set_rotation(&mut physics, y_rot, x_rot); + // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means + // so investigate that ig + let new_pos = Vec3 { + x: new_pos_x, + y: new_pos_y, + z: new_pos_z, + }; + + **position = new_pos; + + local_player.write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get()); + local_player.write_packet( + ServerboundMovePlayerPosRotPacket { + x: new_pos.x, + y: new_pos.y, + z: new_pos.z, + y_rot, + x_rot, + // this is always false + on_ground: false, } + .get(), + ); + } + ClientboundGamePacket::PlayerInfoUpdate(p) => { + debug!("Got player info packet {:?}", p); - physics.delta = Vec3 { - x: delta_x, - y: delta_y, - z: delta_z, - }; - // we call a function instead of setting the fields ourself since the - // function makes sure the rotations stay in their - // ranges - set_rotation(&mut physics, y_rot, x_rot); - // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means - // so investigate that ig - let new_pos = Vec3 { - x: new_pos_x, - y: new_pos_y, - z: new_pos_z, - }; + let mut system_state: SystemState<( + Query<&mut LocalPlayer>, + EventWriter, + EventWriter, + )> = SystemState::new(ecs); + let (mut query, mut add_player_events, mut update_player_events) = + system_state.get_mut(ecs); + let mut local_player = query.get_mut(player_entity).unwrap(); - **position = new_pos; - - local_player - .write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get()); - local_player.write_packet( - ServerboundMovePlayerPosRotPacket { - x: new_pos.x, - y: new_pos.y, - z: new_pos.z, - y_rot, - x_rot, - // this is always false - on_ground: false, + for updated_info in &p.entries { + // add the new player maybe + if p.actions.add_player { + let info = PlayerInfo { + profile: updated_info.profile.clone(), + uuid: updated_info.profile.uuid, + gamemode: updated_info.game_mode, + latency: updated_info.latency, + display_name: updated_info.display_name.clone(), + }; + local_player + .players + .insert(updated_info.profile.uuid, info.clone()); + add_player_events.send(AddPlayerEvent { + entity: player_entity, + info: info.clone(), + }); + } else if let Some(info) = + local_player.players.get_mut(&updated_info.profile.uuid) + { + // `else if` because the block for add_player above + // already sets all the fields + if p.actions.update_game_mode { + info.gamemode = updated_info.game_mode; } - .get(), + if p.actions.update_latency { + info.latency = updated_info.latency; + } + if p.actions.update_display_name { + info.display_name = updated_info.display_name.clone(); + } + update_player_events.send(UpdatePlayerEvent { + entity: player_entity, + info: info.clone(), + }); + } else { + warn!( + "Ignoring PlayerInfoUpdate for unknown player {}", + updated_info.profile.uuid + ); + } + } + } + ClientboundGamePacket::PlayerInfoRemove(p) => { + let mut system_state: SystemState<( + Query<&mut LocalPlayer>, + EventWriter, + )> = SystemState::new(ecs); + let (mut query, mut remove_player_events) = system_state.get_mut(ecs); + let mut local_player = query.get_mut(player_entity).unwrap(); + + for uuid in &p.profile_ids { + if let Some(info) = local_player.players.remove(uuid) { + remove_player_events.send(RemovePlayerEvent { + entity: player_entity, + info, + }); + } + } + } + ClientboundGamePacket::SetChunkCacheCenter(p) => { + debug!("Got chunk cache center packet {:?}", p); + + let mut system_state: SystemState> = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + let mut partial_world = local_player.partial_world.write(); + + partial_world.chunks.view_center = ChunkPos::new(p.x, p.z); + } + ClientboundGamePacket::LevelChunkWithLight(p) => { + debug!("Got chunk with light packet {} {}", p.x, p.z); + let pos = ChunkPos::new(p.x, p.z); + + let mut system_state: SystemState> = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + // OPTIMIZATION: if we already know about the chunk from the + // shared world (and not ourselves), then we don't need to + // parse it again. This is only used when we have a shared + // world, since we check that the chunk isn't currently owned + // by this client. + let shared_chunk = local_player.world.read().chunks.get(&pos); + let this_client_has_chunk = local_player + .partial_world + .read() + .chunks + .limited_get(&pos) + .is_some(); + + let mut world = local_player.world.write(); + let mut partial_world = local_player.partial_world.write(); + + if !this_client_has_chunk { + if let Some(shared_chunk) = shared_chunk { + trace!( + "Skipping parsing chunk {:?} because we already know about it", + pos + ); + partial_world.chunks.set_with_shared_reference( + &pos, + Some(shared_chunk.clone()), + &mut world.chunks, + ); + continue; + } + } + + if let Err(e) = partial_world.chunks.replace_with_packet_data( + &pos, + &mut Cursor::new(&p.chunk_data.data), + &mut world.chunks, + ) { + error!("Couldn't set chunk data: {}", e); + } + } + ClientboundGamePacket::LightUpdate(_p) => { + // debug!("Got light update packet {:?}", p); + } + ClientboundGamePacket::AddEntity(p) => { + debug!("Got add entity packet {:?}", p); + + let mut system_state: SystemState<(Commands, Query>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let world_name = query.get_mut(player_entity).unwrap(); + + if let Some(WorldName(world_name)) = world_name { + let bundle = p.as_entity_bundle(world_name.clone()); + let mut entity_commands = commands.spawn(( + MinecraftEntityId(p.id), + LoadedBy(HashSet::from([player_entity])), + bundle, + )); + // the bundle doesn't include the default entity metadata so we add that + // separately + p.apply_metadata(&mut entity_commands); + } else { + warn!("got add player packet but we haven't gotten a login packet yet"); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::SetEntityData(p) => { + debug!("Got set entity data packet {:?}", p); + + let mut system_state: SystemState<( + Commands, + Query<&mut LocalPlayer>, + Query<&EntityKind>, + )> = SystemState::new(ecs); + let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.id)); + drop(world); + + if let Some(entity) = entity { + let entity_kind = entity_kind_query.get(entity).unwrap(); + let mut entity_commands = commands.entity(entity); + if let Err(e) = apply_metadata( + &mut entity_commands, + **entity_kind, + (*p.packed_items).clone(), + ) { + warn!("{e}"); + } + } else { + warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::UpdateAttributes(_p) => { + // debug!("Got update attributes packet {:?}", p); + } + ClientboundGamePacket::SetEntityMotion(_p) => { + // debug!("Got entity velocity packet {:?}", p); + } + ClientboundGamePacket::SetEntityLink(p) => { + debug!("Got set entity link packet {:?}", p); + } + ClientboundGamePacket::AddPlayer(p) => { + debug!("Got add player packet {:?}", p); + + #[allow(clippy::type_complexity)] + let mut system_state: SystemState<( + Commands, + Query<(&mut LocalPlayer, Option<&WorldName>)>, + )> = SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let (local_player, world_name) = query.get_mut(player_entity).unwrap(); + + if let Some(WorldName(world_name)) = world_name { + let bundle = p.as_player_bundle(world_name.clone()); + let mut spawned = commands.spawn(( + MinecraftEntityId(p.id), + LoadedBy(HashSet::from([player_entity])), + bundle, + )); + + if let Some(player_info) = local_player.players.get(&p.uuid) { + spawned.insert(GameProfileComponent(player_info.profile.clone())); + } + } else { + warn!("got add player packet but we haven't gotten a login packet yet"); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::InitializeBorder(p) => { + debug!("Got initialize border packet {:?}", p); + } + ClientboundGamePacket::SetTime(_p) => { + // debug!("Got set time packet {:?}", p); + } + ClientboundGamePacket::SetDefaultSpawnPosition(p) => { + debug!("Got set default spawn position packet {:?}", p); + } + ClientboundGamePacket::ContainerSetContent(p) => { + debug!("Got container set content packet {:?}", p); + } + ClientboundGamePacket::SetHealth(p) => { + debug!("Got set health packet {:?}", p); + + let mut system_state: SystemState<(Query<&mut Health>, EventWriter)> = + SystemState::new(ecs); + let (mut query, mut death_events) = system_state.get_mut(ecs); + let mut health = query.get_mut(player_entity).unwrap(); + + if p.health == 0. && **health != 0. { + death_events.send(DeathEvent { + entity: player_entity, + packet: None, + }); + } + + **health = p.health; + + // the `Dead` component is added by the `update_dead` system + // in azalea-world and then the `dead_event` system fires + // the Death event. + } + ClientboundGamePacket::SetExperience(p) => { + debug!("Got set experience packet {:?}", p); + } + ClientboundGamePacket::TeleportEntity(p) => { + let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.id)); + drop(world); + + if let Some(entity) = entity { + let new_position = p.position; + commands.add(RelativeEntityUpdate { + entity, + partial_world: local_player.partial_world.clone(), + update: Box::new(move |entity| { + let mut position = entity.get_mut::().unwrap(); + **position = new_position; + }), + }); + } else { + warn!("Got teleport entity packet for unknown entity id {}", p.id); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::UpdateAdvancements(p) => { + debug!("Got update advancements packet {:?}", p); + } + ClientboundGamePacket::RotateHead(_p) => { + // debug!("Got rotate head packet {:?}", p); + } + ClientboundGamePacket::MoveEntityPos(p) => { + let mut system_state: SystemState<(Commands, Query<&LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id)); + drop(world); + + if let Some(entity) = entity { + let delta = p.delta.clone(); + commands.add(RelativeEntityUpdate { + entity, + partial_world: local_player.partial_world.clone(), + update: Box::new(move |entity_mut| { + let mut position = entity_mut.get_mut::().unwrap(); + **position = position.with_delta(&delta); + }), + }); + } else { + warn!( + "Got move entity pos packet for unknown entity id {}", + p.entity_id ); } - ClientboundGamePacket::PlayerInfoUpdate(p) => { - debug!("Got player info packet {:?}", p); - let mut system_state: SystemState<( - Query<&mut LocalPlayer>, - EventWriter, - EventWriter, - )> = SystemState::new(ecs); - let (mut query, mut add_player_events, mut update_player_events) = - system_state.get_mut(ecs); - let mut local_player = query.get_mut(player_entity).unwrap(); - - for updated_info in &p.entries { - // add the new player maybe - if p.actions.add_player { - let info = PlayerInfo { - profile: updated_info.profile.clone(), - uuid: updated_info.profile.uuid, - gamemode: updated_info.game_mode, - latency: updated_info.latency, - display_name: updated_info.display_name.clone(), - }; - local_player - .players - .insert(updated_info.profile.uuid, info.clone()); - add_player_events.send(AddPlayerEvent { - entity: player_entity, - info: info.clone(), - }); - } else if let Some(info) = - local_player.players.get_mut(&updated_info.profile.uuid) - { - // `else if` because the block for add_player above - // already sets all the fields - if p.actions.update_game_mode { - info.gamemode = updated_info.game_mode; - } - if p.actions.update_latency { - info.latency = updated_info.latency; - } - if p.actions.update_display_name { - info.display_name = updated_info.display_name.clone(); - } - update_player_events.send(UpdatePlayerEvent { - entity: player_entity, - info: info.clone(), - }); - } else { - warn!( - "Ignoring PlayerInfoUpdate for unknown player {}", - updated_info.profile.uuid - ); - } - } - } - ClientboundGamePacket::PlayerInfoRemove(p) => { - let mut system_state: SystemState<( - Query<&mut LocalPlayer>, - EventWriter, - )> = SystemState::new(ecs); - let (mut query, mut remove_player_events) = system_state.get_mut(ecs); - let mut local_player = query.get_mut(player_entity).unwrap(); - - for uuid in &p.profile_ids { - if let Some(info) = local_player.players.remove(uuid) { - remove_player_events.send(RemovePlayerEvent { - entity: player_entity, - info, - }); - } - } - } - ClientboundGamePacket::SetChunkCacheCenter(p) => { - debug!("Got chunk cache center packet {:?}", p); - - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - let mut partial_world = local_player.partial_world.write(); - - partial_world.chunks.view_center = ChunkPos::new(p.x, p.z); - } - ClientboundGamePacket::LevelChunkWithLight(p) => { - debug!("Got chunk with light packet {} {}", p.x, p.z); - let pos = ChunkPos::new(p.x, p.z); - - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - // OPTIMIZATION: if we already know about the chunk from the - // shared world (and not ourselves), then we don't need to - // parse it again. This is only used when we have a shared - // world, since we check that the chunk isn't currently owned - // by this client. - let shared_chunk = local_player.world.read().chunks.get(&pos); - let this_client_has_chunk = local_player - .partial_world - .read() - .chunks - .limited_get(&pos) - .is_some(); - - let mut world = local_player.world.write(); - let mut partial_world = local_player.partial_world.write(); - - if !this_client_has_chunk { - if let Some(shared_chunk) = shared_chunk { - trace!( - "Skipping parsing chunk {:?} because we already know about it", - pos - ); - partial_world.chunks.set_with_shared_reference( - &pos, - Some(shared_chunk.clone()), - &mut world.chunks, - ); - continue; - } - } - - if let Err(e) = partial_world.chunks.replace_with_packet_data( - &pos, - &mut Cursor::new(&p.chunk_data.data), - &mut world.chunks, - ) { - error!("Couldn't set chunk data: {}", e); - } - } - ClientboundGamePacket::LightUpdate(_p) => { - // debug!("Got light update packet {:?}", p); - } - ClientboundGamePacket::AddEntity(p) => { - debug!("Got add entity packet {:?}", p); - - let mut system_state: SystemState<(Commands, Query>)> = - SystemState::new(ecs); - let (mut commands, mut query) = system_state.get_mut(ecs); - let world_name = query.get_mut(player_entity).unwrap(); - - if let Some(WorldName(world_name)) = world_name { - let bundle = p.as_entity_bundle(world_name.clone()); - let mut entity_commands = commands.spawn(( - MinecraftEntityId(p.id), - LoadedBy(HashSet::from([player_entity])), - bundle, - )); - // the bundle doesn't include the default entity metadata so we add that - // separately - p.apply_metadata(&mut entity_commands); - } else { - warn!("got add player packet but we haven't gotten a login packet yet"); - } - - system_state.apply(ecs); - } - ClientboundGamePacket::SetEntityData(p) => { - debug!("Got set entity data packet {:?}", p); - - let mut system_state: SystemState<( - Commands, - Query<&mut LocalPlayer>, - Query<&EntityKind>, - )> = SystemState::new(ecs); - let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - let world = local_player.world.read(); - let entity = world.entity_by_id(&MinecraftEntityId(p.id)); - drop(world); - - if let Some(entity) = entity { - let entity_kind = entity_kind_query.get(entity).unwrap(); - let mut entity_commands = commands.entity(entity); - if let Err(e) = apply_metadata( - &mut entity_commands, - **entity_kind, - (*p.packed_items).clone(), - ) { - warn!("{e}"); - } - } else { - warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id); - } - - system_state.apply(ecs); - } - ClientboundGamePacket::UpdateAttributes(_p) => { - // debug!("Got update attributes packet {:?}", p); - } - ClientboundGamePacket::SetEntityMotion(_p) => { - // debug!("Got entity velocity packet {:?}", p); - } - ClientboundGamePacket::SetEntityLink(p) => { - debug!("Got set entity link packet {:?}", p); - } - ClientboundGamePacket::AddPlayer(p) => { - debug!("Got add player packet {:?}", p); - - #[allow(clippy::type_complexity)] - let mut system_state: SystemState<( - Commands, - Query<(&mut LocalPlayer, Option<&WorldName>)>, - )> = SystemState::new(ecs); - let (mut commands, mut query) = system_state.get_mut(ecs); - let (local_player, world_name) = query.get_mut(player_entity).unwrap(); - - if let Some(WorldName(world_name)) = world_name { - let bundle = p.as_player_bundle(world_name.clone()); - let mut spawned = commands.spawn(( - MinecraftEntityId(p.id), - LoadedBy(HashSet::from([player_entity])), - bundle, - )); - - if let Some(player_info) = local_player.players.get(&p.uuid) { - spawned.insert(GameProfileComponent(player_info.profile.clone())); - } - } else { - warn!("got add player packet but we haven't gotten a login packet yet"); - } - - system_state.apply(ecs); - } - ClientboundGamePacket::InitializeBorder(p) => { - debug!("Got initialize border packet {:?}", p); - } - ClientboundGamePacket::SetTime(_p) => { - // debug!("Got set time packet {:?}", p); - } - ClientboundGamePacket::SetDefaultSpawnPosition(p) => { - debug!("Got set default spawn position packet {:?}", p); - } - ClientboundGamePacket::ContainerSetContent(p) => { - debug!("Got container set content packet {:?}", p); - } - ClientboundGamePacket::SetHealth(p) => { - debug!("Got set health packet {:?}", p); - - let mut system_state: SystemState<( - Query<&mut Health>, - EventWriter, - )> = SystemState::new(ecs); - let (mut query, mut death_events) = system_state.get_mut(ecs); - let mut health = query.get_mut(player_entity).unwrap(); - - if p.health == 0. && **health != 0. { - death_events.send(DeathEvent { - entity: player_entity, - packet: None, - }); - } - - **health = p.health; - - // the `Dead` component is added by the `update_dead` system - // in azalea-world and then the `dead_event` system fires - // the Death event. - } - ClientboundGamePacket::SetExperience(p) => { - debug!("Got set experience packet {:?}", p); - } - ClientboundGamePacket::TeleportEntity(p) => { - let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = - SystemState::new(ecs); - let (mut commands, mut query) = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - let world = local_player.world.read(); - let entity = world.entity_by_id(&MinecraftEntityId(p.id)); - drop(world); - - if let Some(entity) = entity { - let new_position = p.position; - commands.add(RelativeEntityUpdate { - entity, - partial_world: local_player.partial_world.clone(), - update: Box::new(move |entity| { - let mut position = entity.get_mut::().unwrap(); - **position = new_position; - }), - }); - } else { - warn!("Got teleport entity packet for unknown entity id {}", p.id); - } - - system_state.apply(ecs); - } - ClientboundGamePacket::UpdateAdvancements(p) => { - debug!("Got update advancements packet {:?}", p); - } - ClientboundGamePacket::RotateHead(_p) => { - // debug!("Got rotate head packet {:?}", p); - } - ClientboundGamePacket::MoveEntityPos(p) => { - let mut system_state: SystemState<(Commands, Query<&LocalPlayer>)> = - SystemState::new(ecs); - let (mut commands, mut query) = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - let world = local_player.world.read(); - let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id)); - drop(world); - - if let Some(entity) = entity { - let delta = p.delta.clone(); - commands.add(RelativeEntityUpdate { - entity, - partial_world: local_player.partial_world.clone(), - update: Box::new(move |entity_mut| { - let mut position = entity_mut.get_mut::().unwrap(); - **position = position.with_delta(&delta); - }), - }); - } else { - warn!( - "Got move entity pos packet for unknown entity id {}", - p.entity_id - ); - } - - system_state.apply(ecs); - } - ClientboundGamePacket::MoveEntityPosRot(p) => { - let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = - SystemState::new(ecs); - let (mut commands, mut query) = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - let world = local_player.world.read(); - let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id)); - drop(world); - - if let Some(entity) = entity { - let delta = p.delta.clone(); - commands.add(RelativeEntityUpdate { - entity, - partial_world: local_player.partial_world.clone(), - update: Box::new(move |entity_mut| { - let mut position = entity_mut.get_mut::().unwrap(); - **position = position.with_delta(&delta); - }), - }); - } else { - warn!( - "Got move entity pos rot packet for unknown entity id {}", - p.entity_id - ); - } - - system_state.apply(ecs); - } - - ClientboundGamePacket::MoveEntityRot(_p) => { - // debug!("Got move entity rot packet {:?}", p); - } - ClientboundGamePacket::KeepAlive(p) => { - debug!("Got keep alive packet {p:?} for {player_entity:?}"); - - let mut system_state: SystemState<( - Query<&mut LocalPlayer>, - EventWriter, - )> = SystemState::new(ecs); - let (mut query, mut keepalive_events) = system_state.get_mut(ecs); - - keepalive_events.send(KeepAliveEvent { - entity: player_entity, - id: p.id, - }); - - let mut local_player = query.get_mut(player_entity).unwrap(); - local_player.write_packet(ServerboundKeepAlivePacket { id: p.id }.get()); - debug!("Sent keep alive packet {p:?} for {player_entity:?}"); - } - ClientboundGamePacket::RemoveEntities(p) => { - debug!("Got remove entities packet {:?}", p); - } - ClientboundGamePacket::PlayerChat(p) => { - debug!("Got player chat packet {:?}", p); - - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut chat_events = system_state.get_mut(ecs); - - chat_events.send(ChatReceivedEvent { - entity: player_entity, - packet: ChatPacket::Player(Arc::new(p.clone())), - }); - } - ClientboundGamePacket::SystemChat(p) => { - debug!("Got system chat packet {:?}", p); - - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut chat_events = system_state.get_mut(ecs); - - chat_events.send(ChatReceivedEvent { - entity: player_entity, - packet: ChatPacket::System(Arc::new(p.clone())), - }); - } - ClientboundGamePacket::Sound(_p) => { - // debug!("Got sound packet {:?}", p); - } - ClientboundGamePacket::LevelEvent(p) => { - debug!("Got level event packet {:?}", p); - } - ClientboundGamePacket::BlockUpdate(p) => { - debug!("Got block update packet {:?}", p); - - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - let world = local_player.world.write(); - - world.chunks.set_block_state(&p.pos, p.block_state); - } - ClientboundGamePacket::Animate(p) => { - debug!("Got animate packet {:?}", p); - } - ClientboundGamePacket::SectionBlocksUpdate(p) => { - debug!("Got section blocks update packet {:?}", p); - let mut system_state: SystemState> = - SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - let world = local_player.world.write(); - - for state in &p.states { - world - .chunks - .set_block_state(&(p.section_pos + state.pos.clone()), state.state); - } - } - ClientboundGamePacket::GameEvent(p) => { - debug!("Got game event packet {:?}", p); - } - ClientboundGamePacket::LevelParticles(p) => { - debug!("Got level particles packet {:?}", p); - } - ClientboundGamePacket::ServerData(p) => { - debug!("Got server data packet {:?}", p); - } - ClientboundGamePacket::SetEquipment(p) => { - debug!("Got set equipment packet {:?}", p); - } - ClientboundGamePacket::UpdateMobEffect(p) => { - debug!("Got update mob effect packet {:?}", p); - } - ClientboundGamePacket::AddExperienceOrb(_) => {} - ClientboundGamePacket::AwardStats(_) => {} - ClientboundGamePacket::BlockChangedAck(_) => {} - ClientboundGamePacket::BlockDestruction(_) => {} - ClientboundGamePacket::BlockEntityData(_) => {} - ClientboundGamePacket::BlockEvent(p) => { - debug!("Got block event packet {:?}", p); - } - ClientboundGamePacket::BossEvent(_) => {} - ClientboundGamePacket::CommandSuggestions(_) => {} - ClientboundGamePacket::ContainerSetData(_) => {} - ClientboundGamePacket::ContainerSetSlot(_) => {} - ClientboundGamePacket::Cooldown(_) => {} - ClientboundGamePacket::CustomChatCompletions(_) => {} - ClientboundGamePacket::DeleteChat(_) => {} - ClientboundGamePacket::Explode(_) => {} - ClientboundGamePacket::ForgetLevelChunk(_) => {} - ClientboundGamePacket::HorseScreenOpen(_) => {} - ClientboundGamePacket::MapItemData(_) => {} - ClientboundGamePacket::MerchantOffers(_) => {} - ClientboundGamePacket::MoveVehicle(_) => {} - ClientboundGamePacket::OpenBook(_) => {} - ClientboundGamePacket::OpenScreen(_) => {} - ClientboundGamePacket::OpenSignEditor(_) => {} - ClientboundGamePacket::Ping(_) => {} - ClientboundGamePacket::PlaceGhostRecipe(_) => {} - ClientboundGamePacket::PlayerCombatEnd(_) => {} - ClientboundGamePacket::PlayerCombatEnter(_) => {} - ClientboundGamePacket::PlayerCombatKill(p) => { - debug!("Got player kill packet {:?}", p); - - #[allow(clippy::type_complexity)] - let mut system_state: SystemState<( - Commands, - Query<(&MinecraftEntityId, Option<&Dead>)>, - EventWriter, - )> = SystemState::new(ecs); - let (mut commands, mut query, mut death_events) = system_state.get_mut(ecs); - let (entity_id, dead) = query.get_mut(player_entity).unwrap(); - - if **entity_id == p.player_id && dead.is_none() { - commands.entity(player_entity).insert(Dead); - death_events.send(DeathEvent { - entity: player_entity, - packet: Some(p.clone()), - }); - } - - system_state.apply(ecs); - } - ClientboundGamePacket::PlayerLookAt(_) => {} - ClientboundGamePacket::RemoveMobEffect(_) => {} - ClientboundGamePacket::ResourcePack(_) => {} - ClientboundGamePacket::Respawn(p) => { - debug!("Got respawn packet {:?}", p); - - let mut system_state: SystemState = SystemState::new(ecs); - let mut commands = system_state.get(ecs); - - // Remove the Dead marker component from the player. - commands.entity(player_entity).remove::(); - - system_state.apply(ecs); - } - ClientboundGamePacket::SelectAdvancementsTab(_) => {} - ClientboundGamePacket::SetActionBarText(_) => {} - ClientboundGamePacket::SetBorderCenter(_) => {} - ClientboundGamePacket::SetBorderLerpSize(_) => {} - ClientboundGamePacket::SetBorderSize(_) => {} - ClientboundGamePacket::SetBorderWarningDelay(_) => {} - ClientboundGamePacket::SetBorderWarningDistance(_) => {} - ClientboundGamePacket::SetCamera(_) => {} - ClientboundGamePacket::SetDisplayObjective(_) => {} - ClientboundGamePacket::SetObjective(_) => {} - ClientboundGamePacket::SetPassengers(_) => {} - ClientboundGamePacket::SetPlayerTeam(_) => {} - ClientboundGamePacket::SetScore(_) => {} - ClientboundGamePacket::SetSimulationDistance(_) => {} - ClientboundGamePacket::SetSubtitleText(_) => {} - ClientboundGamePacket::SetTitleText(_) => {} - ClientboundGamePacket::SetTitlesAnimation(_) => {} - ClientboundGamePacket::SoundEntity(_) => {} - ClientboundGamePacket::StopSound(_) => {} - ClientboundGamePacket::TabList(_) => {} - ClientboundGamePacket::TagQuery(_) => {} - ClientboundGamePacket::TakeItemEntity(_) => {} - ClientboundGamePacket::DisguisedChat(_) => {} - ClientboundGamePacket::UpdateEnabledFeatures(_) => {} - ClientboundGamePacket::ContainerClose(_) => {} + system_state.apply(ecs); } + ClientboundGamePacket::MoveEntityPosRot(p) => { + let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id)); + drop(world); + + if let Some(entity) = entity { + let delta = p.delta.clone(); + commands.add(RelativeEntityUpdate { + entity, + partial_world: local_player.partial_world.clone(), + update: Box::new(move |entity_mut| { + let mut position = entity_mut.get_mut::().unwrap(); + **position = position.with_delta(&delta); + }), + }); + } else { + warn!( + "Got move entity pos rot packet for unknown entity id {}", + p.entity_id + ); + } + + system_state.apply(ecs); + } + + ClientboundGamePacket::MoveEntityRot(_p) => { + // debug!("Got move entity rot packet {:?}", p); + } + ClientboundGamePacket::KeepAlive(p) => { + debug!("Got keep alive packet {p:?} for {player_entity:?}"); + + let mut system_state: SystemState<( + Query<&mut LocalPlayer>, + EventWriter, + )> = SystemState::new(ecs); + let (mut query, mut keepalive_events) = system_state.get_mut(ecs); + + keepalive_events.send(KeepAliveEvent { + entity: player_entity, + id: p.id, + }); + + let mut local_player = query.get_mut(player_entity).unwrap(); + local_player.write_packet(ServerboundKeepAlivePacket { id: p.id }.get()); + debug!("Sent keep alive packet {p:?} for {player_entity:?}"); + } + ClientboundGamePacket::RemoveEntities(p) => { + debug!("Got remove entities packet {:?}", p); + } + ClientboundGamePacket::PlayerChat(p) => { + debug!("Got player chat packet {:?}", p); + + let mut system_state: SystemState> = + SystemState::new(ecs); + let mut chat_events = system_state.get_mut(ecs); + + chat_events.send(ChatReceivedEvent { + entity: player_entity, + packet: ChatPacket::Player(Arc::new(p.clone())), + }); + } + ClientboundGamePacket::SystemChat(p) => { + debug!("Got system chat packet {:?}", p); + + let mut system_state: SystemState> = + SystemState::new(ecs); + let mut chat_events = system_state.get_mut(ecs); + + chat_events.send(ChatReceivedEvent { + entity: player_entity, + packet: ChatPacket::System(Arc::new(p.clone())), + }); + } + ClientboundGamePacket::Sound(_p) => { + // debug!("Got sound packet {:?}", p); + } + ClientboundGamePacket::LevelEvent(p) => { + debug!("Got level event packet {:?}", p); + } + ClientboundGamePacket::BlockUpdate(p) => { + debug!("Got block update packet {:?}", p); + + let mut system_state: SystemState> = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.write(); + + world.chunks.set_block_state(&p.pos, p.block_state); + } + ClientboundGamePacket::Animate(p) => { + debug!("Got animate packet {:?}", p); + } + ClientboundGamePacket::SectionBlocksUpdate(p) => { + debug!("Got section blocks update packet {:?}", p); + let mut system_state: SystemState> = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.write(); + + for state in &p.states { + world + .chunks + .set_block_state(&(p.section_pos + state.pos.clone()), state.state); + } + } + ClientboundGamePacket::GameEvent(p) => { + debug!("Got game event packet {:?}", p); + } + ClientboundGamePacket::LevelParticles(p) => { + debug!("Got level particles packet {:?}", p); + } + ClientboundGamePacket::ServerData(p) => { + debug!("Got server data packet {:?}", p); + } + ClientboundGamePacket::SetEquipment(p) => { + debug!("Got set equipment packet {:?}", p); + } + ClientboundGamePacket::UpdateMobEffect(p) => { + debug!("Got update mob effect packet {:?}", p); + } + ClientboundGamePacket::AddExperienceOrb(_) => {} + ClientboundGamePacket::AwardStats(_) => {} + ClientboundGamePacket::BlockChangedAck(_) => {} + ClientboundGamePacket::BlockDestruction(_) => {} + ClientboundGamePacket::BlockEntityData(_) => {} + ClientboundGamePacket::BlockEvent(p) => { + debug!("Got block event packet {:?}", p); + } + ClientboundGamePacket::BossEvent(_) => {} + ClientboundGamePacket::CommandSuggestions(_) => {} + ClientboundGamePacket::ContainerSetData(_) => {} + ClientboundGamePacket::ContainerSetSlot(_) => {} + ClientboundGamePacket::Cooldown(_) => {} + ClientboundGamePacket::CustomChatCompletions(_) => {} + ClientboundGamePacket::DeleteChat(_) => {} + ClientboundGamePacket::Explode(_) => {} + ClientboundGamePacket::ForgetLevelChunk(_) => {} + ClientboundGamePacket::HorseScreenOpen(_) => {} + ClientboundGamePacket::MapItemData(_) => {} + ClientboundGamePacket::MerchantOffers(_) => {} + ClientboundGamePacket::MoveVehicle(_) => {} + ClientboundGamePacket::OpenBook(_) => {} + ClientboundGamePacket::OpenScreen(_) => {} + ClientboundGamePacket::OpenSignEditor(_) => {} + ClientboundGamePacket::Ping(_) => {} + ClientboundGamePacket::PlaceGhostRecipe(_) => {} + ClientboundGamePacket::PlayerCombatEnd(_) => {} + ClientboundGamePacket::PlayerCombatEnter(_) => {} + ClientboundGamePacket::PlayerCombatKill(p) => { + debug!("Got player kill packet {:?}", p); + + #[allow(clippy::type_complexity)] + let mut system_state: SystemState<( + Commands, + Query<(&MinecraftEntityId, Option<&Dead>)>, + EventWriter, + )> = SystemState::new(ecs); + let (mut commands, mut query, mut death_events) = system_state.get_mut(ecs); + let (entity_id, dead) = query.get_mut(player_entity).unwrap(); + + if **entity_id == p.player_id && dead.is_none() { + commands.entity(player_entity).insert(Dead); + death_events.send(DeathEvent { + entity: player_entity, + packet: Some(p.clone()), + }); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::PlayerLookAt(_) => {} + ClientboundGamePacket::RemoveMobEffect(_) => {} + ClientboundGamePacket::ResourcePack(_) => {} + ClientboundGamePacket::Respawn(p) => { + debug!("Got respawn packet {:?}", p); + + let mut system_state: SystemState = SystemState::new(ecs); + let mut commands = system_state.get(ecs); + + // Remove the Dead marker component from the player. + commands.entity(player_entity).remove::(); + + system_state.apply(ecs); + } + ClientboundGamePacket::SelectAdvancementsTab(_) => {} + ClientboundGamePacket::SetActionBarText(_) => {} + ClientboundGamePacket::SetBorderCenter(_) => {} + ClientboundGamePacket::SetBorderLerpSize(_) => {} + ClientboundGamePacket::SetBorderSize(_) => {} + ClientboundGamePacket::SetBorderWarningDelay(_) => {} + ClientboundGamePacket::SetBorderWarningDistance(_) => {} + ClientboundGamePacket::SetCamera(_) => {} + ClientboundGamePacket::SetDisplayObjective(_) => {} + ClientboundGamePacket::SetObjective(_) => {} + ClientboundGamePacket::SetPassengers(_) => {} + ClientboundGamePacket::SetPlayerTeam(_) => {} + ClientboundGamePacket::SetScore(_) => {} + ClientboundGamePacket::SetSimulationDistance(_) => {} + ClientboundGamePacket::SetSubtitleText(_) => {} + ClientboundGamePacket::SetTitleText(_) => {} + ClientboundGamePacket::SetTitlesAnimation(_) => {} + ClientboundGamePacket::SoundEntity(_) => {} + ClientboundGamePacket::StopSound(_) => {} + ClientboundGamePacket::TabList(_) => {} + ClientboundGamePacket::TagQuery(_) => {} + ClientboundGamePacket::TakeItemEntity(_) => {} + ClientboundGamePacket::DisguisedChat(_) => {} + ClientboundGamePacket::UpdateEnabledFeatures(_) => {} + ClientboundGamePacket::ContainerClose(_) => {} } } } @@ -974,7 +1019,6 @@ impl PacketReceiver { break; }; } - println!("Write task finished"); // receiver is automatically closed when it's dropped } } diff --git a/azalea-ecs/azalea-ecs-macros/src/component.rs b/azalea-ecs/azalea-ecs-macros/src/component.rs index 306b64de..e076bbe1 100644 --- a/azalea-ecs/azalea-ecs-macros/src/component.rs +++ b/azalea-ecs/azalea-ecs-macros/src/component.rs @@ -19,7 +19,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); TokenStream::from(quote! { - impl #impl_generics #azalea_ecs_path::system::BevyResource for #struct_name #type_generics #where_clause { + impl #impl_generics #azalea_ecs_path::system::_BevyResource for #struct_name #type_generics #where_clause { } }) } @@ -44,7 +44,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); TokenStream::from(quote! { - impl #impl_generics #azalea_ecs_path::component::BevyComponent for #struct_name #type_generics #where_clause { + impl #impl_generics #azalea_ecs_path::component::_BevyComponent for #struct_name #type_generics #where_clause { type Storage = #storage; } }) diff --git a/azalea-ecs/azalea-ecs-macros/src/lib.rs b/azalea-ecs/azalea-ecs-macros/src/lib.rs index 09ccb094..9d4e9b2d 100755 --- a/azalea-ecs/azalea-ecs-macros/src/lib.rs +++ b/azalea-ecs/azalea-ecs-macros/src/lib.rs @@ -151,13 +151,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { match field_kind { BundleFieldKind::Component => { field_component_ids.push(quote! { - <#field_type as #ecs_path::bundle::BevyBundle>::component_ids(components, storages, &mut *ids); + <#field_type as #ecs_path::bundle::_BevyBundle>::component_ids(components, storages, &mut *ids); }); field_get_components.push(quote! { self.#field.get_components(&mut *func); }); field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::BevyBundle>::from_components(ctx, &mut *func), + #field: <#field_type as #ecs_path::bundle::_BevyBundle>::from_components(ctx, &mut *func), }); } @@ -174,7 +174,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { TokenStream::from(quote! { /// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order - unsafe impl #impl_generics #ecs_path::bundle::BevyBundle for #struct_name #ty_generics #where_clause { + unsafe impl #impl_generics #ecs_path::bundle::_BevyBundle for #struct_name #ty_generics #where_clause { fn component_ids( components: &mut #ecs_path::component::Components, storages: &mut #ecs_path::storage::Storages, @@ -488,7 +488,9 @@ pub fn derive_stage_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = azalea_ecs_path(); trait_path.segments.push(format_ident!("schedule").into()); - trait_path.segments.push(format_ident!("StageLabel").into()); + trait_path + .segments + .push(format_ident!("_BevyStageLabel").into()); derive_label(input, &trait_path, "stage_label") } diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs b/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs index 5fbba0e9..c79e3efe 100644 --- a/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs +++ b/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs @@ -38,6 +38,7 @@ impl Default for BevyManifest { impl BevyManifest { pub fn maybe_get_path(&self, name: &str) -> Option { const AZALEA: &str = "azalea"; + const AZALEA_ECS: &str = "azalea_ecs"; const BEVY_ECS: &str = "bevy_ecs"; const BEVY: &str = "bevy"; @@ -57,6 +58,8 @@ impl BevyManifest { return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); } else if let Some(dep) = deps.get(AZALEA) { dep_package(dep).unwrap_or(AZALEA) + } else if let Some(dep) = deps.get(AZALEA_ECS) { + dep_package(dep).unwrap_or(AZALEA_ECS) } else if let Some(dep) = deps.get(BEVY_ECS) { dep_package(dep).unwrap_or(BEVY_ECS) } else if let Some(dep) = deps.get(BEVY) { diff --git a/azalea-ecs/src/lib.rs b/azalea-ecs/src/lib.rs index 571bd741..bc374e45 100644 --- a/azalea-ecs/src/lib.rs +++ b/azalea-ecs/src/lib.rs @@ -29,14 +29,14 @@ pub mod component { // we do this because re-exporting Component would re-export the macro as well, // which is bad (since we have our own Component macro) // instead, we have to do this so Component is a trait alias and the original - // impl-able trait is still available as BevyComponent + // impl-able trait is still available as _BevyComponent pub trait Component = bevy_ecs::component::Component; - pub use bevy_ecs::component::Component as BevyComponent; + pub use bevy_ecs::component::Component as _BevyComponent; } pub mod bundle { pub use azalea_ecs_macros::Bundle; pub trait Bundle = bevy_ecs::bundle::Bundle; - pub use bevy_ecs::bundle::Bundle as BevyBundle; + pub use bevy_ecs::bundle::Bundle as _BevyBundle; } pub mod system { pub use azalea_ecs_macros::Resource; @@ -44,10 +44,19 @@ pub mod system { Command, Commands, EntityCommands, Query, Res, ResMut, SystemState, }; pub trait Resource = bevy_ecs::system::Resource; - pub use bevy_ecs::system::Resource as BevyResource; + pub use bevy_ecs::system::Resource as _BevyResource; +} +pub mod schedule { + pub use azalea_ecs_macros::StageLabel; + pub use bevy_ecs::schedule::{ + IntoRunCriteria, IntoSystemDescriptor, ReportExecutionOrderAmbiguities, Schedule, Stage, + SystemSet, SystemStage, + }; + pub trait StageLabel = bevy_ecs::schedule::StageLabel; + pub use bevy_ecs::schedule::StageLabel as _BevyStageLabel; } pub use bevy_app as app; -pub use bevy_ecs::{entity, event, ptr, query, schedule, storage}; +pub use bevy_ecs::{entity, event, ptr, query, storage}; use app::{App, CoreStage, Plugin}; use bevy_ecs::schedule::*; diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index 053e29b3..55c440a2 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -9,6 +9,7 @@ use azalea::pathfinder::BlockPosGoal; use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection}; use azalea::{Account, Client, Event}; use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket; +use azalea_protocol::packets::game::ClientboundGamePacket; use std::time::Duration; #[derive(Default, Clone, Component)] @@ -148,6 +149,12 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< action: azalea_protocol::packets::game::serverbound_client_command_packet::Action::PerformRespawn, }.get()); } + Event::Packet(packet) => match *packet { + ClientboundGamePacket::Login(_) => { + println!("login packet"); + } + _ => {} + }, _ => {} }