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