aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/packet_handling.rs
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2023-09-21 11:16:29 -0500
committerGitHub <noreply@github.com>2023-09-21 11:16:29 -0500
commit7b3e2e4bf793466a351510c7fbbd08234e93bb0e (patch)
tree7177a919de9982d9e3c7f36a76d2025696f465b6 /azalea-client/src/packet_handling.rs
parent83cce236145cdab1872a472a70943b669a880965 (diff)
downloadazalea-drasl-7b3e2e4bf793466a351510c7fbbd08234e93bb0e.tar.xz
1.20.2 (#99)
* add configuration state * start updating to 23w31a * implement a bit more of 23w31a * chunk batching * start adding configuration state * ioasfhjgsd * almost works * configuration state mostly implemented * handle other packets in configuration state and fix keepalive * cleanup, fix warnings * 23w32a * fix some doctests * 23w33a * 23w35a * 1.20.2-pre2 * fix system conflicts * 1.20.2-pre4 * make tests compile * tests pass * 1.20.2-rc2 * 1.20.2 * Revert "1.20.2" This reverts commit dd152fd265332ead333c919e585ded6d609d7468. * didn't mean to commit that code --------- Co-authored-by: mat <git@matdoes.dev>
Diffstat (limited to 'azalea-client/src/packet_handling.rs')
-rw-r--r--azalea-client/src/packet_handling.rs1349
1 files changed, 0 insertions, 1349 deletions
diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs
deleted file mode 100644
index 6ac657d7..00000000
--- a/azalea-client/src/packet_handling.rs
+++ /dev/null
@@ -1,1349 +0,0 @@
-use std::{
- collections::HashSet,
- io::Cursor,
- sync::{Arc, Weak},
-};
-
-use azalea_buf::McBufWritable;
-use azalea_chat::FormattedText;
-use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
-use azalea_entity::{
- indexing::{EntityIdIndex, EntityUuidIndex},
- metadata::{apply_metadata, Health, PlayerMetadataBundle},
- Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LoadedBy, LookDirection,
- Physics, PlayerBundle, Position, RelativeEntityUpdate,
-};
-use azalea_nbt::NbtCompound;
-use azalea_protocol::{
- connect::{ReadConnection, WriteConnection},
- packets::game::{
- clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket,
- serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
- serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
- serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
- serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
- serverbound_pong_packet::ServerboundPongPacket, ClientboundGamePacket,
- ServerboundGamePacket,
- },
- read::ReadPacketError,
-};
-use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
-use bevy_app::{App, First, Plugin, PreUpdate, Update};
-use bevy_ecs::{
- component::Component,
- entity::Entity,
- event::{EventReader, EventWriter, Events},
- prelude::Event,
- query::Changed,
- schedule::IntoSystemConfigs,
- system::{Commands, Query, Res, ResMut, SystemState},
- world::World,
-};
-use log::{debug, error, trace, warn};
-use parking_lot::{Mutex, RwLock};
-use tokio::sync::mpsc;
-
-use crate::{
- chat::{ChatPacket, ChatReceivedEvent},
- client::{PlayerAbilities, TabList},
- disconnect::DisconnectEvent,
- events::death_listener,
- inventory::{
- ClientSideCloseContainerEvent, InventoryComponent, MenuOpenedEvent,
- SetContainerContentEvent,
- },
- local_player::{GameProfileComponent, Hunger, LocalGameMode, LocalPlayer},
- received_registries::ReceivedRegistries,
- 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 bevy_ecs::event::EventReader;
-///
-/// fn handle_packets(mut events: EventReader<PacketEvent>) {
-/// for PacketEvent {
-/// entity,
-/// packet,
-/// } in events.iter() {
-/// match packet {
-/// 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: ClientboundGamePacket,
-}
-
-pub struct PacketHandlerPlugin;
-
-impl Plugin for PacketHandlerPlugin {
- fn build(&self, app: &mut App) {
- app.add_systems(First, send_packet_events)
- .add_systems(
- PreUpdate,
- process_packet_events
- // we want to index and deindex right after
- .before(EntityUpdateSet::Deindex),
- )
- .add_systems(Update, death_event_on_0_health.before(death_listener))
- .init_resource::<Events<PacketEvent>>()
- .add_event::<AddPlayerEvent>()
- .add_event::<RemovePlayerEvent>()
- .add_event::<UpdatePlayerEvent>()
- .add_event::<ChatReceivedEvent>()
- .add_event::<DeathEvent>()
- .add_event::<KeepAliveEvent>()
- .add_event::<ResourcePackEvent>()
- .add_event::<InstanceLoadedEvent>();
- }
-}
-
-/// 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 [`ClientboundPlayerCombatKillPacket`] will
-/// be included.
-#[derive(Event, Debug, Clone)]
-pub struct DeathEvent {
- pub entity: Entity,
- pub packet: Option<ClientboundPlayerCombatKillPacket>,
-}
-
-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,
- });
- }
- }
-}
-
-/// 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,
- 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>>,
-}
-
-/// Something that receives packets from the server.
-#[derive(Event, Component, Clone)]
-pub struct PacketReceiver {
- pub packets: Arc<Mutex<Vec<ClientboundGamePacket>>>,
- pub run_schedule_sender: mpsc::UnboundedSender<()>,
-}
-
-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();
- }
- }
-}
-
-pub fn process_packet_events(ecs: &mut World) {
- 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,
- &mut EntityIdIndex,
- &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 local_player, mut entity_id_index, game_profile, client_information) =
- query.get_mut(player_entity).unwrap();
-
- {
- let received_registries = ReceivedRegistries(p.registry_holder.root);
-
- let dimension = &received_registries
- .dimension_type
- .value
- .iter()
- .find(|t| t.name == p.dimension_type)
- .unwrap_or_else(|| {
- panic!("No dimension_type with name {}", p.dimension_type)
- })
- .element;
-
- let new_instance_name = p.dimension.clone();
-
- // add this world to the instance_container (or don't if it's already
- // there)
- let instance = instance_container.insert(
- new_instance_name.clone(),
- dimension.height,
- dimension.min_y,
- );
- instance_loaded_events.send(InstanceLoadedEvent {
- entity: player_entity,
- name: new_instance_name.clone(),
- instance: Arc::downgrade(&instance),
- });
-
- // set the partial_world to an empty world
- // (when we add chunks or entities those will be in the
- // instance_container)
-
- *local_player.partial_instance.write() = PartialInstance::new(
- azalea_world::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 world
- Some(player_entity),
- );
- local_player.world = instance;
-
- let player_bundle = PlayerBundle {
- entity: EntityBundle::new(
- game_profile.uuid,
- Vec3::default(),
- azalea_registry::EntityKind::Player,
- new_instance_name,
- ),
- metadata: PlayerMetadataBundle::default(),
- };
- // insert our components into the ecs :)
- commands.entity(player_entity).insert((
- MinecraftEntityId(p.player_id),
- LocalGameMode {
- current: p.game_type,
- previous: p.previous_game_type.into(),
- },
- // this gets overwritten later by the SetHealth packet
- received_registries,
- player_bundle,
- ));
-
- // add our own player to our index
- entity_id_index.insert(MinecraftEntityId(p.player_id), player_entity);
- }
-
- // brand
- let mut brand_data = Vec::new();
- // they don't have to know :)
- "vanilla".write_into(&mut brand_data).unwrap();
- local_player.write_packet(
- ServerboundCustomPayloadPacket {
- identifier: ResourceLocation::new("brand"),
- data: brand_data.into(),
- }
- .get(),
- );
-
- // send the client information that we have set
- log::debug!(
- "Sending client information because login: {:?}",
- client_information
- );
- local_player.write_packet(client_information.clone().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);
- 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::SetCarriedItem(p) => {
- debug!("Got set carried 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,
- });
- // 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);
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<
- Query<(
- &mut LocalPlayer,
- &mut Physics,
- &mut LookDirection,
- &mut Position,
- &mut LastSentPosition,
- )>,
- > = SystemState::new(ecs);
- let mut query = system_state.get_mut(ecs);
- let Ok((
- local_player,
- mut physics,
- mut direction,
- 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 += direction.x_rot;
- }
- if p.relative_arguments.y_rot {
- y_rot += direction.y_rot;
- }
-
- physics.delta = Vec3 {
- x: delta_x,
- y: delta_y,
- z: delta_z,
- };
- // we call a function instead of setting the fields ourself since the
- // function makes sure the rotations stay in their
- // ranges
- (direction.y_rot, direction.x_rot) = (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,
- };
-
- if new_pos != **position {
- **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);
-
- #[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 = 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
- );
- }
- }
-
- *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 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_instance.write();
-
- partial_world.chunks.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 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_instance
- .read()
- .chunks
- .limited_get(&pos)
- .is_some();
-
- let mut world = local_player.world.write();
- let mut partial_world = local_player.partial_instance.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;
- }
- }
-
- let heightmaps = p
- .chunk_data
- .heightmaps
- .as_compound()
- .and_then(|c| c.get(""))
- .and_then(|c| c.as_compound());
- // necessary to make the unwrap_or work
- let empty_nbt_compound = NbtCompound::default();
- let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound);
-
- if let Err(e) = partial_world.chunks.replace_with_packet_data(
- &pos,
- &mut Cursor::new(&p.chunk_data.data),
- heightmaps,
- &mut world.chunks,
- ) {
- error!("Couldn't set chunk data: {}", e);
- }
- }
- ClientboundGamePacket::AddEntity(p) => {
- debug!("Got add entity packet {p:?}");
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(&mut EntityIdIndex, Option<&InstanceName>)>,
- Res<InstanceContainer>,
- ResMut<EntityUuidIndex>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query, instance_container, mut entity_uuid_index) =
- system_state.get_mut(ecs);
- let (mut entity_id_index, instance_name) = query.get_mut(player_entity).unwrap();
-
- if let Some(instance_name) = instance_name {
- let bundle = p.as_entity_bundle((**instance_name).clone());
- let mut spawned = commands.spawn((
- MinecraftEntityId(p.id),
- LoadedBy(HashSet::from([player_entity])),
- bundle,
- ));
- entity_id_index.insert(MinecraftEntityId(p.id), spawned.id());
-
- {
- // add it to the indexes immediately so if there's a packet that references
- // it immediately after it still works
- let instance = instance_container.get(instance_name).unwrap();
- instance
- .write()
- .entity_by_id
- .insert(MinecraftEntityId(p.id), spawned.id());
- entity_uuid_index.insert(p.uuid, spawned.id());
- }
-
- // the bundle doesn't include the default entity metadata so we add that
- // separately
- p.apply_metadata(&mut spawned);
- } 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);
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &LocalPlayer)>,
- Query<&EntityKind>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs);
- let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
-
- let entity = entity_id_index.get(&MinecraftEntityId(p.id));
-
- let Some(entity) = entity else {
- warn!("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();
-
- // we use RelativeEntityUpdate because it makes sure changes aren't made
- // multiple times
- commands.entity(entity).add(RelativeEntityUpdate {
- partial_world: local_player.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_comands = commands.entity(entity_id);
- if let Err(e) = apply_metadata(
- &mut entity_comands,
- *entity_kind,
- (*p.packed_items).clone(),
- ) {
- warn!("{e}");
- }
- });
- }),
- });
-
- 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 EntityIdIndex, &TabList, Option<&InstanceName>)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (mut entity_id_index, tab_list, world_name) =
- query.get_mut(player_entity).unwrap();
-
- if let Some(InstanceName(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,
- ));
- entity_id_index.insert(MinecraftEntityId(p.id), spawned.id());
-
- if let Some(player_info) = tab_list.get(&p.uuid) {
- spawned.insert(GameProfileComponent(player_info.profile.clone()));
- }
- } else {
- warn!("got add player packet but we haven't gotten a login packet yet");
- }
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::InitializeBorder(p) => {
- debug!("Got initialize border packet {:?}", p);
- }
- ClientboundGamePacket::SetTime(_p) => {
- // debug!("Got set time packet {:?}", p);
- }
- ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
- debug!("Got set default spawn position packet {:?}", p);
- }
- ClientboundGamePacket::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, &LocalPlayer)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
-
- let entity = entity_id_index.get(&MinecraftEntityId(p.id));
-
- if let Some(entity) = entity {
- let new_pos = p.position;
- commands.entity(entity).add(RelativeEntityUpdate {
- partial_world: local_player.partial_instance.clone(),
- update: Box::new(move |entity| {
- let mut position = entity.get_mut::<Position>().unwrap();
- if new_pos != **position {
- **position = new_pos;
- }
- }),
- });
- } else {
- warn!("Got teleport entity packet for unknown entity id {}", p.id);
- }
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::UpdateAdvancements(p) => {
- debug!("Got update advancements packet {:?}", p);
- }
- ClientboundGamePacket::RotateHead(_p) => {
- // debug!("Got rotate head packet {:?}", p);
- }
- ClientboundGamePacket::MoveEntityPos(p) => {
- let mut system_state: SystemState<(
- Commands,
- Query<(&EntityIdIndex, &LocalPlayer)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
-
- let entity = entity_id_index.get(&MinecraftEntityId(p.entity_id));
-
- if let Some(entity) = entity {
- let delta = p.delta.clone();
- commands.entity(entity).add(RelativeEntityUpdate {
- partial_world: local_player.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- let mut position = entity_mut.get_mut::<Position>().unwrap();
- let new_pos = position.with_delta(&delta);
- if new_pos != **position {
- **position = new_pos;
- }
- }),
- });
- } 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<(&EntityIdIndex, &LocalPlayer)>,
- )> = SystemState::new(ecs);
- let (mut commands, mut query) = system_state.get_mut(ecs);
- let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
-
- let entity = entity_id_index.get(&MinecraftEntityId(p.entity_id));
-
- if let Some(entity) = entity {
- let delta = p.delta.clone();
- commands.entity(entity).add(RelativeEntityUpdate {
- partial_world: local_player.partial_instance.clone(),
- update: Box::new(move |entity_mut| {
- let mut position = entity_mut.get_mut::<Position>().unwrap();
- let new_pos = position.with_delta(&delta);
- if new_pos != **position {
- **position = new_pos;
- }
- }),
- });
- } else {
- warn!(
- "Got move entity pos rot packet for unknown entity id {}",
- p.entity_id
- );
- }
-
- system_state.apply(ecs);
- }
-
- ClientboundGamePacket::MoveEntityRot(_p) => {
- // debug!("Got move entity rot packet {:?}", p);
- }
- ClientboundGamePacket::KeepAlive(p) => {
- debug!("Got keep alive packet {p:?} for {player_entity:?}");
-
- let mut system_state: SystemState<(
- Query<&mut LocalPlayer>,
- EventWriter<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 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);
-
- 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(&MinecraftEntityId(id)) else {
- warn!("There is no entity with id {id:?}");
- 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;
- };
- 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::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), state.state);
- }
- }
- ClientboundGamePacket::GameEvent(p) => {
- use azalea_protocol::packets::game::clientbound_game_event_packet::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 InventoryComponent>,
- 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 as u8,
- });
- }
- }
- 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 InventoryComponent>> =
- 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 as i8)
- && (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(_) => {}
- ClientboundGamePacket::ForgetLevelChunk(_) => {}
- 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,
- })
- }
- ClientboundGamePacket::OpenSignEditor(_) => {}
- ClientboundGamePacket::Ping(p) => {
- trace!("Got ping 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();
- local_player.write_packet(ServerboundPongPacket { id: p.id }.get());
- }
- 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(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,
- url: p.url,
- hash: p.hash,
- required: p.required,
- prompt: p.prompt,
- });
-
- system_state.apply(ecs);
- }
- ClientboundGamePacket::Respawn(p) => {
- debug!("Got respawn packet {:?}", p);
-
- #[allow(clippy::type_complexity)]
- let mut system_state: SystemState<(
- Commands,
- Query<(
- &mut LocalPlayer,
- &GameProfileComponent,
- &ClientInformation,
- &ReceivedRegistries,
- )>,
- 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 local_player, game_profile, client_information, received_registries) =
- query.get_mut(player_entity).unwrap();
-
- {
- let dimension = &received_registries
- .dimension_type
- .value
- .iter()
- .find(|t| t.name == p.dimension_type)
- .unwrap_or_else(|| {
- panic!("No dimension_type with name {}", p.dimension_type)
- })
- .element;
-
- let new_instance_name = p.dimension.clone();
-
- // add this world to the instance_container (or don't if it's already
- // there)
- let instance = instance_container.insert(
- new_instance_name.clone(),
- dimension.height,
- dimension.min_y,
- );
- instance_loaded_events.send(InstanceLoadedEvent {
- entity: player_entity,
- name: new_instance_name.clone(),
- instance: Arc::downgrade(&instance),
- });
-
- // set the partial_world to an empty world
- // (when we add chunks or entities those will be in the
- // instance_container)
- *local_player.partial_instance.write() = PartialInstance::new(
- azalea_world::calculate_chunk_storage_range(
- client_information.view_distance.into(),
- ),
- Some(player_entity),
- );
- local_player.world = instance;
-
- // this resets a bunch of our components like physics and stuff
- let player_bundle = PlayerBundle {
- entity: EntityBundle::new(
- game_profile.uuid,
- Vec3::default(),
- azalea_registry::EntityKind::Player,
- new_instance_name,
- ),
- metadata: PlayerMetadataBundle::default(),
- };
- // update the local gamemode and metadata things
- commands.entity(player_entity).insert((
- LocalGameMode {
- current: p.game_type,
- previous: p.previous_game_type.into(),
- },
- player_bundle,
- ));
- }
-
- // 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::ClearTitles(_) => {}
- ClientboundGamePacket::SoundEntity(_) => {}
- ClientboundGamePacket::StopSound(_) => {}
- ClientboundGamePacket::TabList(_) => {}
- ClientboundGamePacket::TagQuery(_) => {}
- ClientboundGamePacket::TakeItemEntity(_) => {}
- ClientboundGamePacket::DisguisedChat(_) => {}
- ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
- ClientboundGamePacket::Bundle(_) => {}
- ClientboundGamePacket::DamageEvent(_) => {}
- ClientboundGamePacket::HurtAnimation(_) => {}
- }
- }
-}
-
-impl PacketReceiver {
- /// Loop that reads from the connection and adds the packets to the queue +
- /// runs the schedule.
- pub async fn read_task(self, mut read_conn: ReadConnection<ClientboundGamePacket>) {
- loop {
- match read_conn.read().await {
- Ok(packet) => {
- self.packets.lock().push(packet);
- // tell the client to run all the systems
- self.run_schedule_sender.send(()).unwrap();
- }
- Err(error) => {
- if !matches!(*error, ReadPacketError::ConnectionClosed) {
- error!("Error reading packet from Client: {error:?}");
- }
- break;
- }
- }
- }
- }
-
- /// Consume the [`ServerboundGamePacket`] queue and actually write the
- /// packets to the server. It's like this so writing packets doesn't need to
- /// be awaited.
- pub async fn write_task(
- self,
- mut write_conn: WriteConnection<ServerboundGamePacket>,
- mut write_receiver: mpsc::UnboundedReceiver<ServerboundGamePacket>,
- ) {
- while let Some(packet) = write_receiver.recv().await {
- if let Err(err) = write_conn.write(packet).await {
- error!("Disconnecting because we couldn't write a packet: {err}.");
- break;
- };
- }
- // receiver is automatically closed when it's dropped
- }
-}