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