azalea_client/packet_handling/
game.rs

1use std::{
2    collections::HashSet,
3    io::Cursor,
4    ops::Add,
5    sync::{Arc, Weak},
6};
7
8use azalea_chat::FormattedText;
9use azalea_core::{
10    game_type::GameMode,
11    math,
12    position::{ChunkPos, Vec3},
13    resource_location::ResourceLocation,
14};
15use azalea_entity::{
16    indexing::{EntityIdIndex, EntityUuidIndex},
17    metadata::{apply_metadata, Health},
18    Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection,
19    Physics, Position, RelativeEntityUpdate,
20};
21use azalea_protocol::{
22    packets::{
23        game::{
24            c_player_combat_kill::ClientboundPlayerCombatKill,
25            s_accept_teleportation::ServerboundAcceptTeleportation,
26            s_configuration_acknowledged::ServerboundConfigurationAcknowledged,
27            s_keep_alive::ServerboundKeepAlive, s_move_player_pos_rot::ServerboundMovePlayerPosRot,
28            s_pong::ServerboundPong, ClientboundGamePacket, ServerboundGamePacket,
29        },
30        Packet,
31    },
32    read::deserialize_packet,
33};
34use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
35use bevy_ecs::{prelude::*, system::SystemState};
36use parking_lot::RwLock;
37use tracing::{debug, error, trace, warn};
38use uuid::Uuid;
39
40use crate::{
41    chat::{ChatPacket, ChatReceivedEvent},
42    chunks,
43    disconnect::DisconnectEvent,
44    inventory::{
45        ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
46    },
47    local_player::{
48        GameProfileComponent, Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList,
49    },
50    movement::{KnockbackEvent, KnockbackType},
51    raw_connection::RawConnection,
52    ClientInformation, PlayerInfo,
53};
54
55/// An event that's sent when we receive a packet.
56/// ```
57/// # use azalea_client::packet_handling::game::PacketEvent;
58/// # use azalea_protocol::packets::game::ClientboundGamePacket;
59/// # use bevy_ecs::event::EventReader;
60///
61/// fn handle_packets(mut events: EventReader<PacketEvent>) {
62///     for PacketEvent {
63///         entity,
64///         packet,
65///     } in events.read() {
66///         match packet.as_ref() {
67///             ClientboundGamePacket::LevelParticles(p) => {
68///                 // ...
69///             }
70///             _ => {}
71///         }
72///     }
73/// }
74/// ```
75#[derive(Event, Debug, Clone)]
76pub struct PacketEvent {
77    /// The client entity that received the packet.
78    pub entity: Entity,
79    /// The packet that was actually received.
80    pub packet: Arc<ClientboundGamePacket>,
81}
82
83/// A player joined the game (or more specifically, was added to the tab
84/// list of a local player).
85#[derive(Event, Debug, Clone)]
86pub struct AddPlayerEvent {
87    /// The local player entity that received this event.
88    pub entity: Entity,
89    pub info: PlayerInfo,
90}
91/// A player left the game (or maybe is still in the game and was just
92/// removed from the tab list of a local player).
93#[derive(Event, Debug, Clone)]
94pub struct RemovePlayerEvent {
95    /// The local player entity that received this event.
96    pub entity: Entity,
97    pub info: PlayerInfo,
98}
99/// A player was updated in the tab list of a local player (gamemode, display
100/// name, or latency changed).
101#[derive(Event, Debug, Clone)]
102pub struct UpdatePlayerEvent {
103    /// The local player entity that received this event.
104    pub entity: Entity,
105    pub info: PlayerInfo,
106}
107
108/// Event for when an entity dies. dies. If it's a local player and there's a
109/// reason in the death screen, the [`ClientboundPlayerCombatKill`] will
110/// be included.
111#[derive(Event, Debug, Clone)]
112pub struct DeathEvent {
113    pub entity: Entity,
114    pub packet: Option<ClientboundPlayerCombatKill>,
115}
116
117/// A KeepAlive packet is sent from the server to verify that the client is
118/// still connected.
119#[derive(Event, Debug, Clone)]
120pub struct KeepAliveEvent {
121    pub entity: Entity,
122    /// The ID of the keepalive. This is an arbitrary number, but vanilla
123    /// servers use the time to generate this.
124    pub id: u64,
125}
126
127#[derive(Event, Debug, Clone)]
128pub struct ResourcePackEvent {
129    pub entity: Entity,
130    /// The random ID for this request to download the resource pack. The packet
131    /// for replying to a resource pack push must contain the same ID.
132    pub id: Uuid,
133    pub url: String,
134    pub hash: String,
135    pub required: bool,
136    pub prompt: Option<FormattedText>,
137}
138
139/// An instance (aka world, dimension) was loaded by a client.
140///
141/// Since the instance is given to you as a weak reference, it won't be able to
142/// be `upgrade`d if all local players leave it.
143#[derive(Event, Debug, Clone)]
144pub struct InstanceLoadedEvent {
145    pub entity: Entity,
146    pub name: ResourceLocation,
147    pub instance: Weak<RwLock<Instance>>,
148}
149
150pub fn send_packet_events(
151    query: Query<(Entity, &RawConnection), With<LocalEntity>>,
152    mut packet_events: ResMut<Events<PacketEvent>>,
153) {
154    // we manually clear and send the events at the beginning of each update
155    // since otherwise it'd cause issues with events in process_packet_events
156    // running twice
157    packet_events.clear();
158    for (player_entity, raw_connection) in &query {
159        let packets_lock = raw_connection.incoming_packet_queue();
160        let mut packets = packets_lock.lock();
161        if !packets.is_empty() {
162            for raw_packet in packets.iter() {
163                let packet =
164                    match deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new(raw_packet))
165                    {
166                        Ok(packet) => packet,
167                        Err(err) => {
168                            error!("failed to read packet: {err:?}");
169                            debug!("packet bytes: {raw_packet:?}");
170                            continue;
171                        }
172                    };
173                packet_events.send(PacketEvent {
174                    entity: player_entity,
175                    packet: Arc::new(packet),
176                });
177            }
178            // clear the packets right after we read them
179            packets.clear();
180        }
181    }
182}
183
184pub fn process_packet_events(ecs: &mut World) {
185    let mut events_owned = Vec::<(Entity, Arc<ClientboundGamePacket>)>::new();
186    {
187        let mut system_state = SystemState::<EventReader<PacketEvent>>::new(ecs);
188        let mut events = system_state.get_mut(ecs);
189        for PacketEvent {
190            entity: player_entity,
191            packet,
192        } in events.read()
193        {
194            // we do this so `ecs` isn't borrowed for the whole loop
195            events_owned.push((*player_entity, packet.clone()));
196        }
197    }
198    for (player_entity, packet) in events_owned {
199        let packet_clone = packet.clone();
200        let packet_ref = packet_clone.as_ref();
201        match packet_ref {
202            ClientboundGamePacket::Login(p) => {
203                debug!("Got login packet");
204
205                #[allow(clippy::type_complexity)]
206                let mut system_state: SystemState<(
207                    Commands,
208                    Query<(
209                        &GameProfileComponent,
210                        &ClientInformation,
211                        Option<&mut InstanceName>,
212                        Option<&mut LoadedBy>,
213                        &mut EntityIdIndex,
214                        &mut InstanceHolder,
215                    )>,
216                    EventWriter<InstanceLoadedEvent>,
217                    ResMut<InstanceContainer>,
218                    ResMut<EntityUuidIndex>,
219                    EventWriter<SendPacketEvent>,
220                )> = SystemState::new(ecs);
221                let (
222                    mut commands,
223                    mut query,
224                    mut instance_loaded_events,
225                    mut instance_container,
226                    mut entity_uuid_index,
227                    mut send_packet_events,
228                ) = system_state.get_mut(ecs);
229                let (
230                    game_profile,
231                    client_information,
232                    instance_name,
233                    loaded_by,
234                    mut entity_id_index,
235                    mut instance_holder,
236                ) = query.get_mut(player_entity).unwrap();
237
238                {
239                    let new_instance_name = p.common.dimension.clone();
240
241                    if let Some(mut instance_name) = instance_name {
242                        *instance_name = instance_name.clone();
243                    } else {
244                        commands
245                            .entity(player_entity)
246                            .insert(InstanceName(new_instance_name.clone()));
247                    }
248
249                    let Some(dimension_type_element) =
250                        instance_holder.instance.read().registries.dimension_type()
251                    else {
252                        error!("Server didn't send dimension type registry, can't log in");
253                        continue;
254                    };
255
256                    let dimension_name = ResourceLocation::new(&p.common.dimension.to_string());
257
258                    let Some(dimension) = dimension_type_element.map.get(&dimension_name) else {
259                        error!("No dimension_type with name {dimension_name}");
260                        continue;
261                    };
262
263                    // add this world to the instance_container (or don't if it's already
264                    // there)
265                    let weak_instance = instance_container.insert(
266                        new_instance_name.clone(),
267                        dimension.height,
268                        dimension.min_y,
269                    );
270                    instance_loaded_events.send(InstanceLoadedEvent {
271                        entity: player_entity,
272                        name: new_instance_name.clone(),
273                        instance: Arc::downgrade(&weak_instance),
274                    });
275
276                    // set the partial_world to an empty world
277                    // (when we add chunks or entities those will be in the
278                    // instance_container)
279
280                    *instance_holder.partial_instance.write() = PartialInstance::new(
281                        azalea_world::chunk_storage::calculate_chunk_storage_range(
282                            client_information.view_distance.into(),
283                        ),
284                        // this argument makes it so other clients don't update this player entity
285                        // in a shared instance
286                        Some(player_entity),
287                    );
288                    {
289                        let map = instance_holder.instance.read().registries.map.clone();
290                        let new_registries = &mut weak_instance.write().registries;
291                        // add the registries from this instance to the weak instance
292                        for (registry_name, registry) in map {
293                            new_registries.map.insert(registry_name, registry);
294                        }
295                    }
296                    instance_holder.instance = weak_instance;
297
298                    let entity_bundle = EntityBundle::new(
299                        game_profile.uuid,
300                        Vec3::default(),
301                        azalea_registry::EntityKind::Player,
302                        new_instance_name,
303                    );
304                    let entity_id = p.player_id;
305                    // insert our components into the ecs :)
306                    commands.entity(player_entity).insert((
307                        entity_id,
308                        LocalGameMode {
309                            current: p.common.game_type,
310                            previous: p.common.previous_game_type.into(),
311                        },
312                        entity_bundle,
313                    ));
314
315                    azalea_entity::indexing::add_entity_to_indexes(
316                        entity_id,
317                        player_entity,
318                        Some(game_profile.uuid),
319                        &mut entity_id_index,
320                        &mut entity_uuid_index,
321                        &mut instance_holder.instance.write(),
322                    );
323
324                    // update or insert loaded_by
325                    if let Some(mut loaded_by) = loaded_by {
326                        loaded_by.insert(player_entity);
327                    } else {
328                        commands
329                            .entity(player_entity)
330                            .insert(LoadedBy(HashSet::from_iter(vec![player_entity])));
331                    }
332                }
333
334                // send the client information that we have set
335                debug!(
336                    "Sending client information because login: {:?}",
337                    client_information
338                );
339                send_packet_events.send(SendPacketEvent::new(player_entity,
340                    azalea_protocol::packets::game::s_client_information::ServerboundClientInformation { information: client_information.clone() },
341                ));
342
343                system_state.apply(ecs);
344            }
345            ClientboundGamePacket::SetChunkCacheRadius(p) => {
346                debug!("Got set chunk cache radius packet {p:?}");
347            }
348
349            ClientboundGamePacket::ChunkBatchStart(_p) => {
350                // the packet is empty, just a marker to tell us when the batch starts and ends
351                debug!("Got chunk batch start");
352                let mut system_state: SystemState<EventWriter<chunks::ChunkBatchStartEvent>> =
353                    SystemState::new(ecs);
354                let mut chunk_batch_start_events = system_state.get_mut(ecs);
355
356                chunk_batch_start_events.send(chunks::ChunkBatchStartEvent {
357                    entity: player_entity,
358                });
359            }
360            ClientboundGamePacket::ChunkBatchFinished(p) => {
361                debug!("Got chunk batch finished {p:?}");
362
363                let mut system_state: SystemState<EventWriter<chunks::ChunkBatchFinishedEvent>> =
364                    SystemState::new(ecs);
365                let mut chunk_batch_start_events = system_state.get_mut(ecs);
366
367                chunk_batch_start_events.send(chunks::ChunkBatchFinishedEvent {
368                    entity: player_entity,
369                    batch_size: p.batch_size,
370                });
371            }
372
373            ClientboundGamePacket::CustomPayload(p) => {
374                debug!("Got custom payload packet {p:?}");
375            }
376            ClientboundGamePacket::ChangeDifficulty(p) => {
377                debug!("Got difficulty packet {p:?}");
378            }
379            ClientboundGamePacket::Commands(_p) => {
380                debug!("Got declare commands packet");
381            }
382            ClientboundGamePacket::PlayerAbilities(p) => {
383                debug!("Got player abilities packet {p:?}");
384                let mut system_state: SystemState<Query<&mut PlayerAbilities>> =
385                    SystemState::new(ecs);
386                let mut query = system_state.get_mut(ecs);
387                let mut player_abilities = query.get_mut(player_entity).unwrap();
388
389                *player_abilities = PlayerAbilities::from(p);
390            }
391            ClientboundGamePacket::SetCursorItem(p) => {
392                debug!("Got set cursor item packet {p:?}");
393            }
394            ClientboundGamePacket::UpdateTags(_p) => {
395                debug!("Got update tags packet");
396            }
397            ClientboundGamePacket::Disconnect(p) => {
398                warn!("Got disconnect packet {p:?}");
399                let mut system_state: SystemState<EventWriter<DisconnectEvent>> =
400                    SystemState::new(ecs);
401                let mut disconnect_events = system_state.get_mut(ecs);
402                disconnect_events.send(DisconnectEvent {
403                    entity: player_entity,
404                    reason: Some(p.reason.clone()),
405                });
406            }
407            ClientboundGamePacket::UpdateRecipes(_p) => {
408                debug!("Got update recipes packet");
409            }
410            ClientboundGamePacket::EntityEvent(_p) => {
411                // debug!("Got entity event packet {p:?}");
412            }
413            ClientboundGamePacket::PlayerPosition(p) => {
414                debug!("Got player position packet {p:?}");
415
416                #[allow(clippy::type_complexity)]
417                let mut system_state: SystemState<(
418                    Query<(
419                        &mut Physics,
420                        &mut LookDirection,
421                        &mut Position,
422                        &mut LastSentPosition,
423                    )>,
424                    EventWriter<SendPacketEvent>,
425                )> = SystemState::new(ecs);
426                let (mut query, mut send_packet_events) = system_state.get_mut(ecs);
427                let Ok((mut physics, mut direction, mut position, mut last_sent_position)) =
428                    query.get_mut(player_entity)
429                else {
430                    continue;
431                };
432
433                **last_sent_position = **position;
434
435                fn apply_change<T: Add<Output = T>>(base: T, condition: bool, change: T) -> T {
436                    if condition {
437                        base + change
438                    } else {
439                        change
440                    }
441                }
442
443                let new_x = apply_change(position.x, p.relative.x, p.change.pos.x);
444                let new_y = apply_change(position.y, p.relative.y, p.change.pos.y);
445                let new_z = apply_change(position.z, p.relative.z, p.change.pos.z);
446
447                let new_y_rot = apply_change(
448                    direction.y_rot,
449                    p.relative.y_rot,
450                    p.change.look_direction.y_rot,
451                );
452                let new_x_rot = apply_change(
453                    direction.x_rot,
454                    p.relative.x_rot,
455                    p.change.look_direction.x_rot,
456                );
457
458                let mut new_delta_from_rotations = physics.velocity;
459                if p.relative.rotate_delta {
460                    let y_rot_delta = direction.y_rot - new_y_rot;
461                    let x_rot_delta = direction.x_rot - new_x_rot;
462                    new_delta_from_rotations = new_delta_from_rotations
463                        .x_rot(math::to_radians(x_rot_delta as f64) as f32)
464                        .y_rot(math::to_radians(y_rot_delta as f64) as f32);
465                }
466
467                let new_delta = Vec3::new(
468                    apply_change(
469                        new_delta_from_rotations.x,
470                        p.relative.delta_x,
471                        p.change.delta.x,
472                    ),
473                    apply_change(
474                        new_delta_from_rotations.y,
475                        p.relative.delta_y,
476                        p.change.delta.y,
477                    ),
478                    apply_change(
479                        new_delta_from_rotations.z,
480                        p.relative.delta_z,
481                        p.change.delta.z,
482                    ),
483                );
484
485                // apply the updates
486
487                physics.velocity = new_delta;
488
489                (direction.y_rot, direction.x_rot) = (new_y_rot, new_x_rot);
490
491                let new_pos = Vec3::new(new_x, new_y, new_z);
492                if new_pos != **position {
493                    **position = new_pos;
494                }
495
496                // old_pos is set to the current position when we're teleported
497                physics.set_old_pos(&position);
498
499                // send the relevant packets
500
501                send_packet_events.send(SendPacketEvent::new(
502                    player_entity,
503                    ServerboundAcceptTeleportation { id: p.id },
504                ));
505                send_packet_events.send(SendPacketEvent::new(
506                    player_entity,
507                    ServerboundMovePlayerPosRot {
508                        pos: new_pos,
509                        look_direction: LookDirection::new(new_y_rot, new_x_rot),
510                        // this is always false
511                        on_ground: false,
512                    },
513                ));
514            }
515            ClientboundGamePacket::PlayerInfoUpdate(p) => {
516                debug!("Got player info packet {p:?}");
517
518                #[allow(clippy::type_complexity)]
519                let mut system_state: SystemState<(
520                    Query<&mut TabList>,
521                    EventWriter<AddPlayerEvent>,
522                    EventWriter<UpdatePlayerEvent>,
523                    ResMut<TabList>,
524                )> = SystemState::new(ecs);
525                let (
526                    mut query,
527                    mut add_player_events,
528                    mut update_player_events,
529                    mut tab_list_resource,
530                ) = system_state.get_mut(ecs);
531                let mut tab_list = query.get_mut(player_entity).unwrap();
532
533                for updated_info in &p.entries {
534                    // add the new player maybe
535                    if p.actions.add_player {
536                        let info = PlayerInfo {
537                            profile: updated_info.profile.clone(),
538                            uuid: updated_info.profile.uuid,
539                            gamemode: updated_info.game_mode,
540                            latency: updated_info.latency,
541                            display_name: updated_info.display_name.clone(),
542                        };
543                        tab_list.insert(updated_info.profile.uuid, info.clone());
544                        add_player_events.send(AddPlayerEvent {
545                            entity: player_entity,
546                            info: info.clone(),
547                        });
548                    } else if let Some(info) = tab_list.get_mut(&updated_info.profile.uuid) {
549                        // `else if` because the block for add_player above
550                        // already sets all the fields
551                        if p.actions.update_game_mode {
552                            info.gamemode = updated_info.game_mode;
553                        }
554                        if p.actions.update_latency {
555                            info.latency = updated_info.latency;
556                        }
557                        if p.actions.update_display_name {
558                            info.display_name.clone_from(&updated_info.display_name);
559                        }
560                        update_player_events.send(UpdatePlayerEvent {
561                            entity: player_entity,
562                            info: info.clone(),
563                        });
564                    } else {
565                        let uuid = updated_info.profile.uuid;
566                        #[cfg(debug_assertions)]
567                        warn!("Ignoring PlayerInfoUpdate for unknown player {uuid}");
568                        #[cfg(not(debug_assertions))]
569                        debug!("Ignoring PlayerInfoUpdate for unknown player {uuid}");
570                    }
571                }
572
573                *tab_list_resource = tab_list.clone();
574            }
575            ClientboundGamePacket::PlayerInfoRemove(p) => {
576                let mut system_state: SystemState<(
577                    Query<&mut TabList>,
578                    EventWriter<RemovePlayerEvent>,
579                    ResMut<TabList>,
580                )> = SystemState::new(ecs);
581                let (mut query, mut remove_player_events, mut tab_list_resource) =
582                    system_state.get_mut(ecs);
583                let mut tab_list = query.get_mut(player_entity).unwrap();
584
585                for uuid in &p.profile_ids {
586                    if let Some(info) = tab_list.remove(uuid) {
587                        remove_player_events.send(RemovePlayerEvent {
588                            entity: player_entity,
589                            info,
590                        });
591                    }
592                    tab_list_resource.remove(uuid);
593                }
594            }
595            ClientboundGamePacket::SetChunkCacheCenter(p) => {
596                debug!("Got chunk cache center packet {p:?}");
597
598                let mut system_state: SystemState<Query<&mut InstanceHolder>> =
599                    SystemState::new(ecs);
600                let mut query = system_state.get_mut(ecs);
601                let instance_holder = query.get_mut(player_entity).unwrap();
602                let mut partial_world = instance_holder.partial_instance.write();
603
604                partial_world
605                    .chunks
606                    .update_view_center(ChunkPos::new(p.x, p.z));
607            }
608            ClientboundGamePacket::ChunksBiomes(_) => {}
609            ClientboundGamePacket::LightUpdate(_p) => {
610                // debug!("Got light update packet {p:?}");
611            }
612            ClientboundGamePacket::LevelChunkWithLight(p) => {
613                debug!("Got chunk with light packet {} {}", p.x, p.z);
614
615                let mut system_state: SystemState<EventWriter<chunks::ReceiveChunkEvent>> =
616                    SystemState::new(ecs);
617                let mut receive_chunk_events = system_state.get_mut(ecs);
618                receive_chunk_events.send(chunks::ReceiveChunkEvent {
619                    entity: player_entity,
620                    packet: p.clone(),
621                });
622            }
623            ClientboundGamePacket::AddEntity(p) => {
624                debug!("Got add entity packet {p:?}");
625
626                #[allow(clippy::type_complexity)]
627                let mut system_state: SystemState<(
628                    Commands,
629                    Query<(&mut EntityIdIndex, Option<&InstanceName>, Option<&TabList>)>,
630                    Query<&mut LoadedBy>,
631                    Query<Entity>,
632                    Res<InstanceContainer>,
633                    ResMut<EntityUuidIndex>,
634                )> = SystemState::new(ecs);
635                let (
636                    mut commands,
637                    mut query,
638                    mut loaded_by_query,
639                    entity_query,
640                    instance_container,
641                    mut entity_uuid_index,
642                ) = system_state.get_mut(ecs);
643                let (mut entity_id_index, instance_name, tab_list) =
644                    query.get_mut(player_entity).unwrap();
645
646                let entity_id = p.id;
647
648                let Some(instance_name) = instance_name else {
649                    warn!("got add player packet but we haven't gotten a login packet yet");
650                    continue;
651                };
652
653                // check if the entity already exists, and if it does then only add to LoadedBy
654                let instance = instance_container.get(instance_name).unwrap();
655                if let Some(&ecs_entity) = instance.read().entity_by_id.get(&entity_id) {
656                    // entity already exists
657                    let Ok(mut loaded_by) = loaded_by_query.get_mut(ecs_entity) else {
658                        // LoadedBy for this entity isn't in the ecs! figure out what went wrong
659                        // and print an error
660
661                        let entity_in_ecs = entity_query.get(ecs_entity).is_ok();
662
663                        if entity_in_ecs {
664                            error!("LoadedBy for entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id");
665                        } else {
666                            error!("Entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id");
667                        }
668                        continue;
669                    };
670                    loaded_by.insert(player_entity);
671
672                    // per-client id index
673                    entity_id_index.insert(entity_id, ecs_entity);
674
675                    debug!("added to LoadedBy of entity {ecs_entity:?} with id {entity_id:?}");
676                    continue;
677                };
678
679                // entity doesn't exist in the global index!
680
681                let bundle = p.as_entity_bundle((**instance_name).clone());
682                let mut spawned =
683                    commands.spawn((entity_id, LoadedBy(HashSet::from([player_entity])), bundle));
684                let ecs_entity: Entity = spawned.id();
685                debug!("spawned entity {ecs_entity:?} with id {entity_id:?}");
686
687                azalea_entity::indexing::add_entity_to_indexes(
688                    entity_id,
689                    ecs_entity,
690                    Some(p.uuid),
691                    &mut entity_id_index,
692                    &mut entity_uuid_index,
693                    &mut instance.write(),
694                );
695
696                // add the GameProfileComponent if the uuid is in the tab list
697                if let Some(tab_list) = tab_list {
698                    // (technically this makes it possible for non-player entities to have
699                    // GameProfileComponents but the server would have to be doing something
700                    // really weird)
701                    if let Some(player_info) = tab_list.get(&p.uuid) {
702                        spawned.insert(GameProfileComponent(player_info.profile.clone()));
703                    }
704                }
705
706                // the bundle doesn't include the default entity metadata so we add that
707                // separately
708                p.apply_metadata(&mut spawned);
709
710                system_state.apply(ecs);
711            }
712            ClientboundGamePacket::SetEntityData(p) => {
713                debug!("Got set entity data packet {p:?}");
714
715                #[allow(clippy::type_complexity)]
716                let mut system_state: SystemState<(
717                    Commands,
718                    Query<(&EntityIdIndex, &InstanceHolder)>,
719                    Query<&EntityKind>,
720                )> = SystemState::new(ecs);
721                let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs);
722                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
723
724                let entity = entity_id_index.get(p.id);
725
726                let Some(entity) = entity else {
727                    // some servers like hypixel trigger this a lot :(
728                    debug!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
729                    continue;
730                };
731                let entity_kind = *entity_kind_query.get(entity).unwrap();
732
733                let packed_items = p.packed_items.clone().to_vec();
734
735                // we use RelativeEntityUpdate because it makes sure changes aren't made
736                // multiple times
737                commands.entity(entity).queue(RelativeEntityUpdate {
738                    partial_world: instance_holder.partial_instance.clone(),
739                    update: Box::new(move |entity| {
740                        let entity_id = entity.id();
741                        entity.world_scope(|world| {
742                            let mut commands_system_state = SystemState::<Commands>::new(world);
743                            let mut commands = commands_system_state.get_mut(world);
744                            let mut entity_commands = commands.entity(entity_id);
745                            if let Err(e) =
746                                apply_metadata(&mut entity_commands, *entity_kind, packed_items)
747                            {
748                                warn!("{e}");
749                            }
750                            commands_system_state.apply(world);
751                        });
752                    }),
753                });
754
755                system_state.apply(ecs);
756            }
757            ClientboundGamePacket::UpdateAttributes(_p) => {
758                // debug!("Got update attributes packet {p:?}");
759            }
760            ClientboundGamePacket::SetEntityMotion(p) => {
761                // vanilla servers use this packet for knockback, but note that the Explode
762                // packet is also sometimes used by servers for knockback
763
764                let mut system_state: SystemState<(
765                    Commands,
766                    Query<(&EntityIdIndex, &InstanceHolder)>,
767                )> = SystemState::new(ecs);
768                let (mut commands, mut query) = system_state.get_mut(ecs);
769                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
770
771                let Some(entity) = entity_id_index.get(p.id) else {
772                    // note that this log (and some other ones like the one in RemoveEntities)
773                    // sometimes happens when killing mobs. it seems to be a vanilla bug, which is
774                    // why it's a debug log instead of a warning
775                    debug!(
776                        "Got set entity motion packet for unknown entity id {}",
777                        p.id
778                    );
779                    continue;
780                };
781
782                // this is to make sure the same entity velocity update doesn't get sent
783                // multiple times when in swarms
784
785                let knockback = KnockbackType::Set(Vec3 {
786                    x: p.xa as f64 / 8000.,
787                    y: p.ya as f64 / 8000.,
788                    z: p.za as f64 / 8000.,
789                });
790
791                commands.entity(entity).queue(RelativeEntityUpdate {
792                    partial_world: instance_holder.partial_instance.clone(),
793                    update: Box::new(move |entity_mut| {
794                        entity_mut.world_scope(|world| {
795                            world.send_event(KnockbackEvent { entity, knockback })
796                        });
797                    }),
798                });
799
800                system_state.apply(ecs);
801            }
802            ClientboundGamePacket::SetEntityLink(p) => {
803                debug!("Got set entity link packet {p:?}");
804            }
805            ClientboundGamePacket::InitializeBorder(p) => {
806                debug!("Got initialize border packet {p:?}");
807            }
808            ClientboundGamePacket::SetTime(_p) => {
809                // debug!("Got set time packet {p:?}");
810            }
811            ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
812                debug!("Got set default spawn position packet {p:?}");
813            }
814            ClientboundGamePacket::SetHealth(p) => {
815                debug!("Got set health packet {p:?}");
816
817                let mut system_state: SystemState<Query<(&mut Health, &mut Hunger)>> =
818                    SystemState::new(ecs);
819                let mut query = system_state.get_mut(ecs);
820                let (mut health, mut hunger) = query.get_mut(player_entity).unwrap();
821
822                **health = p.health;
823                (hunger.food, hunger.saturation) = (p.food, p.saturation);
824
825                // the `Dead` component is added by the `update_dead` system
826                // in azalea-world and then the `dead_event` system fires
827                // the Death event.
828            }
829            ClientboundGamePacket::SetExperience(p) => {
830                debug!("Got set experience packet {p:?}");
831            }
832            ClientboundGamePacket::TeleportEntity(p) => {
833                let mut system_state: SystemState<(
834                    Commands,
835                    Query<(&EntityIdIndex, &InstanceHolder)>,
836                )> = SystemState::new(ecs);
837                let (mut commands, mut query) = system_state.get_mut(ecs);
838                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
839
840                let Some(entity) = entity_id_index.get(p.id) else {
841                    warn!("Got teleport entity packet for unknown entity id {}", p.id);
842                    continue;
843                };
844
845                let new_pos = p.change.pos;
846                let new_look_direction = LookDirection {
847                    x_rot: (p.change.look_direction.x_rot as i32 * 360) as f32 / 256.,
848                    y_rot: (p.change.look_direction.y_rot as i32 * 360) as f32 / 256.,
849                };
850                commands.entity(entity).queue(RelativeEntityUpdate {
851                    partial_world: instance_holder.partial_instance.clone(),
852                    update: Box::new(move |entity| {
853                        let mut position = entity.get_mut::<Position>().unwrap();
854                        if new_pos != **position {
855                            **position = new_pos;
856                        }
857                        let position = *position;
858                        let mut look_direction = entity.get_mut::<LookDirection>().unwrap();
859                        if new_look_direction != *look_direction {
860                            *look_direction = new_look_direction;
861                        }
862                        // old_pos is set to the current position when we're teleported
863                        let mut physics = entity.get_mut::<Physics>().unwrap();
864                        physics.set_old_pos(&position);
865                    }),
866                });
867
868                system_state.apply(ecs);
869            }
870            ClientboundGamePacket::UpdateAdvancements(p) => {
871                debug!("Got update advancements packet {p:?}");
872            }
873            ClientboundGamePacket::RotateHead(_p) => {
874                // debug!("Got rotate head packet {p:?}");
875            }
876            ClientboundGamePacket::MoveEntityPos(p) => {
877                let mut system_state: SystemState<(
878                    Commands,
879                    Query<(&EntityIdIndex, &InstanceHolder)>,
880                )> = SystemState::new(ecs);
881                let (mut commands, mut query) = system_state.get_mut(ecs);
882                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
883
884                debug!("Got move entity pos packet {p:?}");
885
886                let Some(entity) = entity_id_index.get(p.entity_id) else {
887                    debug!(
888                        "Got move entity pos packet for unknown entity id {}",
889                        p.entity_id
890                    );
891                    continue;
892                };
893
894                let new_delta = p.delta.clone();
895                let new_on_ground = p.on_ground;
896                commands.entity(entity).queue(RelativeEntityUpdate {
897                    partial_world: instance_holder.partial_instance.clone(),
898                    update: Box::new(move |entity_mut| {
899                        let mut physics = entity_mut.get_mut::<Physics>().unwrap();
900                        let new_pos = physics.vec_delta_codec.decode(
901                            new_delta.xa as i64,
902                            new_delta.ya as i64,
903                            new_delta.za as i64,
904                        );
905                        physics.vec_delta_codec.set_base(new_pos);
906                        physics.set_on_ground(new_on_ground);
907
908                        let mut position = entity_mut.get_mut::<Position>().unwrap();
909                        if new_pos != **position {
910                            **position = new_pos;
911                        }
912                    }),
913                });
914
915                system_state.apply(ecs);
916            }
917            ClientboundGamePacket::MoveEntityPosRot(p) => {
918                let mut system_state: SystemState<(
919                    Commands,
920                    Query<(&EntityIdIndex, &InstanceHolder)>,
921                )> = SystemState::new(ecs);
922                let (mut commands, mut query) = system_state.get_mut(ecs);
923                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
924
925                debug!("Got move entity pos rot packet {p:?}");
926
927                let entity = entity_id_index.get(p.entity_id);
928
929                if let Some(entity) = entity {
930                    let new_delta = p.delta.clone();
931                    let new_look_direction = LookDirection {
932                        x_rot: (p.x_rot as i32 * 360) as f32 / 256.,
933                        y_rot: (p.y_rot as i32 * 360) as f32 / 256.,
934                    };
935
936                    let new_on_ground = p.on_ground;
937
938                    commands.entity(entity).queue(RelativeEntityUpdate {
939                        partial_world: instance_holder.partial_instance.clone(),
940                        update: Box::new(move |entity_mut| {
941                            let mut physics = entity_mut.get_mut::<Physics>().unwrap();
942                            let new_pos = physics.vec_delta_codec.decode(
943                                new_delta.xa as i64,
944                                new_delta.ya as i64,
945                                new_delta.za as i64,
946                            );
947                            physics.vec_delta_codec.set_base(new_pos);
948                            physics.set_on_ground(new_on_ground);
949
950                            let mut position = entity_mut.get_mut::<Position>().unwrap();
951                            if new_pos != **position {
952                                **position = new_pos;
953                            }
954
955                            let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
956                            if new_look_direction != *look_direction {
957                                *look_direction = new_look_direction;
958                            }
959                        }),
960                    });
961                } else {
962                    // often triggered by hypixel :(
963                    debug!(
964                        "Got move entity pos rot packet for unknown entity id {}",
965                        p.entity_id
966                    );
967                }
968
969                system_state.apply(ecs);
970            }
971
972            ClientboundGamePacket::MoveEntityRot(p) => {
973                let mut system_state: SystemState<(
974                    Commands,
975                    Query<(&EntityIdIndex, &InstanceHolder)>,
976                )> = SystemState::new(ecs);
977                let (mut commands, mut query) = system_state.get_mut(ecs);
978                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
979
980                let entity = entity_id_index.get(p.entity_id);
981
982                if let Some(entity) = entity {
983                    let new_look_direction = LookDirection {
984                        x_rot: (p.x_rot as i32 * 360) as f32 / 256.,
985                        y_rot: (p.y_rot as i32 * 360) as f32 / 256.,
986                    };
987                    let new_on_ground = p.on_ground;
988
989                    commands.entity(entity).queue(RelativeEntityUpdate {
990                        partial_world: instance_holder.partial_instance.clone(),
991                        update: Box::new(move |entity_mut| {
992                            let mut physics = entity_mut.get_mut::<Physics>().unwrap();
993                            physics.set_on_ground(new_on_ground);
994
995                            let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
996                            if new_look_direction != *look_direction {
997                                *look_direction = new_look_direction;
998                            }
999                        }),
1000                    });
1001                } else {
1002                    warn!(
1003                        "Got move entity rot packet for unknown entity id {}",
1004                        p.entity_id
1005                    );
1006                }
1007
1008                system_state.apply(ecs);
1009            }
1010            ClientboundGamePacket::KeepAlive(p) => {
1011                debug!("Got keep alive packet {p:?} for {player_entity:?}");
1012
1013                let mut system_state: SystemState<(
1014                    EventWriter<KeepAliveEvent>,
1015                    EventWriter<SendPacketEvent>,
1016                )> = SystemState::new(ecs);
1017                let (mut keepalive_events, mut send_packet_events) = system_state.get_mut(ecs);
1018
1019                keepalive_events.send(KeepAliveEvent {
1020                    entity: player_entity,
1021                    id: p.id,
1022                });
1023                send_packet_events.send(SendPacketEvent::new(
1024                    player_entity,
1025                    ServerboundKeepAlive { id: p.id },
1026                ));
1027            }
1028            ClientboundGamePacket::RemoveEntities(p) => {
1029                debug!("Got remove entities packet {p:?}");
1030
1031                let mut system_state: SystemState<(
1032                    Query<&mut EntityIdIndex>,
1033                    Query<&mut LoadedBy>,
1034                )> = SystemState::new(ecs);
1035
1036                let (mut query, mut entity_query) = system_state.get_mut(ecs);
1037                let Ok(mut entity_id_index) = query.get_mut(player_entity) else {
1038                    warn!("our local player doesn't have EntityIdIndex");
1039                    continue;
1040                };
1041
1042                for &id in &p.entity_ids {
1043                    let Some(entity) = entity_id_index.remove(id) else {
1044                        debug!("Tried to remove entity with id {id} but it wasn't in the EntityIdIndex");
1045                        continue;
1046                    };
1047                    let Ok(mut loaded_by) = entity_query.get_mut(entity) else {
1048                        warn!(
1049                            "tried to despawn entity {id} but it doesn't have a LoadedBy component",
1050                        );
1051                        continue;
1052                    };
1053
1054                    // the [`remove_despawned_entities_from_indexes`] system will despawn the entity
1055                    // if it's not loaded by anything anymore
1056
1057                    // also we can't just ecs.despawn because if we're in a swarm then the entity
1058                    // might still be loaded by another client
1059
1060                    loaded_by.remove(&player_entity);
1061                }
1062            }
1063            ClientboundGamePacket::PlayerChat(p) => {
1064                debug!("Got player chat packet {p:?}");
1065
1066                let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
1067                    SystemState::new(ecs);
1068                let mut chat_events = system_state.get_mut(ecs);
1069
1070                chat_events.send(ChatReceivedEvent {
1071                    entity: player_entity,
1072                    packet: ChatPacket::Player(Arc::new(p.clone())),
1073                });
1074            }
1075            ClientboundGamePacket::SystemChat(p) => {
1076                debug!("Got system chat packet {p:?}");
1077
1078                let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
1079                    SystemState::new(ecs);
1080                let mut chat_events = system_state.get_mut(ecs);
1081
1082                chat_events.send(ChatReceivedEvent {
1083                    entity: player_entity,
1084                    packet: ChatPacket::System(Arc::new(p.clone())),
1085                });
1086            }
1087            ClientboundGamePacket::DisguisedChat(p) => {
1088                debug!("Got disguised chat packet {p:?}");
1089
1090                let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
1091                    SystemState::new(ecs);
1092                let mut chat_events = system_state.get_mut(ecs);
1093
1094                chat_events.send(ChatReceivedEvent {
1095                    entity: player_entity,
1096                    packet: ChatPacket::Disguised(Arc::new(p.clone())),
1097                });
1098            }
1099            ClientboundGamePacket::Sound(_p) => {
1100                // debug!("Got sound packet {p:?}");
1101            }
1102            ClientboundGamePacket::LevelEvent(p) => {
1103                debug!("Got level event packet {p:?}");
1104            }
1105            ClientboundGamePacket::BlockUpdate(p) => {
1106                debug!("Got block update packet {p:?}");
1107
1108                let mut system_state: SystemState<Query<&mut InstanceHolder>> =
1109                    SystemState::new(ecs);
1110                let mut query = system_state.get_mut(ecs);
1111                let local_player = query.get_mut(player_entity).unwrap();
1112
1113                let world = local_player.instance.write();
1114
1115                world.chunks.set_block_state(&p.pos, p.block_state);
1116            }
1117            ClientboundGamePacket::Animate(p) => {
1118                debug!("Got animate packet {p:?}");
1119            }
1120            ClientboundGamePacket::SectionBlocksUpdate(p) => {
1121                debug!("Got section blocks update packet {p:?}");
1122                let mut system_state: SystemState<Query<&mut InstanceHolder>> =
1123                    SystemState::new(ecs);
1124                let mut query = system_state.get_mut(ecs);
1125                let local_player = query.get_mut(player_entity).unwrap();
1126
1127                let world = local_player.instance.write();
1128
1129                for state in &p.states {
1130                    world
1131                        .chunks
1132                        .set_block_state(&(p.section_pos + state.pos), state.state);
1133                }
1134            }
1135            ClientboundGamePacket::GameEvent(p) => {
1136                use azalea_protocol::packets::game::c_game_event::EventType;
1137
1138                debug!("Got game event packet {p:?}");
1139
1140                #[allow(clippy::single_match)]
1141                match p.event {
1142                    EventType::ChangeGameMode => {
1143                        let mut system_state: SystemState<Query<&mut LocalGameMode>> =
1144                            SystemState::new(ecs);
1145                        let mut query = system_state.get_mut(ecs);
1146                        let mut local_game_mode = query.get_mut(player_entity).unwrap();
1147                        if let Some(new_game_mode) = GameMode::from_id(p.param as u8) {
1148                            local_game_mode.current = new_game_mode;
1149                        }
1150                    }
1151                    _ => {}
1152                }
1153            }
1154            ClientboundGamePacket::LevelParticles(p) => {
1155                debug!("Got level particles packet {p:?}");
1156            }
1157            ClientboundGamePacket::ServerData(p) => {
1158                debug!("Got server data packet {p:?}");
1159            }
1160            ClientboundGamePacket::SetEquipment(p) => {
1161                debug!("Got set equipment packet {p:?}");
1162            }
1163            ClientboundGamePacket::UpdateMobEffect(p) => {
1164                debug!("Got update mob effect packet {p:?}");
1165            }
1166            ClientboundGamePacket::AddExperienceOrb(_) => {}
1167            ClientboundGamePacket::AwardStats(_) => {}
1168            ClientboundGamePacket::BlockChangedAck(_) => {}
1169            ClientboundGamePacket::BlockDestruction(_) => {}
1170            ClientboundGamePacket::BlockEntityData(_) => {}
1171            ClientboundGamePacket::BlockEvent(p) => {
1172                debug!("Got block event packet {p:?}");
1173            }
1174            ClientboundGamePacket::BossEvent(_) => {}
1175            ClientboundGamePacket::CommandSuggestions(_) => {}
1176            ClientboundGamePacket::ContainerSetContent(p) => {
1177                debug!("Got container set content packet {p:?}");
1178
1179                let mut system_state: SystemState<(
1180                    Query<&mut Inventory>,
1181                    EventWriter<SetContainerContentEvent>,
1182                )> = SystemState::new(ecs);
1183                let (mut query, mut events) = system_state.get_mut(ecs);
1184                let mut inventory = query.get_mut(player_entity).unwrap();
1185
1186                // container id 0 is always the player's inventory
1187                if p.container_id == 0 {
1188                    // this is just so it has the same type as the `else` block
1189                    for (i, slot) in p.items.iter().enumerate() {
1190                        if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) {
1191                            *slot_mut = slot.clone();
1192                        }
1193                    }
1194                } else {
1195                    events.send(SetContainerContentEvent {
1196                        entity: player_entity,
1197                        slots: p.items.clone(),
1198                        container_id: p.container_id,
1199                    });
1200                }
1201            }
1202            ClientboundGamePacket::ContainerSetData(p) => {
1203                debug!("Got container set data packet {p:?}");
1204                // let mut system_state: SystemState<Query<&mut
1205                // InventoryComponent>> =
1206                //     SystemState::new(ecs);
1207                // let mut query = system_state.get_mut(ecs);
1208                // let mut inventory =
1209                // query.get_mut(player_entity).unwrap();
1210
1211                // TODO: handle ContainerSetData packet
1212                // this is used for various things like the furnace progress
1213                // bar
1214                // see https://wiki.vg/Protocol#Set_Container_Property
1215            }
1216            ClientboundGamePacket::ContainerSetSlot(p) => {
1217                debug!("Got container set slot packet {p:?}");
1218
1219                let mut system_state: SystemState<Query<&mut Inventory>> = SystemState::new(ecs);
1220                let mut query = system_state.get_mut(ecs);
1221                let mut inventory = query.get_mut(player_entity).unwrap();
1222
1223                if p.container_id == -1 {
1224                    // -1 means carried item
1225                    inventory.carried = p.item_stack.clone();
1226                } else if p.container_id == -2 {
1227                    if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
1228                        *slot = p.item_stack.clone();
1229                    }
1230                } else {
1231                    let is_creative_mode_and_inventory_closed = false;
1232                    // technically minecraft has slightly different behavior here if you're in
1233                    // creative mode and have your inventory open
1234                    if p.container_id == 0
1235                        && azalea_inventory::Player::is_hotbar_slot(p.slot.into())
1236                    {
1237                        // minecraft also sets a "pop time" here which is used for an animation
1238                        // but that's not really necessary
1239                        if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
1240                            *slot = p.item_stack.clone();
1241                        }
1242                    } else if p.container_id == inventory.id
1243                        && (p.container_id != 0 || !is_creative_mode_and_inventory_closed)
1244                    {
1245                        // var2.containerMenu.setItem(var4, var1.getStateId(), var3);
1246                        if let Some(slot) = inventory.menu_mut().slot_mut(p.slot.into()) {
1247                            *slot = p.item_stack.clone();
1248                            inventory.state_id = p.state_id;
1249                        }
1250                    }
1251                }
1252            }
1253            ClientboundGamePacket::ContainerClose(_p) => {
1254                // there's p.container_id but minecraft doesn't actually check it
1255                let mut system_state: SystemState<EventWriter<ClientSideCloseContainerEvent>> =
1256                    SystemState::new(ecs);
1257                let mut client_side_close_container_events = system_state.get_mut(ecs);
1258                client_side_close_container_events.send(ClientSideCloseContainerEvent {
1259                    entity: player_entity,
1260                });
1261            }
1262            ClientboundGamePacket::Cooldown(_) => {}
1263            ClientboundGamePacket::CustomChatCompletions(_) => {}
1264            ClientboundGamePacket::DeleteChat(_) => {}
1265            ClientboundGamePacket::Explode(p) => {
1266                trace!("Got explode packet {p:?}");
1267                if let Some(knockback) = p.knockback {
1268                    let mut system_state: SystemState<EventWriter<KnockbackEvent>> =
1269                        SystemState::new(ecs);
1270                    let mut knockback_events = system_state.get_mut(ecs);
1271
1272                    knockback_events.send(KnockbackEvent {
1273                        entity: player_entity,
1274                        knockback: KnockbackType::Set(knockback),
1275                    });
1276
1277                    system_state.apply(ecs);
1278                }
1279            }
1280            ClientboundGamePacket::ForgetLevelChunk(p) => {
1281                debug!("Got forget level chunk packet {p:?}");
1282
1283                let mut system_state: SystemState<Query<&mut InstanceHolder>> =
1284                    SystemState::new(ecs);
1285                let mut query = system_state.get_mut(ecs);
1286                let local_player = query.get_mut(player_entity).unwrap();
1287
1288                let mut partial_instance = local_player.partial_instance.write();
1289
1290                partial_instance.chunks.limited_set(&p.pos, None);
1291            }
1292            ClientboundGamePacket::HorseScreenOpen(_) => {}
1293            ClientboundGamePacket::MapItemData(_) => {}
1294            ClientboundGamePacket::MerchantOffers(_) => {}
1295            ClientboundGamePacket::MoveVehicle(_) => {}
1296            ClientboundGamePacket::OpenBook(_) => {}
1297            ClientboundGamePacket::OpenScreen(p) => {
1298                debug!("Got open screen packet {p:?}");
1299                let mut system_state: SystemState<EventWriter<MenuOpenedEvent>> =
1300                    SystemState::new(ecs);
1301                let mut menu_opened_events = system_state.get_mut(ecs);
1302                menu_opened_events.send(MenuOpenedEvent {
1303                    entity: player_entity,
1304                    window_id: p.container_id,
1305                    menu_type: p.menu_type,
1306                    title: p.title.to_owned(),
1307                });
1308            }
1309            ClientboundGamePacket::OpenSignEditor(_) => {}
1310            ClientboundGamePacket::Ping(p) => {
1311                debug!("Got ping packet {p:?}");
1312
1313                let mut system_state: SystemState<EventWriter<SendPacketEvent>> =
1314                    SystemState::new(ecs);
1315                let mut send_packet_events = system_state.get_mut(ecs);
1316
1317                send_packet_events.send(SendPacketEvent::new(
1318                    player_entity,
1319                    ServerboundPong { id: p.id },
1320                ));
1321            }
1322            ClientboundGamePacket::PlaceGhostRecipe(_) => {}
1323            ClientboundGamePacket::PlayerCombatEnd(_) => {}
1324            ClientboundGamePacket::PlayerCombatEnter(_) => {}
1325            ClientboundGamePacket::PlayerCombatKill(p) => {
1326                debug!("Got player kill packet {p:?}");
1327
1328                #[allow(clippy::type_complexity)]
1329                let mut system_state: SystemState<(
1330                    Commands,
1331                    Query<(&MinecraftEntityId, Option<&Dead>)>,
1332                    EventWriter<DeathEvent>,
1333                )> = SystemState::new(ecs);
1334                let (mut commands, mut query, mut death_events) = system_state.get_mut(ecs);
1335                let (entity_id, dead) = query.get_mut(player_entity).unwrap();
1336
1337                if *entity_id == p.player_id && dead.is_none() {
1338                    commands.entity(player_entity).insert(Dead);
1339                    death_events.send(DeathEvent {
1340                        entity: player_entity,
1341                        packet: Some(p.clone()),
1342                    });
1343                }
1344
1345                system_state.apply(ecs);
1346            }
1347            ClientboundGamePacket::PlayerLookAt(_) => {}
1348            ClientboundGamePacket::RemoveMobEffect(_) => {}
1349            ClientboundGamePacket::ResourcePackPush(p) => {
1350                debug!("Got resource pack packet {p:?}");
1351
1352                let mut system_state: SystemState<EventWriter<ResourcePackEvent>> =
1353                    SystemState::new(ecs);
1354                let mut resource_pack_events = system_state.get_mut(ecs);
1355
1356                resource_pack_events.send(ResourcePackEvent {
1357                    entity: player_entity,
1358                    id: p.id,
1359                    url: p.url.to_owned(),
1360                    hash: p.hash.to_owned(),
1361                    required: p.required,
1362                    prompt: p.prompt.to_owned(),
1363                });
1364
1365                system_state.apply(ecs);
1366            }
1367            ClientboundGamePacket::ResourcePackPop(_) => {}
1368            ClientboundGamePacket::Respawn(p) => {
1369                debug!("Got respawn packet {p:?}");
1370
1371                #[allow(clippy::type_complexity)]
1372                let mut system_state: SystemState<(
1373                    Commands,
1374                    Query<(
1375                        &mut InstanceHolder,
1376                        &GameProfileComponent,
1377                        &ClientInformation,
1378                    )>,
1379                    EventWriter<InstanceLoadedEvent>,
1380                    ResMut<InstanceContainer>,
1381                )> = SystemState::new(ecs);
1382                let (mut commands, mut query, mut instance_loaded_events, mut instance_container) =
1383                    system_state.get_mut(ecs);
1384                let (mut instance_holder, game_profile, client_information) =
1385                    query.get_mut(player_entity).unwrap();
1386
1387                {
1388                    let new_instance_name = p.common.dimension.clone();
1389
1390                    let Some(dimension_type_element) =
1391                        instance_holder.instance.read().registries.dimension_type()
1392                    else {
1393                        error!("Server didn't send dimension type registry, can't log in.");
1394                        continue;
1395                    };
1396
1397                    let dimension_name = ResourceLocation::new(&p.common.dimension.to_string());
1398
1399                    let Some(dimension) = dimension_type_element.map.get(&dimension_name) else {
1400                        error!("No dimension_type with name {dimension_name}");
1401                        continue;
1402                    };
1403
1404                    // add this world to the instance_container (or don't if it's already
1405                    // there)
1406                    let weak_instance = instance_container.insert(
1407                        new_instance_name.clone(),
1408                        dimension.height,
1409                        dimension.min_y,
1410                    );
1411                    instance_loaded_events.send(InstanceLoadedEvent {
1412                        entity: player_entity,
1413                        name: new_instance_name.clone(),
1414                        instance: Arc::downgrade(&weak_instance),
1415                    });
1416
1417                    // set the partial_world to an empty world
1418                    // (when we add chunks or entities those will be in the
1419                    // instance_container)
1420
1421                    *instance_holder.partial_instance.write() = PartialInstance::new(
1422                        azalea_world::chunk_storage::calculate_chunk_storage_range(
1423                            client_information.view_distance.into(),
1424                        ),
1425                        Some(player_entity),
1426                    );
1427                    instance_holder.instance = weak_instance;
1428
1429                    // this resets a bunch of our components like physics and stuff
1430                    let entity_bundle = EntityBundle::new(
1431                        game_profile.uuid,
1432                        Vec3::default(),
1433                        azalea_registry::EntityKind::Player,
1434                        new_instance_name,
1435                    );
1436                    // update the local gamemode and metadata things
1437                    commands.entity(player_entity).insert((
1438                        LocalGameMode {
1439                            current: p.common.game_type,
1440                            previous: p.common.previous_game_type.into(),
1441                        },
1442                        entity_bundle,
1443                    ));
1444                }
1445
1446                // Remove the Dead marker component from the player.
1447                commands.entity(player_entity).remove::<Dead>();
1448
1449                system_state.apply(ecs);
1450            }
1451
1452            ClientboundGamePacket::StartConfiguration(_p) => {
1453                let mut system_state: SystemState<(Commands, EventWriter<SendPacketEvent>)> =
1454                    SystemState::new(ecs);
1455                let (mut commands, mut packet_events) = system_state.get_mut(ecs);
1456
1457                packet_events.send(SendPacketEvent::new(
1458                    player_entity,
1459                    ServerboundConfigurationAcknowledged {},
1460                ));
1461
1462                commands
1463                    .entity(player_entity)
1464                    .insert(crate::client::InConfigState)
1465                    .remove::<crate::JoinedClientBundle>();
1466
1467                system_state.apply(ecs);
1468            }
1469
1470            ClientboundGamePacket::EntityPositionSync(p) => {
1471                let mut system_state: SystemState<(
1472                    Commands,
1473                    Query<(&EntityIdIndex, &InstanceHolder)>,
1474                )> = SystemState::new(ecs);
1475                let (mut commands, mut query) = system_state.get_mut(ecs);
1476                let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap();
1477
1478                let Some(entity) = entity_id_index.get(p.id) else {
1479                    debug!("Got teleport entity packet for unknown entity id {}", p.id);
1480                    continue;
1481                };
1482
1483                let new_position = p.values.pos;
1484                let new_on_ground = p.on_ground;
1485                let new_look_direction = p.values.look_direction;
1486
1487                commands.entity(entity).queue(RelativeEntityUpdate {
1488                    partial_world: instance_holder.partial_instance.clone(),
1489                    update: Box::new(move |entity_mut| {
1490                        let is_local_entity = entity_mut.get::<LocalEntity>().is_some();
1491                        let mut physics = entity_mut.get_mut::<Physics>().unwrap();
1492
1493                        physics.vec_delta_codec.set_base(new_position);
1494
1495                        if is_local_entity {
1496                            debug!("Ignoring entity position sync packet for local player");
1497                            return;
1498                        }
1499
1500                        physics.set_on_ground(new_on_ground);
1501
1502                        let mut last_sent_position =
1503                            entity_mut.get_mut::<LastSentPosition>().unwrap();
1504                        **last_sent_position = new_position;
1505                        let mut position = entity_mut.get_mut::<Position>().unwrap();
1506                        **position = new_position;
1507
1508                        let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
1509                        *look_direction = new_look_direction;
1510                    }),
1511                });
1512
1513                system_state.apply(ecs);
1514            }
1515
1516            ClientboundGamePacket::SelectAdvancementsTab(_) => {}
1517            ClientboundGamePacket::SetActionBarText(_) => {}
1518            ClientboundGamePacket::SetBorderCenter(_) => {}
1519            ClientboundGamePacket::SetBorderLerpSize(_) => {}
1520            ClientboundGamePacket::SetBorderSize(_) => {}
1521            ClientboundGamePacket::SetBorderWarningDelay(_) => {}
1522            ClientboundGamePacket::SetBorderWarningDistance(_) => {}
1523            ClientboundGamePacket::SetCamera(_) => {}
1524            ClientboundGamePacket::SetDisplayObjective(_) => {}
1525            ClientboundGamePacket::SetObjective(_) => {}
1526            ClientboundGamePacket::SetPassengers(_) => {}
1527            ClientboundGamePacket::SetPlayerTeam(_) => {}
1528            ClientboundGamePacket::SetScore(_) => {}
1529            ClientboundGamePacket::SetSimulationDistance(_) => {}
1530            ClientboundGamePacket::SetSubtitleText(_) => {}
1531            ClientboundGamePacket::SetTitleText(_) => {}
1532            ClientboundGamePacket::SetTitlesAnimation(_) => {}
1533            ClientboundGamePacket::ClearTitles(_) => {}
1534            ClientboundGamePacket::SoundEntity(_) => {}
1535            ClientboundGamePacket::StopSound(_) => {}
1536            ClientboundGamePacket::TabList(_) => {}
1537            ClientboundGamePacket::TagQuery(_) => {}
1538            ClientboundGamePacket::TakeItemEntity(_) => {}
1539            ClientboundGamePacket::BundleDelimiter(_) => {}
1540            ClientboundGamePacket::DamageEvent(_) => {}
1541            ClientboundGamePacket::HurtAnimation(_) => {}
1542
1543            ClientboundGamePacket::TickingState(_) => {}
1544            ClientboundGamePacket::TickingStep(_) => {}
1545
1546            ClientboundGamePacket::ResetScore(_) => {}
1547            ClientboundGamePacket::CookieRequest(_) => {}
1548            ClientboundGamePacket::DebugSample(_) => {}
1549            ClientboundGamePacket::PongResponse(_) => {}
1550            ClientboundGamePacket::StoreCookie(_) => {}
1551            ClientboundGamePacket::Transfer(_) => {}
1552            ClientboundGamePacket::MoveMinecartAlongTrack(_) => {}
1553            ClientboundGamePacket::SetHeldSlot(_) => {}
1554            ClientboundGamePacket::SetPlayerInventory(_) => {}
1555            ClientboundGamePacket::ProjectilePower(_) => {}
1556            ClientboundGamePacket::CustomReportDetails(_) => {}
1557            ClientboundGamePacket::ServerLinks(_) => {}
1558            ClientboundGamePacket::PlayerRotation(_) => {}
1559            ClientboundGamePacket::RecipeBookAdd(_) => {}
1560            ClientboundGamePacket::RecipeBookRemove(_) => {}
1561            ClientboundGamePacket::RecipeBookSettings(_) => {}
1562        }
1563    }
1564}
1565
1566/// An event for sending a packet to the server while we're in the `game` state.
1567#[derive(Event)]
1568pub struct SendPacketEvent {
1569    pub sent_by: Entity,
1570    pub packet: ServerboundGamePacket,
1571}
1572impl SendPacketEvent {
1573    pub fn new(sent_by: Entity, packet: impl Packet<ServerboundGamePacket>) -> Self {
1574        let packet = packet.into_variant();
1575        Self { sent_by, packet }
1576    }
1577}
1578
1579pub fn handle_send_packet_event(
1580    mut send_packet_events: EventReader<SendPacketEvent>,
1581    mut query: Query<&mut RawConnection>,
1582) {
1583    for event in send_packet_events.read() {
1584        if let Ok(raw_connection) = query.get_mut(event.sent_by) {
1585            // debug!("Sending packet: {:?}", event.packet);
1586            if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
1587                error!("Failed to send packet: {e}");
1588            }
1589        }
1590    }
1591}