aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--azalea-client/src/events.rs36
-rw-r--r--azalea-client/src/packet_handling.rs1602
-rw-r--r--azalea-ecs/azalea-ecs-macros/src/component.rs4
-rwxr-xr-xazalea-ecs/azalea-ecs-macros/src/lib.rs10
-rw-r--r--azalea-ecs/azalea-ecs-macros/src/utils/mod.rs3
-rw-r--r--azalea-ecs/src/lib.rs19
-rw-r--r--azalea/examples/testbot.rs7
7 files changed, 882 insertions, 799 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<ClientboundGamePacket>),
/// 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<PacketReceiver>>) {
- 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<PacketEvent>) {
+ 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<PacketEvent>) {
+/// 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::<AddPlayerEvent>()
- .add_event::<RemovePlayerEvent>()
- .add_event::<UpdatePlayerEvent>()
- .add_event::<ChatReceivedEvent>()
- .add_event::<DeathEvent>()
- .add_event::<KeepAliveEvent>();
+ 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::<Events<PacketEvent>>()
+ .add_event::<AddPlayerEvent>()
+ .add_event::<RemovePlayerEvent>()
+ .add_event::<UpdatePlayerEvent>()
+ .add_event::<ChatReceivedEvent>()
+ .add_event::<DeathEvent>()
+ .add_event::<KeepAliveEvent>();
}
}
@@ -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<PacketReceiver>>,
- > = 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<Events<PacketEvent>>,
+) {
+ // 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");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(
- &mut LocalPlayer,
- Option<&mut WorldName>,
- &GameProfileComponent,
- )>,
- ResMut<WorldContainer>,
- )> = 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
-
- 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();
-
- 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 :)
+fn process_packet_events(ecs: &mut Ecs) {
+ let mut events_owned = Vec::new();
+ let mut system_state: SystemState<EventReader<PacketEvent>> = 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<WorldContainer>,
+ )> = 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
+
+ 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();
+
+ 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()));
}
-
- // 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
+ // 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.write_packet(client_information_packet.get());
-
- // 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<EventWriter<DisconnectEvent>> =
- 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 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 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,
+ 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(),
};
- // 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);
-
- let mut system_state: SystemState<(
- Query<&mut LocalPlayer>,
- EventWriter<AddPlayerEvent>,
- EventWriter<UpdatePlayerEvent>,
- )> = 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
- );
- }
- }
+ // insert our components into the ecs :)
+ commands
+ .entity(player_entity)
+ .insert((MinecraftEntityId(p.player_id), player_bundle));
}
- ClientboundGamePacket::PlayerInfoRemove(p) => {
- let mut system_state: SystemState<(
- Query<&mut LocalPlayer>,
- EventWriter<RemovePlayerEvent>,
- )> = 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<Query<&mut LocalPlayer>> =
- 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();
+ // 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());
+
+ // brand
+ local_player.write_packet(
+ ServerboundCustomPayloadPacket {
+ identifier: ResourceLocation::new("brand").unwrap(),
+ // they don't have to know :)
+ data: "vanilla".into(),
+ }
+ .get(),
+ );
- 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<Query<&mut LocalPlayer>> =
- 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,
- );
+ 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<EventWriter<DisconnectEvent>> =
+ 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;
- }
- }
+ };
- 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);
- }
+ 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 (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;
}
- ClientboundGamePacket::LightUpdate(_p) => {
- // debug!("Got light update packet {:?}", p);
+ if p.relative_arguments.y_rot {
+ y_rot += physics.y_rot;
}
- ClientboundGamePacket::AddEntity(p) => {
- debug!("Got add entity packet {:?}", p);
-
- let mut system_state: SystemState<(Commands, Query<Option<&WorldName>>)> =
- 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);
+ 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,
}
-
- 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()));
+ .get(),
+ );
+ }
+ ClientboundGamePacket::PlayerInfoUpdate(p) => {
+ debug!("Got player info packet {:?}", p);
+
+ let mut system_state: SystemState<(
+ Query<&mut LocalPlayer>,
+ EventWriter<AddPlayerEvent>,
+ EventWriter<UpdatePlayerEvent>,
+ )> = 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!("got add player packet but we haven't gotten a login packet yet");
+ warn!(
+ "Ignoring PlayerInfoUpdate for unknown player {}",
+ updated_info.profile.uuid
+ );
}
-
- 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<DeathEvent>,
- )> = 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 {
+ }
+ ClientboundGamePacket::PlayerInfoRemove(p) => {
+ let mut system_state: SystemState<(
+ Query<&mut LocalPlayer>,
+ EventWriter<RemovePlayerEvent>,
+ )> = 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,
- packet: None,
+ info,
});
}
+ }
+ }
+ ClientboundGamePacket::SetChunkCacheCenter(p) => {
+ debug!("Got chunk cache center packet {:?}", p);
- **health = p.health;
+ let mut system_state: SystemState<Query<&mut LocalPlayer>> = 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();
- // 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::<Position>().unwrap();
- **position = new_position;
- }),
- });
- } else {
- warn!("Got teleport entity packet for unknown entity id {}", p.id);
+ 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<Query<&mut LocalPlayer>> = 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;
}
-
- system_state.apply(ecs);
}
- ClientboundGamePacket::UpdateAdvancements(p) => {
- debug!("Got update advancements packet {:?}", p);
+
+ 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::RotateHead(_p) => {
- // debug!("Got rotate head packet {:?}", p);
+ }
+ 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<Option<&WorldName>>)> =
+ 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");
}
- 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::<Position>().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::<Position>().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::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}");
}
-
- system_state.apply(ecs);
+ } else {
+ warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
}
- ClientboundGamePacket::MoveEntityRot(_p) => {
- // debug!("Got move entity rot packet {:?}", p);
+ 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");
}
- ClientboundGamePacket::KeepAlive(p) => {
- debug!("Got keep alive packet {p:?} for {player_entity:?}");
- let mut system_state: SystemState<(
- Query<&mut LocalPlayer>,
- EventWriter<KeepAliveEvent>,
- )> = SystemState::new(ecs);
- let (mut query, mut keepalive_events) = system_state.get_mut(ecs);
+ 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<DeathEvent>)> =
+ SystemState::new(ecs);
+ let (mut query, mut death_events) = system_state.get_mut(ecs);
+ let mut health = query.get_mut(player_entity).unwrap();
- keepalive_events.send(KeepAliveEvent {
+ if p.health == 0. && **health != 0. {
+ death_events.send(DeathEvent {
entity: player_entity,
- id: p.id,
+ packet: None,
});
-
- 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<EventWriter<ChatReceivedEvent>> =
- SystemState::new(ecs);
- let mut chat_events = system_state.get_mut(ecs);
+ **health = p.health;
- chat_events.send(ChatReceivedEvent {
- entity: player_entity,
- packet: ChatPacket::Player(Arc::new(p.clone())),
+ // 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::<Position>().unwrap();
+ **position = new_position;
+ }),
});
+ } else {
+ warn!("Got teleport entity packet for unknown entity id {}", p.id);
}
- ClientboundGamePacket::SystemChat(p) => {
- debug!("Got system chat packet {:?}", p);
-
- let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
- 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())),
+ 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::<Position>().unwrap();
+ **position = position.with_delta(&delta);
+ }),
});
+ } else {
+ warn!(
+ "Got move entity pos packet for unknown entity id {}",
+ p.entity_id
+ );
}
- ClientboundGamePacket::Sound(_p) => {
- // debug!("Got sound packet {:?}", p);
- }
- ClientboundGamePacket::LevelEvent(p) => {
- debug!("Got level event packet {:?}", p);
+
+ 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::<Position>().unwrap();
+ **position = position.with_delta(&delta);
+ }),
+ });
+ } else {
+ warn!(
+ "Got move entity pos rot packet for unknown entity id {}",
+ p.entity_id
+ );
}
- ClientboundGamePacket::BlockUpdate(p) => {
- debug!("Got block update packet {:?}", p);
- let mut system_state: SystemState<Query<&mut LocalPlayer>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
+ 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<KeepAliveEvent>,
+ )> = 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<EventWriter<ChatReceivedEvent>> =
+ SystemState::new(ecs);
+ let mut chat_events = system_state.get_mut(ecs);
- let world = local_player.world.write();
+ chat_events.send(ChatReceivedEvent {
+ entity: player_entity,
+ packet: ChatPacket::Player(Arc::new(p.clone())),
+ });
+ }
+ ClientboundGamePacket::SystemChat(p) => {
+ debug!("Got system chat packet {:?}", p);
- 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<Query<&mut LocalPlayer>> =
- 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);
+ let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
+ 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<Query<&mut LocalPlayer>> = 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<Query<&mut LocalPlayer>> = 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::AddExperienceOrb(_) => {}
- ClientboundGamePacket::AwardStats(_) => {}
- ClientboundGamePacket::BlockChangedAck(_) => {}
- ClientboundGamePacket::BlockDestruction(_) => {}
- ClientboundGamePacket::BlockEntityData(_) => {}
- ClientboundGamePacket::BlockEvent(p) => {
- debug!("Got block event packet {:?}", p);
+ }
+ 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<DeathEvent>,
+ )> = 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()),
+ });
}
- 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<DeathEvent>,
- )> = 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);
+ system_state.apply(ecs);
+ }
+ ClientboundGamePacket::PlayerLookAt(_) => {}
+ ClientboundGamePacket::RemoveMobEffect(_) => {}
+ ClientboundGamePacket::ResourcePack(_) => {}
+ ClientboundGamePacket::Respawn(p) => {
+ debug!("Got respawn packet {:?}", p);
- let mut system_state: SystemState<Commands> = SystemState::new(ecs);
- let mut commands = system_state.get(ecs);
+ let mut system_state: SystemState<Commands> = SystemState::new(ecs);
+ let mut commands = system_state.get(ecs);
- // Remove the Dead marker component from the player.
- commands.entity(player_entity).remove::<Dead>();
+ // Remove the Dead marker component from the player.
+ commands.entity(player_entity).remove::<Dead>();
- 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::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<syn::Path> {
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");
+ }
+ _ => {}
+ },
_ => {}
}