Skip to main content

azalea_client/plugins/packet/game/
mod.rs

1mod events;
2
3use std::{collections::HashSet, sync::Arc};
4
5use azalea_core::{
6    entity_id::MinecraftEntityId,
7    game_type::GameMode,
8    position::{ChunkPos, Vec3},
9};
10use azalea_entity::{
11    ActiveEffects, Dead, EntityBundle, EntityKindComponent, HasClientLoaded, LoadedBy, LocalEntity,
12    LookDirection, Physics, PlayerAbilities, Position,
13    indexing::{EntityIdIndex, EntityUuidIndex},
14    inventory::Inventory,
15    metadata::{Health, apply_metadata},
16};
17use azalea_protocol::{
18    common::movements::MoveFlags,
19    packets::{ConnectionProtocol, game::*},
20};
21use azalea_registry::builtin::EntityKind;
22use azalea_world::{PartialWorld, WorldName, Worlds};
23use bevy_ecs::{prelude::*, system::SystemState};
24pub use events::*;
25use tracing::{debug, error, trace, warn};
26
27use crate::{
28    ClientInformation,
29    block_update::QueuedServerBlockUpdates,
30    chat::{ChatPacket, ChatReceivedEvent},
31    chunks,
32    connection::RawConnection,
33    cookies::{RequestCookieEvent, StoreCookieEvent},
34    disconnect::DisconnectEvent,
35    interact::BlockStatePredictionHandler,
36    inventory::{ClientsideCloseContainerEvent, MenuOpenedEvent, SetContainerContentEvent},
37    local_player::{Experience, Hunger, LocalGameMode, TabList, WorldHolder},
38    movement::{KnockbackData, KnockbackEvent},
39    packet::{
40        as_system, declare_packet_handlers,
41        relative_updates::{EntityUpdateQuery, RelativeEntityUpdate, should_apply_entity_update},
42    },
43    player::{GameProfileComponent, PlayerInfo},
44    tick_counter::TicksConnected,
45};
46
47pub fn process_packet(ecs: &mut World, player: Entity, packet: &ClientboundGamePacket) {
48    let mut handler = GamePacketHandler { player, ecs };
49
50    // the order of these doesn't matter, that's decided by the protocol library
51    declare_packet_handlers!(
52        ClientboundGamePacket,
53        packet,
54        handler,
55        [
56            login,
57            set_chunk_cache_radius,
58            chunk_batch_start,
59            chunk_batch_finished,
60            custom_payload,
61            change_difficulty,
62            commands,
63            player_abilities,
64            set_cursor_item,
65            update_tags,
66            disconnect,
67            update_recipes,
68            entity_event,
69            player_position,
70            player_info_update,
71            player_info_remove,
72            set_chunk_cache_center,
73            chunks_biomes,
74            light_update,
75            level_chunk_with_light,
76            add_entity,
77            set_entity_data,
78            update_attributes,
79            set_entity_motion,
80            set_entity_link,
81            initialize_border,
82            set_time,
83            set_default_spawn_position,
84            set_health,
85            set_experience,
86            teleport_entity,
87            update_advancements,
88            rotate_head,
89            move_entity_pos,
90            move_entity_pos_rot,
91            move_entity_rot,
92            keep_alive,
93            remove_entities,
94            player_chat,
95            system_chat,
96            disguised_chat,
97            sound,
98            level_event,
99            block_update,
100            animate,
101            section_blocks_update,
102            game_event,
103            level_particles,
104            server_data,
105            set_equipment,
106            update_mob_effect,
107            award_stats,
108            block_changed_ack,
109            block_destruction,
110            block_entity_data,
111            block_event,
112            boss_event,
113            command_suggestions,
114            container_set_content,
115            container_set_data,
116            container_set_slot,
117            container_close,
118            cooldown,
119            custom_chat_completions,
120            delete_chat,
121            explode,
122            forget_level_chunk,
123            mount_screen_open,
124            map_item_data,
125            merchant_offers,
126            move_vehicle,
127            open_book,
128            open_screen,
129            open_sign_editor,
130            ping,
131            place_ghost_recipe,
132            player_combat_end,
133            player_combat_enter,
134            player_combat_kill,
135            player_look_at,
136            remove_mob_effect,
137            resource_pack_push,
138            resource_pack_pop,
139            respawn,
140            start_configuration,
141            entity_position_sync,
142            select_advancements_tab,
143            set_action_bar_text,
144            set_border_center,
145            set_border_lerp_size,
146            set_border_size,
147            set_border_warning_delay,
148            set_border_warning_distance,
149            set_camera,
150            set_display_objective,
151            set_objective,
152            set_passengers,
153            set_player_team,
154            set_score,
155            set_simulation_distance,
156            set_subtitle_text,
157            set_title_text,
158            set_titles_animation,
159            clear_titles,
160            sound_entity,
161            stop_sound,
162            tab_list,
163            tag_query,
164            take_item_entity,
165            bundle_delimiter,
166            damage_event,
167            hurt_animation,
168            ticking_state,
169            ticking_step,
170            reset_score,
171            cookie_request,
172            debug_sample,
173            pong_response,
174            store_cookie,
175            transfer,
176            move_minecart_along_track,
177            set_held_slot,
178            set_player_inventory,
179            projectile_power,
180            custom_report_details,
181            server_links,
182            player_rotation,
183            recipe_book_add,
184            recipe_book_remove,
185            recipe_book_settings,
186            test_instance_block_status,
187            waypoint,
188            clear_dialog,
189            show_dialog,
190            debug_block_value,
191            debug_chunk_value,
192            debug_entity_value,
193            debug_event,
194            game_test_highlight_pos,
195        ]
196    );
197}
198
199pub struct GamePacketHandler<'a> {
200    pub ecs: &'a mut World,
201    pub player: Entity,
202}
203impl GamePacketHandler<'_> {
204    pub fn login(&mut self, p: &ClientboundLogin) {
205        debug!("Got login packet");
206
207        as_system::<(
208            Commands,
209            Query<
210                (
211                    &GameProfileComponent,
212                    &ClientInformation,
213                    Option<&mut WorldName>,
214                    Option<&mut LoadedBy>,
215                    &mut EntityIdIndex,
216                    &mut WorldHolder,
217                ),
218                With<LocalEntity>,
219            >,
220            MessageWriter<WorldLoadedEvent>,
221            ResMut<Worlds>,
222            ResMut<EntityUuidIndex>,
223            Query<&mut LoadedBy, Without<LocalEntity>>,
224        )>(
225            self.ecs,
226            |(
227                mut commands,
228                mut query,
229                mut world_loaded_events,
230                mut worlds,
231                mut entity_uuid_index,
232                mut loaded_by_query,
233            )| {
234                let (
235                    game_profile,
236                    client_information,
237                    world_name,
238                    loaded_by,
239                    mut entity_id_index,
240                    mut world_holder,
241                ) = query.get_mut(self.player).unwrap();
242
243                let new_world_name = WorldName(p.common.dimension.clone());
244
245                if let Some(mut world_name) = world_name {
246                    *world_name = new_world_name.clone();
247                } else {
248                    commands.entity(self.player).insert(new_world_name.clone());
249                }
250
251                let weak_world;
252                {
253                    let client_registries = &world_holder.shared.read().registries;
254
255                    let Some((_dimension_type, dimension_data)) =
256                        p.common.dimension_type(client_registries)
257                    else {
258                        return;
259                    };
260
261                    // add this world to the `worlds` (or don't, if it's already there)
262                    weak_world = worlds.get_or_insert(
263                        new_world_name.clone(),
264                        dimension_data.height,
265                        dimension_data.min_y,
266                        client_registries,
267                    );
268                    world_loaded_events.write(WorldLoadedEvent {
269                        entity: self.player,
270                        name: new_world_name.clone(),
271                        world: Arc::downgrade(&weak_world),
272                    });
273                }
274
275                // set the partial world to an empty world (when we add chunks or entities those
276                // will be in the `worlds`)
277
278                *world_holder.partial.write() = PartialWorld::new(
279                    azalea_world::chunk_storage::calculate_chunk_storage_range(
280                        client_information.view_distance.into(),
281                    ),
282                    // this argument makes it so other clients don't update this player entity in a
283                    // shared world
284                    Some(self.player),
285                );
286                {
287                    let client_registries = world_holder.shared.read().registries.clone();
288                    let shared_registries = &mut weak_world.write().registries;
289                    // add the registries from this world to the weak world
290                    shared_registries.extend(client_registries);
291                }
292                world_holder.shared = weak_world;
293
294                let entity_bundle = EntityBundle::new(
295                    game_profile.uuid,
296                    Vec3::ZERO,
297                    EntityKind::Player,
298                    new_world_name,
299                );
300                let entity_id = p.player_id;
301                // insert our components into the ecs :)
302                commands.entity(self.player).insert((
303                    entity_id,
304                    LocalGameMode {
305                        current: p.common.game_type,
306                        previous: p.common.previous_game_type.into(),
307                    },
308                    entity_bundle,
309                    TicksConnected(0),
310                ));
311
312                azalea_entity::indexing::add_entity_to_indexes(
313                    entity_id,
314                    self.player,
315                    Some(game_profile.uuid),
316                    &mut entity_id_index,
317                    &mut entity_uuid_index,
318                    &mut world_holder.shared.write(),
319                );
320
321                // every entity is now unloaded by this player
322                for mut loaded_by in &mut loaded_by_query.iter_mut() {
323                    loaded_by.remove(&self.player);
324                }
325
326                // update or insert loaded_by
327                if let Some(mut loaded_by) = loaded_by {
328                    loaded_by.insert(self.player);
329                } else {
330                    commands
331                        .entity(self.player)
332                        .insert(LoadedBy(HashSet::from_iter(vec![self.player])));
333                }
334            },
335        );
336    }
337
338    pub fn set_chunk_cache_radius(&mut self, p: &ClientboundSetChunkCacheRadius) {
339        debug!("Got set chunk cache radius packet {p:?}");
340    }
341
342    pub fn chunk_batch_start(&mut self, _p: &ClientboundChunkBatchStart) {
343        // the packet is empty, it's just a marker to tell us when the batch starts and
344        // ends
345        debug!("Got chunk batch start");
346
347        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
348            events.write(chunks::ChunkBatchStartEvent {
349                entity: self.player,
350            });
351        });
352    }
353
354    pub fn chunk_batch_finished(&mut self, p: &ClientboundChunkBatchFinished) {
355        debug!("Got chunk batch finished {p:?}");
356
357        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
358            events.write(chunks::ChunkBatchFinishedEvent {
359                entity: self.player,
360                batch_size: p.batch_size,
361            });
362        });
363    }
364
365    pub fn custom_payload(&mut self, p: &ClientboundCustomPayload) {
366        debug!("Got custom payload packet {p:?}");
367    }
368
369    pub fn change_difficulty(&mut self, p: &ClientboundChangeDifficulty) {
370        debug!("Got difficulty packet {p:?}");
371    }
372
373    pub fn commands(&mut self, _p: &ClientboundCommands) {
374        debug!("Got declare commands packet");
375    }
376
377    pub fn player_abilities(&mut self, p: &ClientboundPlayerAbilities) {
378        debug!("Got player abilities packet {p:?}");
379
380        as_system::<Query<&mut PlayerAbilities>>(self.ecs, |mut query| {
381            let mut player_abilities = query.get_mut(self.player).unwrap();
382
383            *player_abilities = PlayerAbilities::from(p);
384        });
385    }
386
387    pub fn set_cursor_item(&mut self, p: &ClientboundSetCursorItem) {
388        debug!("Got set cursor item packet {p:?}");
389    }
390
391    pub fn update_tags(&mut self, _p: &ClientboundUpdateTags) {
392        debug!("Got update tags packet");
393    }
394
395    pub fn disconnect(&mut self, p: &ClientboundDisconnect) {
396        warn!("Got disconnect packet {p:?}");
397
398        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
399            events.write(DisconnectEvent {
400                entity: self.player,
401                reason: Some(p.reason.clone()),
402            });
403        });
404    }
405
406    pub fn update_recipes(&mut self, _p: &ClientboundUpdateRecipes) {
407        debug!("Got update recipes packet");
408    }
409
410    pub fn entity_event(&mut self, _p: &ClientboundEntityEvent) {
411        // debug!("Got entity event packet {p:?}");
412    }
413
414    pub fn player_position(&mut self, p: &ClientboundPlayerPosition) {
415        debug!("Got player position packet {p:?}");
416
417        as_system::<(
418            Query<(&mut Physics, &mut LookDirection, &mut Position)>,
419            Commands,
420        )>(self.ecs, |(mut query, mut commands)| {
421            let Ok((mut physics, mut direction, mut position)) = query.get_mut(self.player) else {
422                return;
423            };
424
425            p.relative
426                .apply(&p.change, &mut position, &mut direction, &mut physics);
427            // old_pos is set to the current position when we're teleported
428            physics.set_old_pos(*position);
429
430            // send the relevant packets
431            commands.trigger(SendGamePacketEvent::new(
432                self.player,
433                ServerboundAcceptTeleportation { id: p.id },
434            ));
435            commands.trigger(SendGamePacketEvent::new(
436                self.player,
437                ServerboundMovePlayerPosRot {
438                    pos: **position,
439                    look_direction: *direction,
440                    flags: MoveFlags::default(),
441                },
442            ));
443        });
444    }
445
446    pub fn player_info_update(&mut self, p: &ClientboundPlayerInfoUpdate) {
447        debug!("Got player info packet {p:?}");
448
449        as_system::<(
450            Query<&mut TabList>,
451            MessageWriter<AddPlayerEvent>,
452            MessageWriter<UpdatePlayerEvent>,
453            ResMut<TabList>,
454        )>(
455            self.ecs,
456            |(
457                mut query,
458                mut add_player_events,
459                mut update_player_events,
460                mut tab_list_resource,
461            )| {
462                let mut tab_list = query.get_mut(self.player).unwrap();
463
464                for updated_info in &p.entries {
465                    // add the new player maybe
466                    if p.actions.add_player {
467                        let info = PlayerInfo {
468                            profile: updated_info.profile.clone(),
469                            uuid: updated_info.profile.uuid,
470                            gamemode: updated_info.game_mode,
471                            latency: updated_info.latency,
472                            display_name: updated_info.display_name.clone(),
473                        };
474                        tab_list.insert(updated_info.profile.uuid, info.clone());
475                        add_player_events.write(AddPlayerEvent {
476                            entity: self.player,
477                            info,
478                        });
479                    } else if let Some(info) = tab_list.get_mut(&updated_info.profile.uuid) {
480                        // `else if` because the block for add_player above
481                        // already sets all the fields
482                        if p.actions.update_game_mode {
483                            info.gamemode = updated_info.game_mode;
484                        }
485                        if p.actions.update_latency {
486                            info.latency = updated_info.latency;
487                        }
488                        if p.actions.update_display_name {
489                            info.display_name.clone_from(&updated_info.display_name);
490                        }
491                        update_player_events.write(UpdatePlayerEvent {
492                            entity: self.player,
493                            info: info.clone(),
494                        });
495                    } else {
496                        let uuid = updated_info.profile.uuid;
497                        debug!("Ignoring PlayerInfoUpdate for unknown player {uuid}");
498                    }
499                }
500
501                *tab_list_resource = tab_list.clone();
502            },
503        );
504    }
505
506    pub fn player_info_remove(&mut self, p: &ClientboundPlayerInfoRemove) {
507        debug!("Got chunk cache center packet {p:?}");
508
509        as_system::<(
510            Query<&mut TabList>,
511            MessageWriter<RemovePlayerEvent>,
512            ResMut<TabList>,
513        )>(
514            self.ecs,
515            |(mut query, mut remove_player_events, mut tab_list_resource)| {
516                let mut tab_list = query.get_mut(self.player).unwrap();
517
518                for uuid in &p.profile_ids {
519                    if let Some(info) = tab_list.remove(uuid) {
520                        remove_player_events.write(RemovePlayerEvent {
521                            entity: self.player,
522                            info,
523                        });
524                    }
525                    tab_list_resource.remove(uuid);
526                }
527            },
528        );
529    }
530
531    pub fn set_chunk_cache_center(&mut self, p: &ClientboundSetChunkCacheCenter) {
532        debug!("Got chunk cache center packet {p:?}");
533
534        as_system::<Query<&WorldHolder>>(self.ecs, |mut query| {
535            let world_holder = query.get_mut(self.player).unwrap();
536            let mut partial_world = world_holder.partial.write();
537
538            partial_world
539                .chunks
540                .update_view_center(ChunkPos::new(p.x, p.z));
541        });
542    }
543
544    pub fn chunks_biomes(&mut self, _p: &ClientboundChunksBiomes) {}
545
546    pub fn light_update(&mut self, _p: &ClientboundLightUpdate) {
547        // debug!("Got light update packet {p:?}");
548    }
549
550    pub fn level_chunk_with_light(&mut self, p: &ClientboundLevelChunkWithLight) {
551        debug!("Got chunk with light packet {} {}", p.x, p.z);
552
553        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
554            events.write(chunks::ReceiveChunkEvent {
555                entity: self.player,
556                packet: p.clone(),
557            });
558        });
559    }
560
561    pub fn add_entity(&mut self, p: &ClientboundAddEntity) {
562        debug!("Got add entity packet {p:?}");
563
564        as_system::<(
565            Commands,
566            Query<(&mut EntityIdIndex, Option<&WorldName>, Option<&TabList>)>,
567            Query<&mut LoadedBy>,
568            Query<Entity>,
569            Res<Worlds>,
570            ResMut<EntityUuidIndex>,
571        )>(
572            self.ecs,
573            |(
574                mut commands,
575                mut query,
576                mut loaded_by_query,
577                entity_query,
578                worlds,
579                mut entity_uuid_index,
580            )| {
581                let (mut entity_id_index, world_name, tab_list) =
582                    query.get_mut(self.player).unwrap();
583
584                let entity_id = p.id;
585
586                let Some(world_name) = world_name else {
587                    warn!("got add player packet but we haven't gotten a login packet yet");
588                    return;
589                };
590
591                // check if the entity already exists, and if it does then only add to LoadedBy
592                let world = worlds.get(world_name).unwrap();
593                if let Some(&ecs_entity) = world.read().entity_by_id.get(&entity_id) {
594                    // entity already exists
595                    let Ok(mut loaded_by) = loaded_by_query.get_mut(ecs_entity) else {
596                        // LoadedBy for this entity isn't in the ecs! figure out what went wrong
597                        // and print an error
598
599                        let entity_in_ecs = entity_query.get(ecs_entity).is_ok();
600
601                        if entity_in_ecs {
602                            error!(
603                                "LoadedBy for entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id"
604                            );
605                        } else {
606                            error!(
607                                "Entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id"
608                            );
609                        }
610                        return;
611                    };
612                    loaded_by.insert(self.player);
613
614                    // per-client id index
615                    entity_id_index.insert(entity_id, ecs_entity);
616
617                    debug!("added to LoadedBy of entity {ecs_entity:?} with id {entity_id:?}");
618                    return;
619                };
620
621                // entity doesn't exist in the global index!
622
623                let bundle = p.as_entity_bundle(world_name.to_owned());
624                let mut spawned =
625                    commands.spawn((entity_id, LoadedBy(HashSet::from([self.player])), bundle));
626                let ecs_entity: Entity = spawned.id();
627                debug!(
628                    "spawned entity {ecs_entity:?} with id {entity_id:?} at {pos:?}",
629                    pos = p.position
630                );
631
632                azalea_entity::indexing::add_entity_to_indexes(
633                    entity_id,
634                    ecs_entity,
635                    Some(p.uuid),
636                    &mut entity_id_index,
637                    &mut entity_uuid_index,
638                    &mut world.write(),
639                );
640
641                // add the GameProfileComponent if the uuid is in the tab list
642                if let Some(tab_list) = tab_list {
643                    // (technically this makes it possible for non-player entities to have
644                    // GameProfileComponents but the server would have to be doing something
645                    // really weird)
646                    if let Some(player_info) = tab_list.get(&p.uuid) {
647                        spawned.insert(GameProfileComponent(player_info.profile.clone()));
648                    }
649                }
650
651                // the bundle doesn't include the default entity metadata so we add that
652                // separately
653                p.apply_metadata(&mut spawned);
654            },
655        );
656    }
657
658    pub fn set_entity_data(&mut self, p: &ClientboundSetEntityData) {
659        as_system::<(
660            Commands,
661            Query<(&EntityIdIndex, &WorldHolder)>,
662            // this is a separate query since it's applied on the entity id that's being updated
663            // instead of the player that received the packet
664            Query<&EntityKindComponent>,
665        )>(self.ecs, |(mut commands, query, entity_kind_query)| {
666            let (entity_id_index, world_holder) = query.get(self.player).unwrap();
667
668            let entity = entity_id_index.get_by_minecraft_entity(p.id);
669
670            let Some(entity) = entity else {
671                // some servers like hypixel trigger this a lot :(
672                debug!(
673                    "Server sent an entity data packet for an entity id ({}) that we don't know about",
674                    p.id
675                );
676                return;
677            };
678
679            let Ok(entity_kind) = entity_kind_query.get(entity) else {
680                debug!(
681                    "Server sent an entity data packet for an entity id ({}) that we have indexed as {entity} but they don't have EntityKind. Maybe a second local client that just disconnected?",
682                    p.id
683                );
684                return;
685            };
686            let entity_kind = **entity_kind;
687
688            debug!("Got set entity data packet {p:?} for entity of kind {entity_kind:?}");
689
690            let packed_items = p.packed_items.clone().to_vec();
691
692            // we use RelativeEntityUpdate because it makes sure changes aren't made
693            // multiple times
694            commands.entity(entity).queue(RelativeEntityUpdate::new(
695                world_holder.partial.clone(),
696                move |entity| {
697                    let entity_id = entity.id();
698                    entity.world_scope(|world| {
699                        let mut commands_system_state = SystemState::<Commands>::new(world);
700                        let mut commands = commands_system_state.get_mut(world);
701                        let mut entity_commands = commands.entity(entity_id);
702                        if let Err(e) =
703                            apply_metadata(&mut entity_commands, entity_kind, packed_items)
704                        {
705                            warn!("{e}");
706                        }
707                        commands_system_state.apply(world);
708                    });
709                },
710            ));
711        });
712    }
713
714    pub fn update_attributes(&mut self, _p: &ClientboundUpdateAttributes) {
715        // debug!("Got update attributes packet {p:?}");
716    }
717
718    pub fn set_entity_motion(&mut self, p: &ClientboundSetEntityMotion) {
719        // vanilla servers use this packet for knockback, but note that the Explode
720        // packet is also sometimes used by servers for knockback
721
722        as_system::<(
723            Commands,
724            Query<(&EntityIdIndex, &WorldHolder)>,
725            EntityUpdateQuery,
726        )>(self.ecs, |(mut commands, query, entity_update_query)| {
727            let (entity_id_index, world_holder) = query.get(self.player).unwrap();
728
729            let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
730                // note that this log (and some other ones like the one in RemoveEntities)
731                // sometimes happens when killing mobs. it seems to be a vanilla bug, which is
732                // why it's a debug log instead of a warning
733                debug!(
734                    "Got set entity motion packet for unknown entity id {}",
735                    p.id
736                );
737                return;
738            };
739
740            let data = KnockbackData::Set(p.delta.to_vec3());
741
742            // this is to make sure the same entity velocity update doesn't get sent
743            // multiple times when in swarms
744            if should_apply_entity_update(
745                &mut commands,
746                &mut world_holder.partial.write(),
747                entity,
748                entity_update_query,
749            ) {
750                commands.trigger(KnockbackEvent { entity, data });
751            }
752        });
753    }
754
755    pub fn set_entity_link(&mut self, p: &ClientboundSetEntityLink) {
756        debug!("Got set entity link packet {p:?}");
757    }
758
759    pub fn initialize_border(&mut self, p: &ClientboundInitializeBorder) {
760        debug!("Got initialize border packet {p:?}");
761    }
762
763    pub fn set_time(&mut self, _p: &ClientboundSetTime) {
764        // debug!("Got set time packet {p:?}");
765    }
766
767    pub fn set_default_spawn_position(&mut self, p: &ClientboundSetDefaultSpawnPosition) {
768        debug!("Got set default spawn position packet {p:?}");
769    }
770
771    pub fn set_health(&mut self, p: &ClientboundSetHealth) {
772        debug!("Got set health packet {p:?}");
773
774        as_system::<Query<(&mut Health, &mut Hunger)>>(self.ecs, |mut query| {
775            let (mut health, mut hunger) = query.get_mut(self.player).unwrap();
776
777            **health = p.health;
778            (hunger.food, hunger.saturation) = (p.food, p.saturation);
779
780            // the `Dead` component is added by the `update_dead` system
781            // in azalea-world and then the `dead_event` system fires
782            // the Death event.
783        });
784    }
785
786    pub fn set_experience(&mut self, p: &ClientboundSetExperience) {
787        debug!("Got set experience packet {p:?}");
788
789        as_system::<Query<&mut Experience>>(self.ecs, |mut query| {
790            let mut experience = query.get_mut(self.player).unwrap();
791            experience.progress = p.experience_progress;
792            experience.level = p.experience_level;
793            experience.total = p.total_experience;
794        });
795    }
796
797    pub fn teleport_entity(&mut self, p: &ClientboundTeleportEntity) {
798        debug!("Got teleport entity packet {p:?}");
799
800        as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
801            self.ecs,
802            |(mut commands, query)| {
803                let (entity_id_index, world_holder) = query.get(self.player).unwrap();
804
805                let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
806                    warn!("Got teleport entity packet for unknown entity id {}", p.id);
807                    return;
808                };
809
810                let relative = p.relative.clone();
811                let change = p.change.clone();
812
813                commands.entity(entity).queue(RelativeEntityUpdate::new(
814                    world_holder.partial.clone(),
815                    move |entity| {
816                        let entity_id = entity.id();
817                        entity.world_scope(move |world| {
818                            let mut query =
819                                world.query::<(&mut Physics, &mut LookDirection, &mut Position)>();
820                            let (mut physics, mut look_direction, mut position) =
821                                query.get_mut(world, entity_id).unwrap();
822                            let old_position = *position;
823                            relative.apply(
824                                &change,
825                                &mut position,
826                                &mut look_direction,
827                                &mut physics,
828                            );
829                            // old_pos is set to the current position when we're teleported
830                            physics.set_old_pos(old_position);
831                        });
832                    },
833                ));
834            },
835        );
836    }
837
838    pub fn update_advancements(&mut self, p: &ClientboundUpdateAdvancements) {
839        debug!("Got update advancements packet {p:?}");
840    }
841
842    pub fn rotate_head(&mut self, _p: &ClientboundRotateHead) {}
843
844    pub fn move_entity_pos(&mut self, p: &ClientboundMoveEntityPos) {
845        as_system::<(
846            Commands,
847            Query<(&EntityIdIndex, &WorldHolder)>,
848            Query<(&mut Physics, &mut Position)>,
849            EntityUpdateQuery,
850        )>(
851            self.ecs,
852            |(mut commands, player_query, mut entity_query, entity_update_query)| {
853                let (entity_id_index, world_holder) = player_query.get(self.player).unwrap();
854
855                debug!("Got move entity pos packet {p:?}");
856
857                let entity_id = p.entity_id;
858                let Some(entity) = entity_id_index.get_by_minecraft_entity(entity_id) else {
859                    debug!("Got move entity pos packet for unknown entity id {entity_id}");
860                    return;
861                };
862
863                let new_delta = p.delta.clone();
864                let new_on_ground = p.on_ground;
865
866                let (mut physics, mut position) = entity_query.get_mut(entity).unwrap();
867
868                if !should_apply_entity_update(
869                    &mut commands,
870                    &mut world_holder.partial.write(),
871                    entity,
872                    entity_update_query,
873                ) {
874                    return;
875                }
876
877                let new_pos = physics.vec_delta_codec.decode(&new_delta);
878                physics.vec_delta_codec.set_base(new_pos);
879                physics.set_on_ground(new_on_ground);
880
881                if new_pos != **position {
882                    **position = new_pos;
883                }
884
885                trace!("Applied movement update for {entity_id} / {entity}");
886            },
887        );
888    }
889
890    pub fn move_entity_pos_rot(&mut self, p: &ClientboundMoveEntityPosRot) {
891        as_system::<(
892            Commands,
893            Query<(&EntityIdIndex, &WorldHolder)>,
894            Query<(&mut Physics, &mut Position, &mut LookDirection)>,
895            EntityUpdateQuery,
896        )>(
897            self.ecs,
898            |(mut commands, player_query, mut entity_query, entity_update_query)| {
899                let (entity_id_index, world_holder) = player_query.get(self.player).unwrap();
900
901                debug!("Got move entity pos rot packet {p:?}");
902
903                let entity = entity_id_index.get_by_minecraft_entity(p.entity_id);
904
905                let Some(entity) = entity else {
906                    // often triggered by hypixel :(
907                    debug!(
908                        "Got move entity pos rot packet for unknown entity id {}",
909                        p.entity_id
910                    );
911                    return;
912                };
913
914                let new_delta = p.delta.clone();
915                let new_look_direction = LookDirection::new(
916                    (p.y_rot as i32 * 360) as f32 / 256.,
917                    (p.x_rot as i32 * 360) as f32 / 256.,
918                );
919
920                let new_on_ground = p.on_ground;
921
922                let (mut physics, mut position, mut look_direction) =
923                    entity_query.get_mut(entity).unwrap();
924
925                if !should_apply_entity_update(
926                    &mut commands,
927                    &mut world_holder.partial.write(),
928                    entity,
929                    entity_update_query,
930                ) {
931                    return;
932                }
933
934                let new_position = physics.vec_delta_codec.decode(&new_delta);
935                physics.vec_delta_codec.set_base(new_position);
936                physics.set_on_ground(new_on_ground);
937
938                if new_position != **position {
939                    **position = new_position;
940                }
941
942                if new_look_direction != *look_direction {
943                    *look_direction = new_look_direction;
944                }
945            },
946        );
947    }
948
949    pub fn move_entity_rot(&mut self, p: &ClientboundMoveEntityRot) {
950        as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
951            self.ecs,
952            |(mut commands, query)| {
953                let (entity_id_index, world_holder) = query.get(self.player).unwrap();
954
955                let entity = entity_id_index.get_by_minecraft_entity(p.entity_id);
956                if let Some(entity) = entity {
957                    let new_look_direction = LookDirection::new(
958                        (p.y_rot as i32 * 360) as f32 / 256.,
959                        (p.x_rot as i32 * 360) as f32 / 256.,
960                    );
961                    let new_on_ground = p.on_ground;
962
963                    commands.entity(entity).queue(RelativeEntityUpdate::new(
964                        world_holder.partial.clone(),
965                        move |entity_mut| {
966                            let mut physics = entity_mut.get_mut::<Physics>().unwrap();
967                            physics.set_on_ground(new_on_ground);
968
969                            let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
970                            if new_look_direction != *look_direction {
971                                *look_direction = new_look_direction;
972                            }
973                        },
974                    ));
975                } else {
976                    warn!(
977                        "Got move entity rot packet for unknown entity id {}",
978                        p.entity_id
979                    );
980                }
981            },
982        );
983    }
984    pub fn keep_alive(&mut self, p: &ClientboundKeepAlive) {
985        debug!("Got keep alive packet {p:?} for {:?}", self.player);
986
987        as_system::<(MessageWriter<KeepAliveEvent>, Commands)>(
988            self.ecs,
989            |(mut keepalive_events, mut commands)| {
990                keepalive_events.write(KeepAliveEvent {
991                    entity: self.player,
992                    id: p.id,
993                });
994                commands.trigger(SendGamePacketEvent::new(
995                    self.player,
996                    ServerboundKeepAlive { id: p.id },
997                ));
998            },
999        );
1000    }
1001
1002    pub fn remove_entities(&mut self, p: &ClientboundRemoveEntities) {
1003        debug!("Got remove entities packet {p:?}");
1004
1005        as_system::<(Query<&mut EntityIdIndex>, Query<&mut LoadedBy>)>(
1006            self.ecs,
1007            |(mut query, mut entity_query)| {
1008                let Ok(mut entity_id_index) = query.get_mut(self.player) else {
1009                    warn!("our local player doesn't have EntityIdIndex");
1010                    return;
1011                };
1012
1013                for &id in &p.entity_ids {
1014                    let Some(entity) = entity_id_index.remove_by_minecraft_entity(id) else {
1015                        debug!(
1016                            "Tried to remove entity with id {id} but it wasn't in the EntityIdIndex. This may be expected on certain server setups (like if they're using VeryManyPlayers)."
1017                        );
1018                        continue;
1019                    };
1020                    let Ok(mut loaded_by) = entity_query.get_mut(entity) else {
1021                        warn!(
1022                            "tried to despawn entity {id} but it doesn't have a LoadedBy component",
1023                        );
1024                        continue;
1025                    };
1026
1027                    // the `remove_despawned_entities_from_indexes` system will despawn the entity
1028                    // if it's not loaded by anything anymore
1029
1030                    // also we can't just ecs.despawn because if we're in a swarm then the entity
1031                    // might still be loaded by another client
1032
1033                    loaded_by.remove(&self.player);
1034                }
1035            },
1036        );
1037    }
1038    pub fn player_chat(&mut self, p: &ClientboundPlayerChat) {
1039        debug!("Got player chat packet {p:?}");
1040
1041        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
1042            events.write(ChatReceivedEvent {
1043                entity: self.player,
1044                packet: ChatPacket::Player(Arc::new(p.clone())),
1045            });
1046        });
1047    }
1048
1049    pub fn system_chat(&mut self, p: &ClientboundSystemChat) {
1050        debug!("Got system chat packet {p:?}");
1051
1052        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
1053            events.write(ChatReceivedEvent {
1054                entity: self.player,
1055                packet: ChatPacket::System(Arc::new(p.clone())),
1056            });
1057        });
1058    }
1059
1060    pub fn disguised_chat(&mut self, p: &ClientboundDisguisedChat) {
1061        debug!("Got disguised chat packet {p:?}");
1062
1063        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
1064            events.write(ChatReceivedEvent {
1065                entity: self.player,
1066                packet: ChatPacket::Disguised(Arc::new(p.clone())),
1067            });
1068        });
1069    }
1070
1071    pub fn sound(&mut self, _p: &ClientboundSound) {}
1072
1073    pub fn level_event(&mut self, p: &ClientboundLevelEvent) {
1074        debug!("Got level event packet {p:?}");
1075    }
1076
1077    pub fn block_update(&mut self, p: &ClientboundBlockUpdate) {
1078        debug!("Got block update packet {p:?}");
1079
1080        as_system::<Query<&mut QueuedServerBlockUpdates>>(self.ecs, |mut query| {
1081            let mut queued = query.get_mut(self.player).unwrap();
1082            queued.list.push((p.pos, p.block_state));
1083        });
1084    }
1085
1086    pub fn animate(&mut self, p: &ClientboundAnimate) {
1087        debug!("Got animate packet {p:?}");
1088    }
1089
1090    pub fn section_blocks_update(&mut self, p: &ClientboundSectionBlocksUpdate) {
1091        debug!("Got section blocks update packet {p:?}");
1092
1093        as_system::<Query<&mut QueuedServerBlockUpdates>>(self.ecs, |mut query| {
1094            let mut queued = query.get_mut(self.player).unwrap();
1095            for new_state in &p.states {
1096                let pos = p.section_pos + new_state.pos;
1097                queued.list.push((pos, new_state.state));
1098            }
1099        });
1100    }
1101
1102    pub fn game_event(&mut self, p: &ClientboundGameEvent) {
1103        use azalea_protocol::packets::game::c_game_event::EventType;
1104
1105        debug!("Got game event packet {p:?}");
1106
1107        #[allow(clippy::single_match)]
1108        match p.event {
1109            EventType::ChangeGameMode => {
1110                as_system::<Query<&mut LocalGameMode>>(self.ecs, |mut query| {
1111                    let mut local_game_mode = query.get_mut(self.player).unwrap();
1112                    if let Some(new_game_mode) = GameMode::from_id(p.param as u8) {
1113                        local_game_mode.current = new_game_mode;
1114                    }
1115                });
1116            }
1117            _ => {}
1118        }
1119    }
1120
1121    pub fn level_particles(&mut self, p: &ClientboundLevelParticles) {
1122        debug!("Got level particles packet {p:?}");
1123    }
1124
1125    pub fn server_data(&mut self, p: &ClientboundServerData) {
1126        debug!("Got server data packet {p:?}");
1127    }
1128
1129    pub fn set_equipment(&mut self, p: &ClientboundSetEquipment) {
1130        debug!("Got set equipment packet {p:?}");
1131    }
1132
1133    pub fn update_mob_effect(&mut self, p: &ClientboundUpdateMobEffect) {
1134        debug!("Got update mob effect packet {p:?}");
1135
1136        let mob_effect = p.mob_effect;
1137        let effect_data = &p.data;
1138
1139        as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
1140            self.ecs,
1141            |(mut commands, query)| {
1142                let (entity_id_index, world_holder) = query.get(self.player).unwrap();
1143
1144                let Some(entity) = entity_id_index.get_by_minecraft_entity(p.entity_id) else {
1145                    debug!(
1146                        "Got update mob effect packet for unknown entity id {}",
1147                        p.entity_id
1148                    );
1149                    return;
1150                };
1151
1152                let partial_world = world_holder.partial.clone();
1153                let effect_data = effect_data.clone();
1154                commands.entity(entity).queue(RelativeEntityUpdate::new(
1155                    partial_world,
1156                    move |entity| {
1157                        if let Some(mut active_effects) = entity.get_mut::<ActiveEffects>() {
1158                            active_effects.insert(mob_effect, effect_data.clone());
1159                        } else {
1160                            let mut active_effects = ActiveEffects::default();
1161                            active_effects.insert(mob_effect, effect_data.clone());
1162                            entity.insert(active_effects);
1163                        }
1164                    },
1165                ));
1166            },
1167        );
1168    }
1169
1170    pub fn award_stats(&mut self, _p: &ClientboundAwardStats) {}
1171
1172    pub fn block_changed_ack(&mut self, p: &ClientboundBlockChangedAck) {
1173        as_system::<Query<(&WorldHolder, &mut BlockStatePredictionHandler)>>(
1174            self.ecs,
1175            |mut query| {
1176                let (local_player, mut prediction_handler) = query.get_mut(self.player).unwrap();
1177                let world = local_player.shared.read();
1178                prediction_handler.end_prediction_up_to(p.seq, &world);
1179            },
1180        );
1181    }
1182
1183    pub fn block_destruction(&mut self, _p: &ClientboundBlockDestruction) {}
1184
1185    pub fn block_entity_data(&mut self, _p: &ClientboundBlockEntityData) {}
1186
1187    pub fn block_event(&mut self, p: &ClientboundBlockEvent) {
1188        debug!("Got block event packet {p:?}");
1189    }
1190
1191    pub fn boss_event(&mut self, _p: &ClientboundBossEvent) {}
1192
1193    pub fn command_suggestions(&mut self, _p: &ClientboundCommandSuggestions) {}
1194
1195    pub fn container_set_content(&mut self, p: &ClientboundContainerSetContent) {
1196        debug!("Got container set content packet {p:?}");
1197
1198        as_system::<(Commands, Query<&mut Inventory>)>(self.ecs, |(mut commands, mut query)| {
1199            let mut inventory = query.get_mut(self.player).unwrap();
1200
1201            // container id 0 is always the player's inventory
1202            if p.container_id == 0 {
1203                // this is just so it has the same type as the `else` block
1204                for (i, slot) in p.items.iter().enumerate() {
1205                    if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) {
1206                        *slot_mut = slot.clone();
1207                    }
1208                }
1209            } else {
1210                commands.trigger(SetContainerContentEvent {
1211                    entity: self.player,
1212                    slots: p.items.clone(),
1213                    container_id: p.container_id,
1214                });
1215            }
1216        });
1217    }
1218
1219    pub fn container_set_data(&mut self, p: &ClientboundContainerSetData) {
1220        debug!("Got container set data packet {p:?}");
1221
1222        // TODO: handle ContainerSetData packet
1223        // this is used for various things like the furnace progress
1224        // bar
1225        // see https://minecraft.wiki/w/Java_Edition_protocol/Packets#Set_Container_Property
1226
1227        // as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
1228        //     let inventory = query.get_mut(self.player).unwrap();
1229        // });
1230    }
1231
1232    pub fn container_set_slot(&mut self, p: &ClientboundContainerSetSlot) {
1233        debug!("Got container set slot packet {p:?}");
1234
1235        as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
1236            let mut inventory = query.get_mut(self.player).unwrap();
1237
1238            if p.container_id == -1 {
1239                // -1 means carried item
1240                inventory.carried = p.item_stack.clone();
1241            } else if p.container_id == -2 {
1242                if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
1243                    *slot = p.item_stack.clone();
1244                }
1245            } else {
1246                let is_creative_mode_and_inventory_closed = false;
1247                // technically minecraft has slightly different behavior here if you're in
1248                // creative mode and have your inventory open
1249                if p.container_id == 0 && azalea_inventory::Player::is_hotbar_slot(p.slot.into()) {
1250                    // minecraft also sets a "pop time" here which is used for an animation
1251                    // but that's not really necessary
1252                    if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
1253                        *slot = p.item_stack.clone();
1254                    }
1255                } else if p.container_id == inventory.id
1256                    && (p.container_id != 0 || !is_creative_mode_and_inventory_closed)
1257                {
1258                    // var2.containerMenu.setItem(var4, var1.getStateId(), var3);
1259                    if let Some(slot) = inventory.menu_mut().slot_mut(p.slot.into()) {
1260                        *slot = p.item_stack.clone();
1261                        inventory.state_id = p.state_id;
1262                    }
1263                }
1264            }
1265        });
1266    }
1267
1268    pub fn container_close(&mut self, p: &ClientboundContainerClose) {
1269        // there's a container_id field in the packet, but minecraft doesn't actually
1270        // check it
1271
1272        debug!("Got container close packet {p:?}");
1273
1274        as_system::<Commands>(self.ecs, |mut commands| {
1275            commands.trigger(ClientsideCloseContainerEvent {
1276                entity: self.player,
1277            });
1278        });
1279    }
1280
1281    pub fn cooldown(&mut self, _p: &ClientboundCooldown) {}
1282
1283    pub fn custom_chat_completions(&mut self, _p: &ClientboundCustomChatCompletions) {}
1284
1285    pub fn delete_chat(&mut self, _p: &ClientboundDeleteChat) {}
1286
1287    pub fn explode(&mut self, p: &ClientboundExplode) {
1288        debug!("Got explode packet {p:?}");
1289
1290        as_system::<Commands>(self.ecs, |mut knockback_events| {
1291            if let Some(knockback) = p.player_knockback {
1292                knockback_events.trigger(KnockbackEvent {
1293                    entity: self.player,
1294                    data: KnockbackData::Add(knockback),
1295                });
1296            }
1297        });
1298    }
1299
1300    pub fn forget_level_chunk(&mut self, p: &ClientboundForgetLevelChunk) {
1301        debug!("Got forget level chunk packet {p:?}");
1302
1303        as_system::<Query<&WorldHolder>>(self.ecs, |mut query| {
1304            let local_player = query.get_mut(self.player).unwrap();
1305
1306            let mut partial_world = local_player.partial.write();
1307
1308            partial_world.chunks.limited_set(&p.pos, None);
1309        });
1310    }
1311
1312    pub fn mount_screen_open(&mut self, _p: &ClientboundMountScreenOpen) {}
1313
1314    pub fn map_item_data(&mut self, _p: &ClientboundMapItemData) {}
1315
1316    pub fn merchant_offers(&mut self, _p: &ClientboundMerchantOffers) {}
1317
1318    pub fn move_vehicle(&mut self, _p: &ClientboundMoveVehicle) {}
1319
1320    pub fn open_book(&mut self, _p: &ClientboundOpenBook) {}
1321
1322    pub fn open_screen(&mut self, p: &ClientboundOpenScreen) {
1323        debug!("Got open screen packet {p:?}");
1324
1325        as_system::<Commands>(self.ecs, |mut commands| {
1326            commands.trigger(MenuOpenedEvent {
1327                entity: self.player,
1328                window_id: p.container_id,
1329                menu_type: p.menu_type,
1330                title: p.title.to_owned(),
1331            });
1332        });
1333    }
1334
1335    pub fn open_sign_editor(&mut self, _p: &ClientboundOpenSignEditor) {}
1336
1337    pub fn ping(&mut self, p: &ClientboundPing) {
1338        debug!("Got ping packet {p:?}");
1339
1340        as_system::<Commands>(self.ecs, |mut commands| {
1341            commands.trigger(GamePingEvent {
1342                entity: self.player,
1343                packet: p.clone(),
1344            });
1345        });
1346    }
1347
1348    pub fn place_ghost_recipe(&mut self, _p: &ClientboundPlaceGhostRecipe) {}
1349
1350    pub fn player_combat_end(&mut self, _p: &ClientboundPlayerCombatEnd) {}
1351
1352    pub fn player_combat_enter(&mut self, _p: &ClientboundPlayerCombatEnter) {}
1353
1354    pub fn player_combat_kill(&mut self, p: &ClientboundPlayerCombatKill) {
1355        debug!("Got player kill packet {p:?}");
1356
1357        as_system::<(
1358            Commands,
1359            Query<(&MinecraftEntityId, Option<&Dead>)>,
1360            MessageWriter<_>,
1361        )>(self.ecs, |(mut commands, mut query, mut events)| {
1362            let (entity_id, dead) = query.get_mut(self.player).unwrap();
1363
1364            if *entity_id == p.player_id && dead.is_none() {
1365                commands.entity(self.player).insert(Dead);
1366                events.write(DeathEvent {
1367                    entity: self.player,
1368                    packet: Some(p.clone()),
1369                });
1370            }
1371        });
1372    }
1373
1374    pub fn player_look_at(&mut self, _p: &ClientboundPlayerLookAt) {}
1375
1376    pub fn remove_mob_effect(&mut self, p: &ClientboundRemoveMobEffect) {
1377        debug!("Got remove mob effect packet {p:?}");
1378
1379        let mob_effect = p.effect;
1380
1381        as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
1382            self.ecs,
1383            |(mut commands, query)| {
1384                let (entity_id_index, world_holder) = query.get(self.player).unwrap();
1385
1386                let Some(entity) = entity_id_index.get_by_minecraft_entity(p.entity_id) else {
1387                    debug!(
1388                        "Got remove mob effect packet for unknown entity id {}",
1389                        p.entity_id
1390                    );
1391                    return;
1392                };
1393
1394                let partial_world = world_holder.partial.clone();
1395                commands.entity(entity).queue(RelativeEntityUpdate::new(
1396                    partial_world,
1397                    move |entity| {
1398                        if let Some(mut active_effects) = entity.get_mut::<ActiveEffects>() {
1399                            active_effects.remove(mob_effect);
1400                        }
1401                    },
1402                ));
1403            },
1404        );
1405    }
1406
1407    pub fn resource_pack_push(&mut self, p: &ClientboundResourcePackPush) {
1408        debug!("Got resource pack packet {p:?}");
1409
1410        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
1411            events.write(ResourcePackEvent {
1412                entity: self.player,
1413                id: p.id,
1414                url: p.url.to_owned(),
1415                hash: p.hash.to_owned(),
1416                required: p.required,
1417                prompt: p.prompt.to_owned(),
1418            });
1419        });
1420    }
1421
1422    pub fn resource_pack_pop(&mut self, _p: &ClientboundResourcePackPop) {}
1423
1424    pub fn respawn(&mut self, p: &ClientboundRespawn) {
1425        debug!("Got respawn packet {p:?}");
1426
1427        as_system::<(
1428            Commands,
1429            Query<
1430                (
1431                    &mut WorldHolder,
1432                    &GameProfileComponent,
1433                    &ClientInformation,
1434                    Option<&mut WorldName>,
1435                ),
1436                With<LocalEntity>,
1437            >,
1438            MessageWriter<_>,
1439            ResMut<Worlds>,
1440            Query<&mut LoadedBy, Without<LocalEntity>>,
1441        )>(
1442            self.ecs,
1443            |(mut commands, mut query, mut events, mut worlds, mut loaded_by_query)| {
1444                let Ok((mut world_holder, game_profile, client_information, world_name)) =
1445                    query.get_mut(self.player)
1446                else {
1447                    warn!("Got respawn packet but player doesn't have the required components");
1448                    return;
1449                };
1450
1451                let new_world_name = WorldName(p.common.dimension.clone());
1452
1453                if let Some(mut world_name) = world_name {
1454                    *world_name = new_world_name.clone();
1455                } else {
1456                    commands.entity(self.player).insert(new_world_name.clone());
1457                }
1458
1459                let weak_world;
1460                {
1461                    let client_registries = &world_holder.shared.read().registries;
1462                    let Some((_dimension_type, dimension_data)) =
1463                        p.common.dimension_type(client_registries)
1464                    else {
1465                        return;
1466                    };
1467
1468                    // add this world to the `worlds` (or don't if it's already there)
1469                    weak_world = worlds.get_or_insert(
1470                        new_world_name.clone(),
1471                        dimension_data.height,
1472                        dimension_data.min_y,
1473                        client_registries,
1474                    );
1475                    events.write(WorldLoadedEvent {
1476                        entity: self.player,
1477                        name: new_world_name.clone(),
1478                        world: Arc::downgrade(&weak_world),
1479                    });
1480                }
1481
1482                // set the partial world to an empty world (when we add chunks or entities,
1483                // those will be in the `worlds`)
1484
1485                *world_holder.partial.write() = PartialWorld::new(
1486                    azalea_world::chunk_storage::calculate_chunk_storage_range(
1487                        client_information.view_distance.into(),
1488                    ),
1489                    Some(self.player),
1490                );
1491                world_holder.shared = weak_world;
1492
1493                // every entity is now unloaded by this player
1494                for mut loaded_by in &mut loaded_by_query.iter_mut() {
1495                    loaded_by.remove(&self.player);
1496                }
1497
1498                // this resets a bunch of our components like physics and stuff
1499                let entity_bundle = EntityBundle::new(
1500                    game_profile.uuid,
1501                    Vec3::ZERO,
1502                    EntityKind::Player,
1503                    new_world_name,
1504                );
1505                // update the local gamemode and metadata things
1506                commands.entity(self.player).insert((
1507                    LocalGameMode {
1508                        current: p.common.game_type,
1509                        previous: p.common.previous_game_type.into(),
1510                    },
1511                    entity_bundle,
1512                ));
1513
1514                commands
1515                    .entity(self.player)
1516                    .remove::<(Dead, HasClientLoaded)>();
1517            },
1518        )
1519    }
1520
1521    pub fn start_configuration(&mut self, _p: &ClientboundStartConfiguration) {
1522        debug!("Got start configuration packet");
1523
1524        as_system::<(Commands, Query<(&mut RawConnection, &mut WorldHolder)>)>(
1525            self.ecs,
1526            |(mut commands, mut query)| {
1527                let Some((mut raw_conn, mut world_holder)) = query.get_mut(self.player).ok() else {
1528                    warn!("Got start configuration packet but player doesn't have a RawConnection");
1529                    return;
1530                };
1531                raw_conn.state = ConnectionProtocol::Configuration;
1532
1533                commands.trigger(SendGamePacketEvent::new(
1534                    self.player,
1535                    ServerboundConfigurationAcknowledged,
1536                ));
1537
1538                commands
1539                    .entity(self.player)
1540                    .insert(crate::client::InConfigState)
1541                    .remove::<crate::JoinedClientBundle>()
1542                    .remove::<EntityBundle>();
1543
1544                world_holder.reset();
1545            },
1546        );
1547    }
1548
1549    pub fn entity_position_sync(&mut self, p: &ClientboundEntityPositionSync) {
1550        as_system::<(
1551            Commands,
1552            Query<(
1553                &EntityIdIndex,
1554                &WorldHolder,
1555                Option<&LocalEntity>,
1556                &mut Physics,
1557                &mut Position,
1558                &mut LookDirection,
1559            )>,
1560            EntityUpdateQuery,
1561        )>(
1562            self.ecs,
1563            |(mut commands, mut query, entity_update_query)| {
1564                let (
1565                    entity_id_index,
1566                    world_holder,
1567                    local_entity,
1568                    mut physics,
1569                    mut position,
1570                    mut look_direction,
1571                ) = query.get_mut(self.player).unwrap();
1572
1573                let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
1574                    debug!("Got teleport entity packet for unknown entity id {}", p.id);
1575                    return;
1576                };
1577
1578                let new_position = p.values.pos;
1579                let new_on_ground = p.on_ground;
1580                let new_look_direction = p.values.look_direction;
1581
1582                if !should_apply_entity_update(
1583                    &mut commands,
1584                    &mut world_holder.partial.write(),
1585                    entity,
1586                    entity_update_query,
1587                ) {
1588                    return;
1589                }
1590                let is_local_entity = local_entity.is_some();
1591
1592                physics.vec_delta_codec.set_base(new_position);
1593
1594                if is_local_entity {
1595                    debug!("Ignoring entity position sync packet for local player");
1596                    return;
1597                }
1598
1599                physics.set_on_ground(new_on_ground);
1600
1601                if **position != new_position {
1602                    **position = new_position;
1603                }
1604
1605                if *look_direction != new_look_direction {
1606                    *look_direction = new_look_direction;
1607                }
1608            },
1609        );
1610    }
1611
1612    pub fn select_advancements_tab(&mut self, _p: &ClientboundSelectAdvancementsTab) {}
1613    pub fn set_action_bar_text(&mut self, _p: &ClientboundSetActionBarText) {}
1614    pub fn set_border_center(&mut self, _p: &ClientboundSetBorderCenter) {}
1615    pub fn set_border_lerp_size(&mut self, _p: &ClientboundSetBorderLerpSize) {}
1616    pub fn set_border_size(&mut self, _p: &ClientboundSetBorderSize) {}
1617    pub fn set_border_warning_delay(&mut self, _p: &ClientboundSetBorderWarningDelay) {}
1618    pub fn set_border_warning_distance(&mut self, _p: &ClientboundSetBorderWarningDistance) {}
1619    pub fn set_camera(&mut self, _p: &ClientboundSetCamera) {}
1620    pub fn set_display_objective(&mut self, _p: &ClientboundSetDisplayObjective) {}
1621    pub fn set_objective(&mut self, _p: &ClientboundSetObjective) {}
1622    pub fn set_passengers(&mut self, _p: &ClientboundSetPassengers) {}
1623    pub fn set_player_team(&mut self, p: &ClientboundSetPlayerTeam) {
1624        debug!("Got set player team packet {p:?}");
1625    }
1626    pub fn set_score(&mut self, _p: &ClientboundSetScore) {}
1627    pub fn set_simulation_distance(&mut self, _p: &ClientboundSetSimulationDistance) {}
1628    pub fn set_subtitle_text(&mut self, _p: &ClientboundSetSubtitleText) {}
1629    pub fn set_title_text(&mut self, _p: &ClientboundSetTitleText) {}
1630    pub fn set_titles_animation(&mut self, _p: &ClientboundSetTitlesAnimation) {}
1631    pub fn clear_titles(&mut self, _p: &ClientboundClearTitles) {}
1632    pub fn sound_entity(&mut self, _p: &ClientboundSoundEntity) {}
1633    pub fn stop_sound(&mut self, _p: &ClientboundStopSound) {}
1634    pub fn tab_list(&mut self, _p: &ClientboundTabList) {}
1635    pub fn tag_query(&mut self, _p: &ClientboundTagQuery) {}
1636    pub fn take_item_entity(&mut self, _p: &ClientboundTakeItemEntity) {}
1637    pub fn bundle_delimiter(&mut self, _p: &ClientboundBundleDelimiter) {}
1638    pub fn damage_event(&mut self, _p: &ClientboundDamageEvent) {}
1639    pub fn hurt_animation(&mut self, _p: &ClientboundHurtAnimation) {}
1640    pub fn ticking_state(&mut self, _p: &ClientboundTickingState) {}
1641    pub fn ticking_step(&mut self, _p: &ClientboundTickingStep) {}
1642    pub fn reset_score(&mut self, _p: &ClientboundResetScore) {}
1643    pub fn cookie_request(&mut self, p: &ClientboundCookieRequest) {
1644        debug!("Got cookie request packet {p:?}");
1645        as_system::<Commands>(self.ecs, |mut commands| {
1646            commands.trigger(RequestCookieEvent {
1647                entity: self.player,
1648                key: p.key.clone(),
1649            });
1650        });
1651    }
1652    pub fn store_cookie(&mut self, p: &ClientboundStoreCookie) {
1653        debug!("Got store cookie packet {p:?}");
1654        as_system::<Commands>(self.ecs, |mut commands| {
1655            commands.trigger(StoreCookieEvent {
1656                entity: self.player,
1657                key: p.key.clone(),
1658                payload: p.payload.clone(),
1659            });
1660        });
1661    }
1662    pub fn debug_sample(&mut self, _p: &ClientboundDebugSample) {}
1663    pub fn pong_response(&mut self, _p: &ClientboundPongResponse) {}
1664    pub fn transfer(&mut self, _p: &ClientboundTransfer) {}
1665    pub fn move_minecart_along_track(&mut self, _p: &ClientboundMoveMinecartAlongTrack) {}
1666    pub fn set_held_slot(&mut self, p: &ClientboundSetHeldSlot) {
1667        debug!("Got set held slot packet {p:?}");
1668
1669        as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
1670            let mut inventory = query.get_mut(self.player).unwrap();
1671            if p.slot <= 8 {
1672                inventory.selected_hotbar_slot = p.slot as u8;
1673            }
1674        });
1675    }
1676    pub fn set_player_inventory(&mut self, _p: &ClientboundSetPlayerInventory) {}
1677    pub fn projectile_power(&mut self, _p: &ClientboundProjectilePower) {}
1678    pub fn custom_report_details(&mut self, _p: &ClientboundCustomReportDetails) {}
1679    pub fn server_links(&mut self, _p: &ClientboundServerLinks) {}
1680    pub fn player_rotation(&mut self, _p: &ClientboundPlayerRotation) {}
1681    pub fn recipe_book_add(&mut self, _p: &ClientboundRecipeBookAdd) {}
1682    pub fn recipe_book_remove(&mut self, _p: &ClientboundRecipeBookRemove) {}
1683    pub fn recipe_book_settings(&mut self, _p: &ClientboundRecipeBookSettings) {}
1684    pub fn test_instance_block_status(&mut self, _p: &ClientboundTestInstanceBlockStatus) {}
1685    pub fn waypoint(&mut self, _p: &ClientboundWaypoint) {}
1686
1687    pub fn clear_dialog(&mut self, p: &ClientboundClearDialog) {
1688        debug!("Got clear dialog packet {p:?}");
1689    }
1690    pub fn show_dialog(&mut self, p: &ClientboundShowDialog) {
1691        debug!("Got show dialog packet {p:?}");
1692    }
1693
1694    pub fn debug_block_value(&mut self, p: &ClientboundDebugBlockValue) {
1695        debug!("Got debug block value packet {p:?}");
1696    }
1697    pub fn debug_chunk_value(&mut self, p: &ClientboundDebugChunkValue) {
1698        debug!("Got debug chunk value packet {p:?}");
1699    }
1700    pub fn debug_entity_value(&mut self, p: &ClientboundDebugEntityValue) {
1701        debug!("Got debug entity value packet {p:?}");
1702    }
1703
1704    pub fn debug_event(&mut self, p: &ClientboundDebugEvent) {
1705        debug!("Got debug event packet {p:?}");
1706    }
1707    pub fn game_test_highlight_pos(&mut self, p: &ClientboundGameTestHighlightPos) {
1708        debug!("Got game test highlight pos packet {p:?}");
1709    }
1710}