aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/packet_handling
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-02-22 21:45:26 -0600
committerGitHub <noreply@github.com>2025-02-22 21:45:26 -0600
commite21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7 (patch)
treeadd6f8bfce40d0c07845d8aa4c9945a0b918444c /azalea-client/src/packet_handling
parentf8130c3c92946d2293634ba4e252d6bc93026c3c (diff)
downloadazalea-drasl-e21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7.tar.xz
Refactor azalea-client (#205)
* start organizing packet_handling more by moving packet handlers into their own functions * finish writing all the handler functions for packets * use macro for generating match statement for packet handler functions * fix set_entity_data * update config state to also use handler functions * organize az-client file structure by moving things into plugins directory * fix merge issues
Diffstat (limited to 'azalea-client/src/packet_handling')
-rw-r--r--azalea-client/src/packet_handling/configuration.rs271
-rw-r--r--azalea-client/src/packet_handling/game.rs1585
-rw-r--r--azalea-client/src/packet_handling/login.rs114
-rw-r--r--azalea-client/src/packet_handling/mod.rs78
4 files changed, 0 insertions, 2048 deletions
diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs
deleted file mode 100644
index bfa6914b..00000000
--- a/azalea-client/src/packet_handling/configuration.rs
+++ /dev/null
@@ -1,271 +0,0 @@
-use std::io::Cursor;
-
-use azalea_entity::indexing::EntityIdIndex;
-use azalea_protocol::packets::config::s_finish_configuration::ServerboundFinishConfiguration;
-use azalea_protocol::packets::config::s_keep_alive::ServerboundKeepAlive;
-use azalea_protocol::packets::config::s_select_known_packs::ServerboundSelectKnownPacks;
-use azalea_protocol::packets::config::{
- self, ClientboundConfigPacket, ServerboundConfigPacket, ServerboundCookieResponse,
- ServerboundResourcePack,
-};
-use azalea_protocol::packets::{ConnectionProtocol, Packet};
-use azalea_protocol::read::deserialize_packet;
-use bevy_ecs::prelude::*;
-use bevy_ecs::system::SystemState;
-use tracing::{debug, error, warn};
-
-use crate::InstanceHolder;
-use crate::client::InConfigState;
-use crate::disconnect::DisconnectEvent;
-use crate::local_player::Hunger;
-use crate::packet_handling::game::KeepAliveEvent;
-use crate::raw_connection::RawConnection;
-
-#[derive(Event, Debug, Clone)]
-pub struct ConfigurationEvent {
- /// The client entity that received the packet.
- pub entity: Entity,
- /// The packet that was actually received.
- pub packet: ClientboundConfigPacket,
-}
-
-pub fn send_packet_events(
- query: Query<(Entity, &RawConnection), With<InConfigState>>,
- mut packet_events: ResMut<Events<ConfigurationEvent>>,
-) {
- // 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, raw_conn) in &query {
- let packets_lock = raw_conn.incoming_packet_queue();
- let mut packets = packets_lock.lock();
- if !packets.is_empty() {
- for raw_packet in packets.iter() {
- let packet = match deserialize_packet::<ClientboundConfigPacket>(&mut Cursor::new(
- raw_packet,
- )) {
- Ok(packet) => packet,
- Err(err) => {
- error!("failed to read packet: {err:?}");
- debug!("packet bytes: {raw_packet:?}");
- continue;
- }
- };
- packet_events.send(ConfigurationEvent {
- entity: player_entity,
- packet,
- });
- }
- // clear the packets right after we read them
- packets.clear();
- }
- }
-}
-
-pub fn process_packet_events(ecs: &mut World) {
- let mut events_owned = Vec::new();
- let mut system_state: SystemState<EventReader<ConfigurationEvent>> = SystemState::new(ecs);
- let mut events = system_state.get_mut(ecs);
- for ConfigurationEvent {
- entity: player_entity,
- packet,
- } in events.read()
- {
- // 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 {
- ClientboundConfigPacket::RegistryData(p) => {
- let mut system_state: SystemState<Query<&mut InstanceHolder>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let instance_holder = query.get_mut(player_entity).unwrap();
- let mut instance = instance_holder.instance.write();
-
- // add the new registry data
- instance.registries.append(p.registry_id, p.entries);
- }
-
- ClientboundConfigPacket::CustomPayload(p) => {
- debug!("Got custom payload packet {p:?}");
- }
- ClientboundConfigPacket::Disconnect(p) => {
- warn!("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,
- reason: Some(p.reason.clone()),
- });
- }
- ClientboundConfigPacket::FinishConfiguration(p) => {
- debug!("got FinishConfiguration packet: {p:?}");
-
- let mut system_state: SystemState<Query<&mut RawConnection>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let mut raw_conn = query.get_mut(player_entity).unwrap();
-
- raw_conn
- .write_packet(ServerboundFinishConfiguration)
- .expect(
- "we should be in the right state and encoding this packet shouldn't fail",
- );
- raw_conn.set_state(ConnectionProtocol::Game);
-
- // these components are added now that we're going to be in the Game state
- ecs.entity_mut(player_entity)
- .remove::<InConfigState>()
- .insert(crate::JoinedClientBundle {
- physics_state: crate::PhysicsState::default(),
- inventory: crate::inventory::Inventory::default(),
- tab_list: crate::local_player::TabList::default(),
- current_sequence_number: crate::interact::CurrentSequenceNumber::default(),
- last_sent_direction: crate::movement::LastSentLookDirection::default(),
- abilities: crate::local_player::PlayerAbilities::default(),
- permission_level: crate::local_player::PermissionLevel::default(),
- hunger: Hunger::default(),
- chunk_batch_info: crate::chunks::ChunkBatchInfo::default(),
-
- entity_id_index: EntityIdIndex::default(),
-
- mining: crate::mining::MineBundle::default(),
- attack: crate::attack::AttackBundle::default(),
-
- _local_entity: azalea_entity::LocalEntity,
- });
- }
- ClientboundConfigPacket::KeepAlive(p) => {
- debug!("Got keep alive packet (in configuration) {p:?} for {player_entity:?}");
-
- let mut system_state: SystemState<(
- Query<&RawConnection>,
- EventWriter<KeepAliveEvent>,
- )> = SystemState::new(ecs);
- let (query, mut keepalive_events) = system_state.get_mut(ecs);
- let raw_conn = query.get(player_entity).unwrap();
-
- keepalive_events.send(KeepAliveEvent {
- entity: player_entity,
- id: p.id,
- });
- raw_conn
- .write_packet(ServerboundKeepAlive { id: p.id })
- .unwrap();
- }
- ClientboundConfigPacket::Ping(p) => {
- debug!("Got ping packet {p:?}");
-
- let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let raw_conn = query.get_mut(player_entity).unwrap();
-
- raw_conn
- .write_packet(config::s_pong::ServerboundPong { id: p.id })
- .unwrap();
- }
- ClientboundConfigPacket::ResourcePackPush(p) => {
- debug!("Got resource pack packet {p:?}");
-
- let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let raw_conn = query.get_mut(player_entity).unwrap();
-
- // always accept resource pack
- raw_conn
- .write_packet(ServerboundResourcePack {
- id: p.id,
- action: config::s_resource_pack::Action::Accepted,
- })
- .unwrap();
- }
- ClientboundConfigPacket::ResourcePackPop(_) => {
- // we can ignore this
- }
- ClientboundConfigPacket::UpdateEnabledFeatures(p) => {
- debug!("Got update enabled features packet {p:?}");
- }
- ClientboundConfigPacket::UpdateTags(_p) => {
- debug!("Got update tags packet");
- }
- ClientboundConfigPacket::CookieRequest(p) => {
- debug!("Got cookie request packet {p:?}");
-
- let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let raw_conn = query.get_mut(player_entity).unwrap();
-
- raw_conn
- .write_packet(ServerboundCookieResponse {
- key: p.key,
- // cookies aren't implemented
- payload: None,
- })
- .unwrap();
- }
- ClientboundConfigPacket::ResetChat(p) => {
- debug!("Got reset chat packet {p:?}");
- }
- ClientboundConfigPacket::StoreCookie(p) => {
- debug!("Got store cookie packet {p:?}");
- }
- ClientboundConfigPacket::Transfer(p) => {
- debug!("Got transfer packet {p:?}");
- }
- ClientboundConfigPacket::SelectKnownPacks(p) => {
- debug!("Got select known packs packet {p:?}");
-
- let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let raw_conn = query.get_mut(player_entity).unwrap();
-
- // resource pack management isn't implemented
- raw_conn
- .write_packet(ServerboundSelectKnownPacks {
- known_packs: vec![],
- })
- .unwrap();
- }
- ClientboundConfigPacket::ServerLinks(_) => {}
- ClientboundConfigPacket::CustomReportDetails(_) => {}
- }
- }
-}
-
-/// An event for sending a packet to the server while we're in the
-/// `configuration` state.
-#[derive(Event)]
-pub struct SendConfigurationEvent {
- pub sent_by: Entity,
- pub packet: ServerboundConfigPacket,
-}
-impl SendConfigurationEvent {
- pub fn new(sent_by: Entity, packet: impl Packet<ServerboundConfigPacket>) -> Self {
- let packet = packet.into_variant();
- Self { sent_by, packet }
- }
-}
-
-pub fn handle_send_packet_event(
- mut send_packet_events: EventReader<SendConfigurationEvent>,
- mut query: Query<(&mut RawConnection, Option<&InConfigState>)>,
-) {
- for event in send_packet_events.read() {
- if let Ok((raw_conn, in_configuration_state)) = query.get_mut(event.sent_by) {
- if in_configuration_state.is_none() {
- error!(
- "Tried to send a configuration packet {:?} while not in configuration state",
- event.packet
- );
- continue;
- }
- debug!("Sending packet: {:?}", event.packet);
- if let Err(e) = raw_conn.write_packet(event.packet.clone()) {
- error!("Failed to send packet: {e}");
- }
- }
- }
-}
diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs
deleted file mode 100644
index 6f2868e9..00000000
--- a/azalea-client/src/packet_handling/game.rs
+++ /dev/null
@@ -1,1585 +0,0 @@
-use std::{
- collections::HashSet,
- io::Cursor,
- ops::Add,
- sync::{Arc, Weak},
-};
-
-use azalea_chat::FormattedText;
-use azalea_core::{
- game_type::GameMode,
- math,
- position::{ChunkPos, Vec3},
- resource_location::ResourceLocation,
-};
-use azalea_entity::{
- Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection,
- Physics, Position, RelativeEntityUpdate,
- indexing::{EntityIdIndex, EntityUuidIndex},
- metadata::{Health, apply_metadata},
-};
-use azalea_protocol::{
- packets::{
- Packet,
- game::{
- ClientboundGamePacket, ServerboundGamePacket,
- c_player_combat_kill::ClientboundPlayerCombatKill,
- s_accept_teleportation::ServerboundAcceptTeleportation,
- s_configuration_acknowledged::ServerboundConfigurationAcknowledged,
- s_keep_alive::ServerboundKeepAlive, s_move_player_pos_rot::ServerboundMovePlayerPosRot,
- s_pong::ServerboundPong,
- },
- },
- read::deserialize_packet,
-};
-use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
-use bevy_ecs::{prelude::*, system::SystemState};
-use parking_lot::RwLock;
-use tracing::{debug, error, trace, warn};
-use uuid::Uuid;
-
-use crate::{
- ClientInformation, PlayerInfo,
- chat::{ChatPacket, ChatReceivedEvent},
- chunks,
- disconnect::DisconnectEvent,
- inventory::{
- ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
- },
- local_player::{
- GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList,
- },
- movement::{KnockbackEvent, KnockbackType},
- raw_connection::RawConnection,
-};
-
-/// An event that's sent when we receive a packet.
-/// ```
-/// # use azalea_client::packet_handling::game::PacketEvent;
-/// # use azalea_protocol::packets::game::ClientboundGamePacket;
-/// # use bevy_ecs::event::EventReader;
-///
-/// fn handle_packets(mut events: EventReader<PacketEvent>) {
-/// for PacketEvent {
-/// entity,
-/// packet,
-/// } in events.read() {
-/// match packet.as_ref() {
-/// ClientboundGamePacket::LevelParticles(p) => {
-/// // ...
-/// }
-/// _ => {}
-/// }
-/// }
-/// }
-/// ```
-#[derive(Event, Debug, Clone)]
-pub struct PacketEvent {
- /// The client entity that received the packet.
- pub entity: Entity,
- /// The packet that was actually received.
- pub packet: Arc<ClientboundGamePacket>,
-}
-
-/// A player joined the game (or more specifically, was added to the tab
-/// list of a local player).
-#[derive(Event, Debug, Clone)]
-pub struct AddPlayerEvent {
- /// The local player entity that received this event.
- pub entity: Entity,
- pub info: PlayerInfo,
-}
-/// A player left the game (or maybe is still in the game and was just
-/// removed from the tab list of a local player).
-#[derive(Event, Debug, Clone)]
-pub struct RemovePlayerEvent {
- /// The local player entity that received this event.
- pub entity: Entity,
- pub info: PlayerInfo,
-}
-/// A player was updated in the tab list of a local player (gamemode, display
-/// name, or latency changed).
-#[derive(Event, Debug, Clone)]
-pub struct UpdatePlayerEvent {
- /// The local player entity that received this event.
- pub entity: Entity,
- pub info: PlayerInfo,
-}
-
-/// Event for when an entity dies. dies. If it's a local player and there's a
-/// reason in the death screen, the [`ClientboundPlayerCombatKill`] will
-/// be included.
-#[derive(Event, Debug, Clone)]
-pub struct DeathEvent {
- pub entity: Entity,
- pub packet: Option<ClientboundPlayerCombatKill>,
-}
-
-/// A KeepAlive packet is sent from the server to verify that the client is
-/// still connected.
-#[derive(Event, Debug, Clone)]
-pub struct KeepAliveEvent {
- pub entity: Entity,
- /// The ID of the keepalive. This is an arbitrary number, but vanilla
- /// servers use the time to generate this.
- pub id: u64,
-}
-
-#[derive(Event, Debug, Clone)]
-pub struct ResourcePackEvent {
- pub entity: Entity,
- /// The random ID for this request to download the resource pack. The packet
- /// for replying to a resource pack push must contain the same ID.
- pub id: Uuid,
- pub url: String,
- pub hash: String,
- pub required: bool,
- pub prompt: Option<FormattedText>,
-}
-
-/// An instance (aka world, dimension) was loaded by a client.
-///
-/// Since the instance is given to you as a weak reference, it won't be able to
-/// be `upgrade`d if all local players leave it.
-#[derive(Event, Debug, Clone)]
-pub struct InstanceLoadedEvent {
- pub entity: Entity,
- pub name: ResourceLocation,
- pub instance: Weak<RwLock<Instance>>,
-}
-
-pub fn send_packet_events(
- query: Query<(Entity, &RawConnection), With<LocalEntity>>,
- 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, raw_connection) in &query {
- let packets_lock = raw_connection.incoming_packet_queue();
- let mut packets = packets_lock.lock();
- if !packets.is_empty() {
- for raw_packet in packets.iter() {
- let packet =
- match deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new(raw_packet))
- {
- Ok(packet) => packet,
- Err(err) => {
- error!("failed to read packet: {err:?}");
- debug!("packet bytes: {raw_packet:?}");
- continue;
- }
- };
- packet_events.send(PacketEvent {
- entity: player_entity,
- packet: Arc::new(packet),
- });
- }
- // clear the packets right after we read them
- packets.clear();
- }
- }
-}
-
-pub fn process_packet_events(ecs: &mut World) {
- let mut events_owned = Vec::<(Entity, Arc<ClientboundGamePacket>)>::new();
- {
- let mut system_state = SystemState::<EventReader<PacketEvent>>::new(ecs);
- let mut events = system_state.get_mut(ecs);
- for PacketEvent {
- entity: player_entity,
- packet,
- } in events.read()
- {
- // 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 {
- let packet_clone = packet.clone();
- let packet_ref = packet_clone.as_ref();
- match packet_ref {
- ClientboundGamePacket::Login(p) => {
- debug!("Got login packet");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(
- &GameProfileComponent,
- &ClientInformation,
- Option<&mut InstanceName>,
- Option<&mut LoadedBy>,
- &mut EntityIdIndex,
- &mut InstanceHolder,
- )>,
- EventWriter<InstanceLoadedEvent>,
- ResMut<InstanceContainer>,
- ResMut<EntityUuidIndex>,
- EventWriter<SendPacketEvent>,
- )> = SystemState::new(ecs);
- let (
- mut commands,
- mut query,
- mut instance_loaded_events,
- mut instance_container,
- mut entity_uuid_index,
- mut send_packet_events,
- ) = system_state.get_mut(ecs);
- let (
- game_profile,
- client_information,
- instance_name,
- loaded_by,
- mut entity_id_index,
- mut instance_holder,
- ) = query.get_mut(player_entity).unwrap();
-
- {
- let new_instance_name = p.common.dimension.clone();
-
- if let Some(mut instance_name) = instance_name {
- *instance_name = instance_name.clone();
- } else {
- commands
- .entity(player_entity)
- .insert(InstanceName(new_instance_name.clone()));
- }
-
- let Some((_dimension_type, dimension_data)) = p
- .common
- .dimension_type(&instance_holder.instance.read().registries)
- else {
- continue;
- };
-
- // add this world to the instance_container (or don't if it's already
- // there)
- let weak_instance = instance_container.insert(
- new_instance_name.clone(),
- dimension_data.height,
- dimension_data.min_y,
- &instance_holder.instance.read().registries,
- );
- instance_loaded_events.send(InstanceLoadedEvent {
- entity: player_entity,
- name: new_instance_name.clone(),
- instance: Arc::downgrade(&weak_instance),
- });
-
- // set the partial_world to an empty world
- // (when we add chunks or entities those will be in the
- // instance_container)
-
- *instance_holder.partial_instance.write() = PartialInstance::new(
- azalea_world::chunk_storage::calculate_chunk_storage_range(
- client_information.view_distance.into(),
- ),
- // this argument makes it so other clients don't update this player entity
- // in a shared instance
- Some(player_entity),
- );
- {
- let map = instance_holder.instance.read().registries.map.clone();
- let new_registries = &mut weak_instance.write().registries;
- // add the registries from this instance to the weak instance
- for (registry_name, registry) in map {
- new_registries.map.insert(registry_name, registry);
- }
- }
- instance_holder.instance = weak_instance;
-
- let entity_bundle = EntityBundle::new(
- game_profile.uuid,
- Vec3::default(),
- azalea_registry::EntityKind::Player,
- new_instance_name,
- );
- let entity_id = p.player_id;
- // insert our components into the ecs :)
- commands.entity(player_entity).insert((
- entity_id,
- LocalGameMode {
- current: p.common.game_type,
- previous: p.common.previous_game_type.into(),
- },
- entity_bundle,
- ));
-
- azalea_entity::indexing::add_entity_to_indexes(
- entity_id,
- player_entity,
- Some(game_profile.uuid),
- &mut entity_id_index,
- &mut entity_uuid_index,
- &mut instance_holder.instance.write(),
- );
-
- // update or insert loaded_by
- if let Some(mut loaded_by) = loaded_by {
- loaded_by.insert(player_entity);
- } else {
- commands
- .entity(player_entity)
- .insert(LoadedBy(HashSet::from_iter(vec![player_entity])));
- }
- }
-
- // send the client information that we have set
- debug!(
- "Sending client information because login: {:?}",
- client_information
- );
- send_packet_events.send(SendPacketEvent::new(player_entity,
- azalea_protocol::packets::game::s_client_information::ServerboundClientInformation { information: client_information.clone() },
- ));
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::SetChunkCacheRadius(p) => {
- debug!("Got set chunk cache radius packet {p:?}");
- }
-
- ClientboundGamePacket::ChunkBatchStart(_p) => {
- // the packet is empty, just a marker to tell us when the batch starts and ends
- debug!("Got chunk batch start");
- let mut system_state: SystemState<EventWriter<chunks::ChunkBatchStartEvent>> =
- SystemState::new(ecs);
- let mut chunk_batch_start_events = system_state.get_mut(ecs);
-
- chunk_batch_start_events.send(chunks::ChunkBatchStartEvent {
- entity: player_entity,
- });
- }
- ClientboundGamePacket::ChunkBatchFinished(p) => {
- debug!("Got chunk batch finished {p:?}");
-
- let mut system_state: SystemState<EventWriter<chunks::ChunkBatchFinishedEvent>> =
- SystemState::new(ecs);
- let mut chunk_batch_start_events = system_state.get_mut(ecs);
-
- chunk_batch_start_events.send(chunks::ChunkBatchFinishedEvent {
- entity: player_entity,
- batch_size: p.batch_size,
- });
- }
-
- 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:?}");
- let mut system_state: SystemState<Query<&mut PlayerAbilities>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let mut player_abilities = query.get_mut(player_entity).unwrap();
-
- *player_abilities = PlayerAbilities::from(p);
- }
- ClientboundGamePacket::SetCursorItem(p) => {
- debug!("Got set cursor item packet {p:?}");
- }
- ClientboundGamePacket::UpdateTags(_p) => {
- debug!("Got update tags packet");
- }
- ClientboundGamePacket::Disconnect(p) => {
- warn!("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,
- reason: Some(p.reason.clone()),
- });
- }
- ClientboundGamePacket::UpdateRecipes(_p) => {
- debug!("Got update recipes packet");
- }
- ClientboundGamePacket::EntityEvent(_p) => {
- // debug!("Got entity event packet {p:?}");
- }
- ClientboundGamePacket::PlayerPosition(p) => {
- debug!("Got player position packet {p:?}");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Query<(
- &mut Physics,
- &mut LookDirection,
- &mut Position,
- &mut LastSentPosition,
- )>,
- EventWriter<SendPacketEvent>,
- )> = SystemState::new(ecs);
- let (mut query, mut send_packet_events) = system_state.get_mut(ecs);
- let Ok((mut physics, mut direction, mut position, mut last_sent_position)) =
- query.get_mut(player_entity)
- else {
- continue;
- };
-
- **last_sent_position = **position;
-
- fn apply_change<T: Add<Output = T>>(base: T, condition: bool, change: T) -> T {
- if condition { base + change } else { change }
- }
-
- let new_x = apply_change(position.x, p.relative.x, p.change.pos.x);
- let new_y = apply_change(position.y, p.relative.y, p.change.pos.y);
- let new_z = apply_change(position.z, p.relative.z, p.change.pos.z);
-
- let new_y_rot = apply_change(
- direction.y_rot,
- p.relative.y_rot,
- p.change.look_direction.y_rot,
- );
- let new_x_rot = apply_change(
- direction.x_rot,
- p.relative.x_rot,
- p.change.look_direction.x_rot,
- );
-
- let mut new_delta_from_rotations = physics.velocity;
- if p.relative.rotate_delta {
- let y_rot_delta = direction.y_rot - new_y_rot;
- let x_rot_delta = direction.x_rot - new_x_rot;
- new_delta_from_rotations = new_delta_from_rotations
- .x_rot(math::to_radians(x_rot_delta as f64) as f32)
- .y_rot(math::to_radians(y_rot_delta as f64) as f32);
- }
-
- let new_delta = Vec3::new(
- apply_change(
- new_delta_from_rotations.x,
- p.relative.delta_x,
- p.change.delta.x,
- ),
- apply_change(
- new_delta_from_rotations.y,
- p.relative.delta_y,
- p.change.delta.y,
- ),
- apply_change(
- new_delta_from_rotations.z,
- p.relative.delta_z,
- p.change.delta.z,
- ),
- );
-
- // apply the updates
-
- physics.velocity = new_delta;
-
- (direction.y_rot, direction.x_rot) = (new_y_rot, new_x_rot);
-
- let new_pos = Vec3::new(new_x, new_y, new_z);
- if new_pos != **position {
- **position = new_pos;
- }
-
- // old_pos is set to the current position when we're teleported
- physics.set_old_pos(&position);
-
- // send the relevant packets
-
- send_packet_events.send(SendPacketEvent::new(
- player_entity,
- ServerboundAcceptTeleportation { id: p.id },
- ));
- send_packet_events.send(SendPacketEvent::new(
- player_entity,
- ServerboundMovePlayerPosRot {
- pos: new_pos,
- look_direction: LookDirection::new(new_y_rot, new_x_rot),
- // this is always false
- on_ground: false,
- },
- ));
- }
- ClientboundGamePacket::PlayerInfoUpdate(p) => {
- debug!("Got player info packet {p:?}");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Query<&mut TabList>,
- EventWriter<AddPlayerEvent>,
- EventWriter<UpdatePlayerEvent>,
- ResMut<TabList>,
- )> = SystemState::new(ecs);
- let (
- mut query,
- mut add_player_events,
- mut update_player_events,
- mut tab_list_resource,
- ) = system_state.get_mut(ecs);
- let mut tab_list = 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(),
- };
- tab_list.insert(updated_info.profile.uuid, info.clone());
- add_player_events.send(AddPlayerEvent {
- entity: player_entity,
- info: info.clone(),
- });
- } else if let Some(info) = tab_list.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.clone_from(&updated_info.display_name);
- }
- update_player_events.send(UpdatePlayerEvent {
- entity: player_entity,
- info: info.clone(),
- });
- } else {
- let uuid = updated_info.profile.uuid;
- #[cfg(debug_assertions)]
- warn!("Ignoring PlayerInfoUpdate for unknown player {uuid}");
- #[cfg(not(debug_assertions))]
- debug!("Ignoring PlayerInfoUpdate for unknown player {uuid}");
- }
- }
-
- *tab_list_resource = tab_list.clone();
- }
- ClientboundGamePacket::PlayerInfoRemove(p) => {
- let mut system_state: SystemState<(
- Query<&mut TabList>,
- EventWriter<RemovePlayerEvent>,
- ResMut<TabList>,
- )> = SystemState::new(ecs);
- let (mut query, mut remove_player_events, mut tab_list_resource) =
- system_state.get_mut(ecs);
- let mut tab_list = query.get_mut(player_entity).unwrap();
-
- for uuid in &p.profile_ids {
- if let Some(info) = tab_list.remove(uuid) {
- remove_player_events.send(RemovePlayerEvent {
- entity: player_entity,
- info,
- });
- }
- tab_list_resource.remove(uuid);
- }
- }
- ClientboundGamePacket::SetChunkCacheCenter(p) => {
- debug!("Got chunk cache center packet {p:?}");
-
- let mut system_state: SystemState<Query<&mut InstanceHolder>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let instance_holder = query.get_mut(player_entity).unwrap();
- let mut partial_world = instance_holder.partial_instance.write();
-
- partial_world
- .chunks
- .update_view_center(ChunkPos::new(p.x, p.z));
- }
- ClientboundGamePacket::ChunksBiomes(_) => {}
- ClientboundGamePacket::LightUpdate(_p) => {
- // debug!("Got light update packet {p:?}");
- }
- ClientboundGamePacket::LevelChunkWithLight(p) => {
- debug!("Got chunk with light packet {} {}", p.x, p.z);
-
- let mut system_state: SystemState<EventWriter<chunks::ReceiveChunkEvent>> =
- SystemState::new(ecs);
- let mut receive_chunk_events = system_state.get_mut(ecs);
- receive_chunk_events.send(chunks::ReceiveChunkEvent {
- entity: player_entity,
- packet: p.clone(),
- });
- }
- ClientboundGamePacket::AddEntity(p) => {
- debug!("Got add entity packet {p:?}");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(&mut EntityIdIndex, Option<&InstanceName>, Option<&TabList>)>,
- Query<&mut LoadedBy>,
- Query<Entity>,
- Res<InstanceContainer>,
- ResMut<EntityUuidIndex>,
- )> = SystemState::new(ecs);
- let (
- mut commands,
- mut query,
- mut loaded_by_query,
- entity_query,
- instance_container,
- mut entity_uuid_index,
- ) = system_state.get_mut(ecs);
- let (mut entity_id_index, instance_name, tab_list) =
- query.get_mut(player_entity).unwrap();
-
- let entity_id = p.id;
-
- let Some(instance_name) = instance_name else {
- warn!("got add player packet but we haven't gotten a login packet yet");
- continue;
- };
-
- // check if the entity already exists, and if it does then only add to LoadedBy
- let instance = instance_container.get(instance_name).unwrap();
- if let Some(&ecs_entity) = instance.read().entity_by_id.get(&entity_id) {
- // entity already exists
- let Ok(mut loaded_by) = loaded_by_query.get_mut(ecs_entity) else {
- // LoadedBy for this entity isn't in the ecs! figure out what went wrong
- // and print an error
-
- let entity_in_ecs = entity_query.get(ecs_entity).is_ok();
-
- if entity_in_ecs {
- error!(
- "LoadedBy for entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id"
- );
- } else {
- error!(
- "Entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id"
- );
- }
- continue;
- };
- loaded_by.insert(player_entity);
-
- // per-client id index
- entity_id_index.insert(entity_id, ecs_entity);
-
- debug!("added to LoadedBy of entity {ecs_entity:?} with id {entity_id:?}");
- continue;
- };
-
- // entity doesn't exist in the global index!
-
- let bundle = p.as_entity_bundle((**instance_name).clone());
- let mut spawned =
- commands.spawn((entity_id, LoadedBy(HashSet::from([player_entity])), bundle));
- let ecs_entity: Entity = spawned.id();
- debug!("spawned entity {ecs_entity:?} with id {entity_id:?}");
-
- azalea_entity::indexing::add_entity_to_indexes(
- entity_id,
- ecs_entity,
- Some(p.uuid),
- &mut entity_id_index,
- &mut entity_uuid_index,
- &mut instance.write(),
- );
-
- // add the GameProfileComponent if the uuid is in the tab list
- if let Some(tab_list) = tab_list {
- // (technically this makes it possible for non-player entities to have
- // GameProfileComponents but the server would have to be doing something
- // really weird)
- if let Some(player_info) = tab_list.get(&p.uuid) {
- spawned.insert(GameProfileComponent(player_info.profile.clone()));
- }
- }
-
- // the bundle doesn't include the default entity metadata so we add that
- // separately
- p.apply_metadata(&mut spawned);
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::SetEntityData(p) => {
- debug!("Got set entity data packet {p:?}");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &InstanceHolder)>,
- Query<&EntityKind>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- let entity = entity_id_index.get(p.id);
-
- let Some(entity) = entity else {
- // some servers like hypixel trigger this a lot :(
- debug!(
- "Server sent an entity data packet for an entity id ({}) that we don't know about",
- p.id
- );
- continue;
- };
- let entity_kind = *entity_kind_query.get(entity).unwrap();
-
- let packed_items = p.packed_items.clone().to_vec();
-
- // we use RelativeEntityUpdate because it makes sure changes aren't made
- // multiple times
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity| {
- let entity_id = entity.id();
- entity.world_scope(|world| {
- let mut commands_system_state = SystemState::<Commands>::new(world);
- let mut commands = commands_system_state.get_mut(world);
- let mut entity_commands = commands.entity(entity_id);
- if let Err(e) =
- apply_metadata(&mut entity_commands, *entity_kind, packed_items)
- {
- warn!("{e}");
- }
- commands_system_state.apply(world);
- });
- }),
- });
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::UpdateAttributes(_p) => {
- // debug!("Got update attributes packet {p:?}");
- }
- ClientboundGamePacket::SetEntityMotion(p) => {
- // vanilla servers use this packet for knockback, but note that the Explode
- // packet is also sometimes used by servers for knockback
-
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &InstanceHolder)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- let Some(entity) = entity_id_index.get(p.id) else {
- // note that this log (and some other ones like the one in RemoveEntities)
- // sometimes happens when killing mobs. it seems to be a vanilla bug, which is
- // why it's a debug log instead of a warning
- debug!(
- "Got set entity motion packet for unknown entity id {}",
- p.id
- );
- continue;
- };
-
- // this is to make sure the same entity velocity update doesn't get sent
- // multiple times when in swarms
-
- let knockback = KnockbackType::Set(Vec3 {
- x: p.xa as f64 / 8000.,
- y: p.ya as f64 / 8000.,
- z: p.za as f64 / 8000.,
- });
-
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- entity_mut.world_scope(|world| {
- world.send_event(KnockbackEvent { entity, knockback })
- });
- }),
- });
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::SetEntityLink(p) => {
- debug!("Got set entity link packet {p:?}");
- }
- 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::SetHealth(p) => {
- debug!("Got set health packet {p:?}");
-
- let mut system_state: SystemState<Query<(&mut Health, &mut Hunger)>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let (mut health, mut hunger) = query.get_mut(player_entity).unwrap();
-
- **health = p.health;
- (hunger.food, hunger.saturation) = (p.food, p.saturation);
-
- // 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<(&EntityIdIndex, &InstanceHolder)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- let Some(entity) = entity_id_index.get(p.id) else {
- warn!("Got teleport entity packet for unknown entity id {}", p.id);
- continue;
- };
-
- let new_pos = p.change.pos;
- let new_look_direction = LookDirection {
- x_rot: (p.change.look_direction.x_rot as i32 * 360) as f32 / 256.,
- y_rot: (p.change.look_direction.y_rot as i32 * 360) as f32 / 256.,
- };
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity| {
- let mut position = entity.get_mut::<Position>().unwrap();
- if new_pos != **position {
- **position = new_pos;
- }
- let position = *position;
- let mut look_direction = entity.get_mut::<LookDirection>().unwrap();
- if new_look_direction != *look_direction {
- *look_direction = new_look_direction;
- }
- // old_pos is set to the current position when we're teleported
- let mut physics = entity.get_mut::<Physics>().unwrap();
- physics.set_old_pos(&position);
- }),
- });
-
- 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<(&EntityIdIndex, &InstanceHolder)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- debug!("Got move entity pos packet {p:?}");
-
- let Some(entity) = entity_id_index.get(p.entity_id) else {
- debug!(
- "Got move entity pos packet for unknown entity id {}",
- p.entity_id
- );
- continue;
- };
-
- let new_delta = p.delta.clone();
- let new_on_ground = p.on_ground;
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- let mut physics = entity_mut.get_mut::<Physics>().unwrap();
- let new_pos = physics.vec_delta_codec.decode(
- new_delta.xa as i64,
- new_delta.ya as i64,
- new_delta.za as i64,
- );
- physics.vec_delta_codec.set_base(new_pos);
- physics.set_on_ground(new_on_ground);
-
- let mut position = entity_mut.get_mut::<Position>().unwrap();
- if new_pos != **position {
- **position = new_pos;
- }
- }),
- });
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::MoveEntityPosRot(p) => {
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &InstanceHolder)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- debug!("Got move entity pos rot packet {p:?}");
-
- let entity = entity_id_index.get(p.entity_id);
-
- if let Some(entity) = entity {
- let new_delta = p.delta.clone();
- let new_look_direction = LookDirection {
- x_rot: (p.x_rot as i32 * 360) as f32 / 256.,
- y_rot: (p.y_rot as i32 * 360) as f32 / 256.,
- };
-
- let new_on_ground = p.on_ground;
-
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- let mut physics = entity_mut.get_mut::<Physics>().unwrap();
- let new_pos = physics.vec_delta_codec.decode(
- new_delta.xa as i64,
- new_delta.ya as i64,
- new_delta.za as i64,
- );
- physics.vec_delta_codec.set_base(new_pos);
- physics.set_on_ground(new_on_ground);
-
- let mut position = entity_mut.get_mut::<Position>().unwrap();
- if new_pos != **position {
- **position = new_pos;
- }
-
- let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
- if new_look_direction != *look_direction {
- *look_direction = new_look_direction;
- }
- }),
- });
- } else {
- // often triggered by hypixel :(
- debug!(
- "Got move entity pos rot packet for unknown entity id {}",
- p.entity_id
- );
- }
-
- system_state.apply(ecs);
- }
-
- ClientboundGamePacket::MoveEntityRot(p) => {
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &InstanceHolder)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- let entity = entity_id_index.get(p.entity_id);
-
- if let Some(entity) = entity {
- let new_look_direction = LookDirection {
- x_rot: (p.x_rot as i32 * 360) as f32 / 256.,
- y_rot: (p.y_rot as i32 * 360) as f32 / 256.,
- };
- let new_on_ground = p.on_ground;
-
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- let mut physics = entity_mut.get_mut::<Physics>().unwrap();
- physics.set_on_ground(new_on_ground);
-
- let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
- if new_look_direction != *look_direction {
- *look_direction = new_look_direction;
- }
- }),
- });
- } else {
- warn!(
- "Got move entity rot packet for unknown entity id {}",
- p.entity_id
- );
- }
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::KeepAlive(p) => {
- debug!("Got keep alive packet {p:?} for {player_entity:?}");
-
- let mut system_state: SystemState<(
- EventWriter<KeepAliveEvent>,
- EventWriter<SendPacketEvent>,
- )> = SystemState::new(ecs);
- let (mut keepalive_events, mut send_packet_events) = system_state.get_mut(ecs);
-
- keepalive_events.send(KeepAliveEvent {
- entity: player_entity,
- id: p.id,
- });
- send_packet_events.send(SendPacketEvent::new(
- player_entity,
- ServerboundKeepAlive { id: p.id },
- ));
- }
- ClientboundGamePacket::RemoveEntities(p) => {
- debug!("Got remove entities packet {p:?}");
-
- let mut system_state: SystemState<(
- Query<&mut EntityIdIndex>,
- Query<&mut LoadedBy>,
- )> = SystemState::new(ecs);
-
- let (mut query, mut entity_query) = system_state.get_mut(ecs);
- let Ok(mut entity_id_index) = query.get_mut(player_entity) else {
- warn!("our local player doesn't have EntityIdIndex");
- continue;
- };
-
- for &id in &p.entity_ids {
- let Some(entity) = entity_id_index.remove(id) else {
- debug!(
- "Tried to remove entity with id {id} but it wasn't in the EntityIdIndex"
- );
- continue;
- };
- let Ok(mut loaded_by) = entity_query.get_mut(entity) else {
- warn!(
- "tried to despawn entity {id} but it doesn't have a LoadedBy component",
- );
- continue;
- };
-
- // the [`remove_despawned_entities_from_indexes`] system will despawn the entity
- // if it's not loaded by anything anymore
-
- // also we can't just ecs.despawn because if we're in a swarm then the entity
- // might still be loaded by another client
-
- loaded_by.remove(&player_entity);
- }
- }
- 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);
-
- 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<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::DisguisedChat(p) => {
- debug!("Got disguised 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::Disguised(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 InstanceHolder>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
-
- let world = local_player.instance.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 InstanceHolder>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
-
- let world = local_player.instance.write();
-
- for state in &p.states {
- world
- .chunks
- .set_block_state(&(p.section_pos + state.pos), state.state);
- }
- }
- ClientboundGamePacket::GameEvent(p) => {
- use azalea_protocol::packets::game::c_game_event::EventType;
-
- debug!("Got game event packet {p:?}");
-
- #[allow(clippy::single_match)]
- match p.event {
- EventType::ChangeGameMode => {
- let mut system_state: SystemState<Query<&mut LocalGameMode>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let mut local_game_mode = query.get_mut(player_entity).unwrap();
- if let Some(new_game_mode) = GameMode::from_id(p.param as u8) {
- local_game_mode.current = new_game_mode;
- }
- }
- _ => {}
- }
- }
- 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::ContainerSetContent(p) => {
- debug!("Got container set content packet {p:?}");
-
- let mut system_state: SystemState<(
- Query<&mut Inventory>,
- EventWriter<SetContainerContentEvent>,
- )> = SystemState::new(ecs);
- let (mut query, mut events) = system_state.get_mut(ecs);
- let mut inventory = query.get_mut(player_entity).unwrap();
-
- // container id 0 is always the player's inventory
- if p.container_id == 0 {
- // this is just so it has the same type as the `else` block
- for (i, slot) in p.items.iter().enumerate() {
- if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) {
- *slot_mut = slot.clone();
- }
- }
- } else {
- events.send(SetContainerContentEvent {
- entity: player_entity,
- slots: p.items.clone(),
- container_id: p.container_id,
- });
- }
- }
- ClientboundGamePacket::ContainerSetData(p) => {
- debug!("Got container set data packet {p:?}");
- // let mut system_state: SystemState<Query<&mut
- // InventoryComponent>> =
- // SystemState::new(ecs);
- // let mut query = system_state.get_mut(ecs);
- // let mut inventory =
- // query.get_mut(player_entity).unwrap();
-
- // TODO: handle ContainerSetData packet
- // this is used for various things like the furnace progress
- // bar
- // see https://wiki.vg/Protocol#Set_Container_Property
- }
- ClientboundGamePacket::ContainerSetSlot(p) => {
- debug!("Got container set slot packet {p:?}");
-
- let mut system_state: SystemState<Query<&mut Inventory>> = SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let mut inventory = query.get_mut(player_entity).unwrap();
-
- if p.container_id == -1 {
- // -1 means carried item
- inventory.carried = p.item_stack.clone();
- } else if p.container_id == -2 {
- if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
- *slot = p.item_stack.clone();
- }
- } else {
- let is_creative_mode_and_inventory_closed = false;
- // technically minecraft has slightly different behavior here if you're in
- // creative mode and have your inventory open
- if p.container_id == 0
- && azalea_inventory::Player::is_hotbar_slot(p.slot.into())
- {
- // minecraft also sets a "pop time" here which is used for an animation
- // but that's not really necessary
- if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
- *slot = p.item_stack.clone();
- }
- } else if p.container_id == inventory.id
- && (p.container_id != 0 || !is_creative_mode_and_inventory_closed)
- {
- // var2.containerMenu.setItem(var4, var1.getStateId(), var3);
- if let Some(slot) = inventory.menu_mut().slot_mut(p.slot.into()) {
- *slot = p.item_stack.clone();
- inventory.state_id = p.state_id;
- }
- }
- }
- }
- ClientboundGamePacket::ContainerClose(_p) => {
- // there's p.container_id but minecraft doesn't actually check it
- let mut system_state: SystemState<EventWriter<ClientSideCloseContainerEvent>> =
- SystemState::new(ecs);
- let mut client_side_close_container_events = system_state.get_mut(ecs);
- client_side_close_container_events.send(ClientSideCloseContainerEvent {
- entity: player_entity,
- });
- }
- ClientboundGamePacket::Cooldown(_) => {}
- ClientboundGamePacket::CustomChatCompletions(_) => {}
- ClientboundGamePacket::DeleteChat(_) => {}
- ClientboundGamePacket::Explode(p) => {
- trace!("Got explode packet {p:?}");
- if let Some(knockback) = p.knockback {
- let mut system_state: SystemState<EventWriter<KnockbackEvent>> =
- SystemState::new(ecs);
- let mut knockback_events = system_state.get_mut(ecs);
-
- knockback_events.send(KnockbackEvent {
- entity: player_entity,
- knockback: KnockbackType::Set(knockback),
- });
-
- system_state.apply(ecs);
- }
- }
- ClientboundGamePacket::ForgetLevelChunk(p) => {
- debug!("Got forget level chunk packet {p:?}");
-
- let mut system_state: SystemState<Query<&mut InstanceHolder>> =
- SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
-
- let mut partial_instance = local_player.partial_instance.write();
-
- partial_instance.chunks.limited_set(&p.pos, None);
- }
- ClientboundGamePacket::HorseScreenOpen(_) => {}
- ClientboundGamePacket::MapItemData(_) => {}
- ClientboundGamePacket::MerchantOffers(_) => {}
- ClientboundGamePacket::MoveVehicle(_) => {}
- ClientboundGamePacket::OpenBook(_) => {}
- ClientboundGamePacket::OpenScreen(p) => {
- debug!("Got open screen packet {p:?}");
- let mut system_state: SystemState<EventWriter<MenuOpenedEvent>> =
- SystemState::new(ecs);
- let mut menu_opened_events = system_state.get_mut(ecs);
- menu_opened_events.send(MenuOpenedEvent {
- entity: player_entity,
- window_id: p.container_id,
- menu_type: p.menu_type,
- title: p.title.to_owned(),
- });
- }
- ClientboundGamePacket::OpenSignEditor(_) => {}
- ClientboundGamePacket::Ping(p) => {
- debug!("Got ping packet {p:?}");
-
- let mut system_state: SystemState<EventWriter<SendPacketEvent>> =
- SystemState::new(ecs);
- let mut send_packet_events = system_state.get_mut(ecs);
-
- send_packet_events.send(SendPacketEvent::new(
- player_entity,
- ServerboundPong { id: p.id },
- ));
- }
- 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::ResourcePackPush(p) => {
- debug!("Got resource pack packet {p:?}");
-
- let mut system_state: SystemState<EventWriter<ResourcePackEvent>> =
- SystemState::new(ecs);
- let mut resource_pack_events = system_state.get_mut(ecs);
-
- resource_pack_events.send(ResourcePackEvent {
- entity: player_entity,
- id: p.id,
- url: p.url.to_owned(),
- hash: p.hash.to_owned(),
- required: p.required,
- prompt: p.prompt.to_owned(),
- });
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::ResourcePackPop(_) => {}
- ClientboundGamePacket::Respawn(p) => {
- debug!("Got respawn packet {p:?}");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(
- &mut InstanceHolder,
- &GameProfileComponent,
- &ClientInformation,
- )>,
- EventWriter<InstanceLoadedEvent>,
- ResMut<InstanceContainer>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query, mut instance_loaded_events, mut instance_container) =
- system_state.get_mut(ecs);
- let (mut instance_holder, game_profile, client_information) =
- query.get_mut(player_entity).unwrap();
-
- {
- let new_instance_name = p.common.dimension.clone();
-
- let Some((_dimension_type, dimension_data)) = p
- .common
- .dimension_type(&instance_holder.instance.read().registries)
- else {
- continue;
- };
-
- // add this world to the instance_container (or don't if it's already
- // there)
- let weak_instance = instance_container.insert(
- new_instance_name.clone(),
- dimension_data.height,
- dimension_data.min_y,
- &instance_holder.instance.read().registries,
- );
- instance_loaded_events.send(InstanceLoadedEvent {
- entity: player_entity,
- name: new_instance_name.clone(),
- instance: Arc::downgrade(&weak_instance),
- });
-
- // set the partial_world to an empty world
- // (when we add chunks or entities those will be in the
- // instance_container)
-
- *instance_holder.partial_instance.write() = PartialInstance::new(
- azalea_world::chunk_storage::calculate_chunk_storage_range(
- client_information.view_distance.into(),
- ),
- Some(player_entity),
- );
- instance_holder.instance = weak_instance;
-
- // this resets a bunch of our components like physics and stuff
- let entity_bundle = EntityBundle::new(
- game_profile.uuid,
- Vec3::default(),
- azalea_registry::EntityKind::Player,
- new_instance_name,
- );
- // update the local gamemode and metadata things
- commands.entity(player_entity).insert((
- LocalGameMode {
- current: p.common.game_type,
- previous: p.common.previous_game_type.into(),
- },
- entity_bundle,
- ));
- }
-
- // Remove the Dead marker component from the player.
- commands.entity(player_entity).remove::<Dead>();
-
- system_state.apply(ecs);
- }
-
- ClientboundGamePacket::StartConfiguration(_p) => {
- let mut system_state: SystemState<(Commands, EventWriter<SendPacketEvent>)> =
- SystemState::new(ecs);
- let (mut commands, mut packet_events) = system_state.get_mut(ecs);
-
- packet_events.send(SendPacketEvent::new(
- player_entity,
- ServerboundConfigurationAcknowledged {},
- ));
-
- commands
- .entity(player_entity)
- .insert(crate::client::InConfigState)
- .remove::<crate::JoinedClientBundle>();
-
- system_state.apply(ecs);
- }
-
- ClientboundGamePacket::EntityPositionSync(p) => {
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &InstanceHolder)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
-
- let Some(entity) = entity_id_index.get(p.id) else {
- debug!("Got teleport entity packet for unknown entity id {}", p.id);
- continue;
- };
-
- let new_position = p.values.pos;
- let new_on_ground = p.on_ground;
- let new_look_direction = p.values.look_direction;
-
- commands.entity(entity).queue(RelativeEntityUpdate {
- partial_world: instance_holder.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- let is_local_entity = entity_mut.get::<LocalEntity>().is_some();
- let mut physics = entity_mut.get_mut::<Physics>().unwrap();
-
- physics.vec_delta_codec.set_base(new_position);
-
- if is_local_entity {
- debug!("Ignoring entity position sync packet for local player");
- return;
- }
-
- physics.set_on_ground(new_on_ground);
-
- let mut last_sent_position =
- entity_mut.get_mut::<LastSentPosition>().unwrap();
- **last_sent_position = new_position;
- let mut position = entity_mut.get_mut::<Position>().unwrap();
- **position = new_position;
-
- let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
- *look_direction = new_look_direction;
- }),
- });
-
- 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::ClearTitles(_) => {}
- ClientboundGamePacket::SoundEntity(_) => {}
- ClientboundGamePacket::StopSound(_) => {}
- ClientboundGamePacket::TabList(_) => {}
- ClientboundGamePacket::TagQuery(_) => {}
- ClientboundGamePacket::TakeItemEntity(_) => {}
- ClientboundGamePacket::BundleDelimiter(_) => {}
- ClientboundGamePacket::DamageEvent(_) => {}
- ClientboundGamePacket::HurtAnimation(_) => {}
-
- ClientboundGamePacket::TickingState(_) => {}
- ClientboundGamePacket::TickingStep(_) => {}
-
- ClientboundGamePacket::ResetScore(_) => {}
- ClientboundGamePacket::CookieRequest(_) => {}
- ClientboundGamePacket::DebugSample(_) => {}
- ClientboundGamePacket::PongResponse(_) => {}
- ClientboundGamePacket::StoreCookie(_) => {}
- ClientboundGamePacket::Transfer(_) => {}
- ClientboundGamePacket::MoveMinecartAlongTrack(_) => {}
- ClientboundGamePacket::SetHeldSlot(_) => {}
- ClientboundGamePacket::SetPlayerInventory(_) => {}
- ClientboundGamePacket::ProjectilePower(_) => {}
- ClientboundGamePacket::CustomReportDetails(_) => {}
- ClientboundGamePacket::ServerLinks(_) => {}
- ClientboundGamePacket::PlayerRotation(_) => {}
- ClientboundGamePacket::RecipeBookAdd(_) => {}
- ClientboundGamePacket::RecipeBookRemove(_) => {}
- ClientboundGamePacket::RecipeBookSettings(_) => {}
- }
- }
-}
-
-/// An event for sending a packet to the server while we're in the `game` state.
-#[derive(Event)]
-pub struct SendPacketEvent {
- pub sent_by: Entity,
- pub packet: ServerboundGamePacket,
-}
-impl SendPacketEvent {
- pub fn new(sent_by: Entity, packet: impl Packet<ServerboundGamePacket>) -> Self {
- let packet = packet.into_variant();
- Self { sent_by, packet }
- }
-}
-
-pub fn handle_send_packet_event(
- mut send_packet_events: EventReader<SendPacketEvent>,
- mut query: Query<&mut RawConnection>,
-) {
- for event in send_packet_events.read() {
- if let Ok(raw_connection) = query.get_mut(event.sent_by) {
- // debug!("Sending packet: {:?}", event.packet);
- if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
- error!("Failed to send packet: {e}");
- }
- }
- }
-}
diff --git a/azalea-client/src/packet_handling/login.rs b/azalea-client/src/packet_handling/login.rs
deleted file mode 100644
index 8cf45afc..00000000
--- a/azalea-client/src/packet_handling/login.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-// login packets aren't actually handled here because compression/encryption
-// would make packet handling a lot messier
-
-use std::{collections::HashSet, sync::Arc};
-
-use azalea_protocol::packets::{
- Packet,
- login::{
- ClientboundLoginPacket, ServerboundLoginPacket,
- s_custom_query_answer::ServerboundCustomQueryAnswer,
- },
-};
-use bevy_ecs::{prelude::*, system::SystemState};
-use derive_more::{Deref, DerefMut};
-use tokio::sync::mpsc;
-use tracing::error;
-
-// this struct is defined here anyways though so it's consistent with the other
-// ones
-
-/// An event that's sent when we receive a login packet from the server. Note
-/// that if you want to handle this in a system, you must add
-/// `.before(azalea::packet_handling::login::process_packet_events)` to it
-/// because that system clears the events.
-#[derive(Event, Debug, Clone)]
-pub struct LoginPacketEvent {
- /// The client entity that received the packet.
- pub entity: Entity,
- /// The packet that was actually received.
- pub packet: Arc<ClientboundLoginPacket>,
-}
-
-/// Event for sending a login packet to the server.
-#[derive(Event)]
-pub struct SendLoginPacketEvent {
- pub entity: Entity,
- pub packet: ServerboundLoginPacket,
-}
-impl SendLoginPacketEvent {
- pub fn new(entity: Entity, packet: impl Packet<ServerboundLoginPacket>) -> Self {
- let packet = packet.into_variant();
- Self { entity, packet }
- }
-}
-
-#[derive(Component)]
-pub struct LoginSendPacketQueue {
- pub tx: mpsc::UnboundedSender<ServerboundLoginPacket>,
-}
-
-/// A marker component for local players that are currently in the
-/// `login` state.
-#[derive(Component, Clone, Debug)]
-pub struct InLoginState;
-
-pub fn handle_send_packet_event(
- mut send_packet_events: EventReader<SendLoginPacketEvent>,
- mut query: Query<&mut LoginSendPacketQueue>,
-) {
- for event in send_packet_events.read() {
- if let Ok(queue) = query.get_mut(event.entity) {
- let _ = queue.tx.send(event.packet.clone());
- } else {
- error!("Sent SendPacketEvent for entity that doesn't have a LoginSendPacketQueue");
- }
- }
-}
-
-/// Plugins can add to this set if they want to handle a custom query packet
-/// themselves. This component removed after the login state ends.
-#[derive(Component, Default, Debug, Deref, DerefMut)]
-pub struct IgnoreQueryIds(HashSet<u32>);
-
-pub fn process_packet_events(ecs: &mut World) {
- let mut events_owned = Vec::new();
- let mut system_state: SystemState<ResMut<Events<LoginPacketEvent>>> = SystemState::new(ecs);
- let mut events = system_state.get_mut(ecs);
- for LoginPacketEvent {
- entity: player_entity,
- packet,
- } in events.drain()
- {
- // we do this so `ecs` isn't borrowed for the whole loop
- events_owned.push((player_entity, packet));
- }
- for (player_entity, packet) in events_owned {
- #[allow(clippy::single_match)]
- match packet.as_ref() {
- ClientboundLoginPacket::CustomQuery(p) => {
- let mut system_state: SystemState<(
- EventWriter<SendLoginPacketEvent>,
- Query<&IgnoreQueryIds>,
- )> = SystemState::new(ecs);
- let (mut send_packet_events, query) = system_state.get_mut(ecs);
-
- let ignore_query_ids = query.get(player_entity).ok().map(|x| x.0.clone());
- if let Some(ignore_query_ids) = ignore_query_ids {
- if ignore_query_ids.contains(&p.transaction_id) {
- continue;
- }
- }
-
- send_packet_events.send(SendLoginPacketEvent::new(
- player_entity,
- ServerboundCustomQueryAnswer {
- transaction_id: p.transaction_id,
- data: None,
- },
- ));
- }
- _ => {}
- }
- }
-}
diff --git a/azalea-client/src/packet_handling/mod.rs b/azalea-client/src/packet_handling/mod.rs
deleted file mode 100644
index 908f368e..00000000
--- a/azalea-client/src/packet_handling/mod.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use azalea_entity::{EntityUpdateSet, metadata::Health};
-use bevy_app::{App, First, Plugin, PreUpdate, Update};
-use bevy_ecs::prelude::*;
-
-use self::{
- game::{
- AddPlayerEvent, DeathEvent, InstanceLoadedEvent, KeepAliveEvent, RemovePlayerEvent,
- ResourcePackEvent, UpdatePlayerEvent,
- },
- login::{LoginPacketEvent, SendLoginPacketEvent},
-};
-use crate::{chat::ChatReceivedEvent, events::death_listener};
-
-pub mod configuration;
-pub mod game;
-pub mod login;
-
-pub struct PacketHandlerPlugin;
-
-pub fn death_event_on_0_health(
- query: Query<(Entity, &Health), Changed<Health>>,
- mut death_events: EventWriter<DeathEvent>,
-) {
- for (entity, health) in query.iter() {
- if **health == 0. {
- death_events.send(DeathEvent {
- entity,
- packet: None,
- });
- }
- }
-}
-
-impl Plugin for PacketHandlerPlugin {
- fn build(&self, app: &mut App) {
- app.add_systems(
- First,
- (game::send_packet_events, configuration::send_packet_events),
- )
- .add_systems(
- PreUpdate,
- (
- game::process_packet_events
- // we want to index and deindex right after
- .before(EntityUpdateSet::Deindex),
- configuration::process_packet_events,
- login::handle_send_packet_event,
- login::process_packet_events,
- ),
- )
- .add_systems(
- Update,
- (
- (
- configuration::handle_send_packet_event,
- game::handle_send_packet_event,
- )
- .chain(),
- death_event_on_0_health.before(death_listener),
- ),
- )
- // we do this instead of add_event so we can handle the events ourselves
- .init_resource::<Events<game::PacketEvent>>()
- .init_resource::<Events<configuration::ConfigurationEvent>>()
- .add_event::<game::SendPacketEvent>()
- .add_event::<configuration::SendConfigurationEvent>()
- .add_event::<AddPlayerEvent>()
- .add_event::<RemovePlayerEvent>()
- .add_event::<UpdatePlayerEvent>()
- .add_event::<ChatReceivedEvent>()
- .add_event::<DeathEvent>()
- .add_event::<KeepAliveEvent>()
- .add_event::<ResourcePackEvent>()
- .add_event::<InstanceLoadedEvent>()
- .add_event::<LoginPacketEvent>()
- .add_event::<SendLoginPacketEvent>();
- }
-}