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::*, system::SystemState};
29pub use events::*;
30use tracing::{debug, error, warn};
31
32use crate::{
33    ClientInformation,
34    block_update::QueuedServerBlockUpdates,
35    chat::{ChatPacket, ChatReceivedEvent},
36    chunks,
37    connection::RawConnection,
38    cookies::{RequestCookieEvent, StoreCookieEvent},
39    disconnect::DisconnectEvent,
40    interact::BlockStatePredictionHandler,
41    inventory::{ClientsideCloseContainerEvent, MenuOpenedEvent, SetContainerContentEvent},
42    local_player::{Experience, Hunger, LocalGameMode, TabList, 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        ]
201    );
202}
203
204pub struct GamePacketHandler<'a> {
205    pub ecs: &'a mut World,
206    pub player: Entity,
207}
208impl GamePacketHandler<'_> {
209    pub fn login(&mut self, p: &ClientboundLogin) {
210        debug!("Got login packet");
211
212        as_system::<(
213            Commands,
214            Query<
215                (
216                    &GameProfileComponent,
217                    &ClientInformation,
218                    Option<&mut WorldName>,
219                    Option<&mut LoadedBy>,
220                    &mut EntityIdIndex,
221                    &mut WorldHolder,
222                ),
223                With<LocalEntity>,
224            >,
225            MessageWriter<WorldLoadedEvent>,
226            ResMut<Worlds>,
227            ResMut<EntityUuidIndex>,
228            Query<&mut LoadedBy, Without<LocalEntity>>,
229        )>(
230            self.ecs,
231            |(
232                mut commands,
233                mut query,
234                mut world_loaded_events,
235                mut worlds,
236                mut entity_uuid_index,
237                mut loaded_by_query,
238            )| {
239                let (
240                    game_profile,
241                    client_information,
242                    world_name,
243                    loaded_by,
244                    mut entity_id_index,
245                    mut world_holder,
246                ) = query.get_mut(self.player).unwrap();
247
248                let new_world_name = WorldName(p.common.dimension.clone());
249
250                if let Some(mut world_name) = world_name {
251                    *world_name = new_world_name.clone();
252                } else {
253                    commands.entity(self.player).insert(new_world_name.clone());
254                }
255
256                let weak_world;
257                {
258                    let client_registries = &world_holder.shared.read().registries;
259
260                    let Some((_dimension_type, dimension_data)) =
261                        p.common.dimension_type(client_registries)
262                    else {
263                        return;
264                    };
265
266                    // add this world to the `worlds` (or don't, if it's already there)
267                    weak_world = worlds.get_or_insert(
268                        new_world_name.clone(),
269                        dimension_data.height,
270                        dimension_data.min_y,
271                        client_registries,
272                    );
273                    world_loaded_events.write(WorldLoadedEvent {
274                        entity: self.player,
275                        name: new_world_name.clone(),
276                        world: Arc::downgrade(&weak_world),
277                    });
278                }
279
280                // set the partial world to an empty world (when we add chunks or entities those
281                // will be in the `worlds`)
282
283                *world_holder.partial.write() = PartialWorld::new(
284                    azalea_world::chunk::calculate_chunk_storage_range(
285                        client_information.view_distance.into(),
286                    ),
287                    // this argument makes it so other clients don't update this player entity in a
288                    // shared world
289                    Some(self.player),
290                );
291                {
292                    let client_registries = world_holder.shared.read().registries.clone();
293                    let shared_registries = &mut weak_world.write().registries;
294                    // add the registries from this world to the weak world
295                    shared_registries.extend(client_registries);
296                }
297                world_holder.shared = weak_world;
298
299                let entity_bundle = EntityBundle::new(
300                    game_profile.uuid,
301                    Vec3::ZERO,
302                    EntityKind::Player,
303                    new_world_name,
304                );
305                let entity_id = p.player_id;
306                // insert our components into the ecs :)
307                commands.entity(self.player).insert((
308                    entity_id,
309                    LocalGameMode {
310                        current: p.common.game_type,
311                        previous: p.common.previous_game_type.into(),
312                    },
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<TabList>,
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();
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<TabList>,
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_system_state = SystemState::<Commands>::new(world);
705                        let mut commands = commands_system_state.get_mut(world);
706                        let mut entity_commands = commands.entity(entity_id);
707                        if let Err(e) =
708                            apply_metadata(&mut entity_commands, entity_kind, packed_items)
709                        {
710                            warn!("{e}");
711                        }
712                        commands_system_state.apply(world);
713                    });
714                },
715            ));
716        });
717    }
718
719    pub fn update_attributes(&mut self, _p: &ClientboundUpdateAttributes) {
720        // debug!("Got update attributes packet {p:?}");
721    }
722
723    pub fn set_entity_motion(&mut self, p: &ClientboundSetEntityMotion) {
724        // vanilla servers use this packet for knockback, but note that the Explode
725        // packet is also sometimes used by servers for knockback
726
727        as_system::<(
728            Commands,
729            Query<(&EntityIdIndex, &WorldHolder)>,
730            EntityUpdateQuery,
731        )>(self.ecs, |(mut commands, query, entity_update_query)| {
732            let (entity_id_index, world_holder) = query.get(self.player).unwrap();
733
734            let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
735                // note that this log (and some other ones like the one in RemoveEntities)
736                // sometimes happens when killing mobs. it seems to be a vanilla bug, which is
737                // why it's a debug log instead of a warning
738                debug!(
739                    "Got set entity motion packet for unknown entity id {}",
740                    p.id
741                );
742                return;
743            };
744
745            let data = KnockbackData::Set(p.delta.to_vec3());
746
747            // this is to make sure the same entity velocity update doesn't get sent
748            // multiple times when in swarms
749            if should_apply_entity_update(
750                &mut commands,
751                &mut world_holder.partial.write(),
752                entity,
753                entity_update_query,
754            ) {
755                commands.trigger(KnockbackEvent { entity, data });
756            }
757        });
758    }
759
760    pub fn set_entity_link(&mut self, p: &ClientboundSetEntityLink) {
761        debug!("Got set entity link packet {p:?}");
762    }
763
764    pub fn initialize_border(&mut self, p: &ClientboundInitializeBorder) {
765        debug!("Got initialize border packet {p:?}");
766    }
767
768    pub fn set_time(&mut self, _p: &ClientboundSetTime) {
769        // debug!("Got set time packet {p:?}");
770    }
771
772    pub fn set_default_spawn_position(&mut self, p: &ClientboundSetDefaultSpawnPosition) {
773        debug!("Got set default spawn position packet {p:?}");
774    }
775
776    pub fn set_health(&mut self, p: &ClientboundSetHealth) {
777        debug!("Got set health packet {p:?}");
778
779        as_system::<Query<(&mut Health, &mut Hunger)>>(self.ecs, |mut query| {
780            let (mut health, mut hunger) = query.get_mut(self.player).unwrap();
781
782            **health = p.health;
783            (hunger.food, hunger.saturation) = (p.food, p.saturation);
784
785            // the `Dead` component is added by the `update_dead` system
786            // in azalea-world and then the `dead_event` system fires
787            // the Death event.
788        });
789    }
790
791    pub fn set_experience(&mut self, p: &ClientboundSetExperience) {
792        debug!("Got set experience packet {p:?}");
793
794        as_system::<Query<&mut Experience>>(self.ecs, |mut query| {
795            let mut experience = query.get_mut(self.player).unwrap();
796            experience.progress = p.experience_progress;
797            experience.level = p.experience_level;
798            experience.total = p.total_experience;
799        });
800    }
801
802    pub fn teleport_entity(&mut self, p: &ClientboundTeleportEntity) {
803        debug!("Got teleport entity packet {p:?}");
804
805        as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
806            self.ecs,
807            |(mut commands, query)| {
808                let (entity_id_index, world_holder) = query.get(self.player).unwrap();
809
810                let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
811                    warn!("Got teleport entity packet for unknown entity id {}", p.id);
812                    return;
813                };
814
815                let relative = p.relative.clone();
816                let change = p.change.clone();
817
818                commands.entity(entity).queue(RelativeEntityUpdate::new(
819                    world_holder.partial.clone(),
820                    move |entity| {
821                        let entity_id = entity.id();
822                        entity.world_scope(move |world| {
823                            let mut query =
824                                world.query::<(&mut Physics, &mut LookDirection, &mut Position)>();
825                            let (mut physics, mut look_direction, mut position) =
826                                query.get_mut(world, entity_id).unwrap();
827                            let old_position = *position;
828                            relative.apply(
829                                &change,
830                                &mut position,
831                                &mut look_direction,
832                                &mut physics,
833                            );
834                            // old_pos is set to the current position when we're teleported
835                            physics.set_old_pos(old_position);
836                        });
837                    },
838                ));
839            },
840        );
841    }
842
843    pub fn update_advancements(&mut self, p: &ClientboundUpdateAdvancements) {
844        debug!("Got update advancements packet {p:?}");
845    }
846
847    pub fn rotate_head(&mut self, _p: &ClientboundRotateHead) {}
848
849    pub fn move_entity_pos(&mut self, p: &ClientboundMoveEntityPos) {
850        as_system::<(
851            Commands,
852            Query<(&EntityIdIndex, &WorldHolder)>,
853            MoveEntityQuery,
854            EntityUpdateQuery,
855        )>(
856            self.ecs,
857            |(commands, player_query, entity_query, entity_update_query)| {
858                move_entity(
859                    self.player,
860                    commands,
861                    MoveEntity {
862                        entity_id: p.entity_id,
863                        delta: Some(p.delta),
864                        look_direction: None,
865                        on_ground: p.on_ground,
866                    },
867                    player_query,
868                    entity_query,
869                    entity_update_query,
870                );
871            },
872        );
873    }
874    pub fn move_entity_pos_rot(&mut self, p: &ClientboundMoveEntityPosRot) {
875        as_system::<(
876            Commands,
877            Query<(&EntityIdIndex, &WorldHolder)>,
878            MoveEntityQuery,
879            EntityUpdateQuery,
880        )>(
881            self.ecs,
882            |(commands, player_query, entity_query, entity_update_query)| {
883                move_entity(
884                    self.player,
885                    commands,
886                    MoveEntity {
887                        entity_id: p.entity_id,
888                        delta: Some(p.delta),
889                        look_direction: Some(p.look_direction),
890                        on_ground: p.on_ground,
891                    },
892                    player_query,
893                    entity_query,
894                    entity_update_query,
895                );
896            },
897        );
898    }
899    pub fn move_entity_rot(&mut self, p: &ClientboundMoveEntityRot) {
900        as_system::<(
901            Commands,
902            Query<(&EntityIdIndex, &WorldHolder)>,
903            MoveEntityQuery,
904            EntityUpdateQuery,
905        )>(
906            self.ecs,
907            |(commands, player_query, entity_query, entity_update_query)| {
908                move_entity(
909                    self.player,
910                    commands,
911                    MoveEntity {
912                        entity_id: p.entity_id,
913                        delta: None,
914                        look_direction: Some(p.look_direction),
915                        on_ground: p.on_ground,
916                    },
917                    player_query,
918                    entity_query,
919                    entity_update_query,
920                );
921            },
922        );
923    }
924
925    pub fn keep_alive(&mut self, p: &ClientboundKeepAlive) {
926        debug!("Got keep alive packet {p:?} for {:?}", self.player);
927
928        as_system::<Commands>(self.ecs, |mut commands| {
929            commands.trigger(KeepAliveEvent {
930                entity: self.player,
931                id: p.id,
932            });
933            commands.trigger(SendGamePacketEvent::new(
934                self.player,
935                ServerboundKeepAlive { id: p.id },
936            ));
937        });
938    }
939
940    pub fn remove_entities(&mut self, p: &ClientboundRemoveEntities) {
941        debug!("Got remove entities packet {p:?}");
942
943        as_system::<(Query<&mut EntityIdIndex>, Query<&mut LoadedBy>)>(
944            self.ecs,
945            |(mut query, mut entity_query)| {
946                let Ok(mut entity_id_index) = query.get_mut(self.player) else {
947                    warn!("our local player doesn't have EntityIdIndex");
948                    return;
949                };
950
951                for &id in &p.entity_ids {
952                    let Some(entity) = entity_id_index.remove_by_minecraft_entity(id) else {
953                        debug!(
954                            "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)."
955                        );
956                        continue;
957                    };
958                    let Ok(mut loaded_by) = entity_query.get_mut(entity) else {
959                        warn!(
960                            "tried to despawn entity {id} but it doesn't have a LoadedBy component",
961                        );
962                        continue;
963                    };
964
965                    // the `remove_despawned_entities_from_indexes` system will despawn the entity
966                    // if it's not loaded by anything anymore
967
968                    // also we can't just ecs.despawn because if we're in a swarm then the entity
969                    // might still be loaded by another client
970
971                    loaded_by.remove(&self.player);
972                }
973            },
974        );
975    }
976    pub fn player_chat(&mut self, p: &ClientboundPlayerChat) {
977        debug!("Got player chat packet {p:?}");
978
979        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
980            events.write(ChatReceivedEvent {
981                entity: self.player,
982                packet: ChatPacket::Player(Arc::new(p.clone())),
983            });
984        });
985    }
986
987    pub fn system_chat(&mut self, p: &ClientboundSystemChat) {
988        debug!("Got system chat packet {p:?}");
989
990        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
991            events.write(ChatReceivedEvent {
992                entity: self.player,
993                packet: ChatPacket::System(Arc::new(p.clone())),
994            });
995        });
996    }
997
998    pub fn disguised_chat(&mut self, p: &ClientboundDisguisedChat) {
999        debug!("Got disguised chat packet {p:?}");
1000
1001        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
1002            events.write(ChatReceivedEvent {
1003                entity: self.player,
1004                packet: ChatPacket::Disguised(Arc::new(p.clone())),
1005            });
1006        });
1007    }
1008
1009    pub fn sound(&mut self, _p: &ClientboundSound) {}
1010
1011    pub fn level_event(&mut self, p: &ClientboundLevelEvent) {
1012        debug!("Got level event packet {p:?}");
1013    }
1014
1015    pub fn block_update(&mut self, p: &ClientboundBlockUpdate) {
1016        debug!("Got block update packet {p:?}");
1017
1018        as_system::<Query<&mut QueuedServerBlockUpdates>>(self.ecs, |mut query| {
1019            let mut queued = query.get_mut(self.player).unwrap();
1020            queued.list.push((p.pos, p.block_state));
1021        });
1022    }
1023
1024    pub fn animate(&mut self, p: &ClientboundAnimate) {
1025        debug!("Got animate packet {p:?}");
1026    }
1027
1028    pub fn section_blocks_update(&mut self, p: &ClientboundSectionBlocksUpdate) {
1029        debug!("Got section blocks update packet {p:?}");
1030
1031        as_system::<Query<&mut QueuedServerBlockUpdates>>(self.ecs, |mut query| {
1032            let mut queued = query.get_mut(self.player).unwrap();
1033            for new_state in &p.states {
1034                let pos = p.section_pos + new_state.pos;
1035                queued.list.push((pos, new_state.state));
1036            }
1037        });
1038    }
1039
1040    pub fn game_event(&mut self, p: &ClientboundGameEvent) {
1041        use azalea_protocol::packets::game::c_game_event::EventType;
1042
1043        debug!("Got game event packet {p:?}");
1044
1045        #[allow(clippy::single_match)]
1046        match p.event {
1047            EventType::ChangeGameMode => {
1048                as_system::<Query<&mut LocalGameMode>>(self.ecs, |mut query| {
1049                    let mut local_game_mode = query.get_mut(self.player).unwrap();
1050                    if let Some(new_game_mode) = GameMode::from_id(p.param as u8) {
1051                        local_game_mode.current = new_game_mode;
1052                    }
1053                });
1054            }
1055            _ => {}
1056        }
1057    }
1058
1059    pub fn level_particles(&mut self, p: &ClientboundLevelParticles) {
1060        debug!("Got level particles packet {p:?}");
1061    }
1062
1063    pub fn server_data(&mut self, p: &ClientboundServerData) {
1064        debug!("Got server data packet {p:?}");
1065    }
1066
1067    pub fn set_equipment(&mut self, p: &ClientboundSetEquipment) {
1068        debug!("Got set equipment packet {p:?}");
1069    }
1070
1071    pub fn update_mob_effect(&mut self, p: &ClientboundUpdateMobEffect) {
1072        debug!("Got update mob effect packet {p:?}");
1073
1074        as_system::<(
1075            Commands,
1076            Query<(&EntityIdIndex, &WorldHolder)>,
1077            EntityUpdateQuery,
1078        )>(self.ecs, |(mut commands, query, entity_update_query)| {
1079            let (entity_id_index, world_holder) = query.get(self.player).unwrap();
1080
1081            let Some(entity) = entity_id_index.get_by_minecraft_entity(p.entity_id) else {
1082                debug!(
1083                    "Got update mob effect packet for unknown entity id {}",
1084                    p.entity_id
1085                );
1086                return;
1087            };
1088
1089            if !should_apply_entity_update(
1090                &mut commands,
1091                &mut world_holder.partial.write(),
1092                entity,
1093                entity_update_query,
1094            ) {
1095                return;
1096            }
1097
1098            commands.trigger(AddEffectEvent {
1099                entity,
1100                id: p.mob_effect,
1101                data: p.data.clone(),
1102            });
1103        });
1104    }
1105
1106    pub fn award_stats(&mut self, _p: &ClientboundAwardStats) {}
1107
1108    pub fn block_changed_ack(&mut self, p: &ClientboundBlockChangedAck) {
1109        as_system::<Query<(&WorldHolder, &mut BlockStatePredictionHandler)>>(
1110            self.ecs,
1111            |mut query| {
1112                let (local_player, mut prediction_handler) = query.get_mut(self.player).unwrap();
1113                let world = local_player.shared.read();
1114                prediction_handler.end_prediction_up_to(p.seq, &world);
1115            },
1116        );
1117    }
1118
1119    pub fn block_destruction(&mut self, _p: &ClientboundBlockDestruction) {}
1120
1121    pub fn block_entity_data(&mut self, _p: &ClientboundBlockEntityData) {}
1122
1123    pub fn block_event(&mut self, p: &ClientboundBlockEvent) {
1124        debug!("Got block event packet {p:?}");
1125    }
1126
1127    pub fn boss_event(&mut self, _p: &ClientboundBossEvent) {}
1128
1129    pub fn command_suggestions(&mut self, _p: &ClientboundCommandSuggestions) {}
1130
1131    pub fn container_set_content(&mut self, p: &ClientboundContainerSetContent) {
1132        debug!("Got container set content packet {p:?}");
1133
1134        as_system::<(Commands, Query<&mut Inventory>)>(self.ecs, |(mut commands, mut query)| {
1135            let mut inventory = query.get_mut(self.player).unwrap();
1136
1137            // container id 0 is always the player's inventory
1138            if p.container_id == 0 {
1139                // this is just so it has the same type as the `else` block
1140                for (i, slot) in p.items.iter().enumerate() {
1141                    if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) {
1142                        *slot_mut = slot.clone();
1143                    }
1144                }
1145            } else {
1146                commands.trigger(SetContainerContentEvent {
1147                    entity: self.player,
1148                    slots: p.items.clone(),
1149                    container_id: p.container_id,
1150                });
1151            }
1152        });
1153    }
1154
1155    pub fn container_set_data(&mut self, p: &ClientboundContainerSetData) {
1156        debug!("Got container set data packet {p:?}");
1157
1158        // TODO: handle ContainerSetData packet
1159        // this is used for various things like the furnace progress
1160        // bar
1161        // see https://minecraft.wiki/w/Java_Edition_protocol/Packets#Set_Container_Property
1162
1163        // as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
1164        //     let inventory = query.get_mut(self.player).unwrap();
1165        // });
1166    }
1167
1168    pub fn container_set_slot(&mut self, p: &ClientboundContainerSetSlot) {
1169        debug!("Got container set slot packet {p:?}");
1170
1171        as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
1172            let mut inventory = query.get_mut(self.player).unwrap();
1173
1174            if p.container_id == -1 {
1175                // -1 means carried item
1176                inventory.carried = p.item_stack.clone();
1177            } else if p.container_id == -2 {
1178                if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
1179                    *slot = p.item_stack.clone();
1180                }
1181            } else {
1182                let is_creative_mode_and_inventory_closed = false;
1183                // technically minecraft has slightly different behavior here if you're in
1184                // creative mode and have your inventory open
1185                if p.container_id == 0 && azalea_inventory::Player::is_hotbar_slot(p.slot.into()) {
1186                    // minecraft also sets a "pop time" here which is used for an animation
1187                    // but that's not really necessary
1188                    if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
1189                        *slot = p.item_stack.clone();
1190                    }
1191                } else if p.container_id == inventory.id
1192                    && (p.container_id != 0 || !is_creative_mode_and_inventory_closed)
1193                {
1194                    // var2.containerMenu.setItem(var4, var1.getStateId(), var3);
1195                    if let Some(slot) = inventory.menu_mut().slot_mut(p.slot.into()) {
1196                        *slot = p.item_stack.clone();
1197                        inventory.state_id = p.state_id;
1198                    }
1199                }
1200            }
1201        });
1202    }
1203
1204    pub fn container_close(&mut self, p: &ClientboundContainerClose) {
1205        // there's a container_id field in the packet, but minecraft doesn't actually
1206        // check it
1207
1208        debug!("Got container close packet {p:?}");
1209
1210        as_system::<Commands>(self.ecs, |mut commands| {
1211            commands.trigger(ClientsideCloseContainerEvent {
1212                entity: self.player,
1213            });
1214        });
1215    }
1216
1217    pub fn cooldown(&mut self, _p: &ClientboundCooldown) {}
1218
1219    pub fn custom_chat_completions(&mut self, _p: &ClientboundCustomChatCompletions) {}
1220
1221    pub fn delete_chat(&mut self, _p: &ClientboundDeleteChat) {}
1222
1223    pub fn explode(&mut self, p: &ClientboundExplode) {
1224        debug!("Got explode packet {p:?}");
1225
1226        as_system::<Commands>(self.ecs, |mut knockback_events| {
1227            if let Some(knockback) = p.player_knockback {
1228                knockback_events.trigger(KnockbackEvent {
1229                    entity: self.player,
1230                    data: KnockbackData::Add(knockback),
1231                });
1232            }
1233        });
1234    }
1235
1236    pub fn forget_level_chunk(&mut self, p: &ClientboundForgetLevelChunk) {
1237        debug!("Got forget level chunk packet {p:?}");
1238
1239        as_system::<Query<&WorldHolder>>(self.ecs, |mut query| {
1240            let local_player = query.get_mut(self.player).unwrap();
1241
1242            let mut partial_world = local_player.partial.write();
1243
1244            partial_world.chunks.limited_set(&p.pos, None);
1245        });
1246    }
1247
1248    pub fn mount_screen_open(&mut self, _p: &ClientboundMountScreenOpen) {}
1249
1250    pub fn map_item_data(&mut self, _p: &ClientboundMapItemData) {}
1251
1252    pub fn merchant_offers(&mut self, _p: &ClientboundMerchantOffers) {}
1253
1254    pub fn move_vehicle(&mut self, _p: &ClientboundMoveVehicle) {}
1255
1256    pub fn open_book(&mut self, _p: &ClientboundOpenBook) {}
1257
1258    pub fn open_screen(&mut self, p: &ClientboundOpenScreen) {
1259        debug!("Got open screen packet {p:?}");
1260
1261        as_system::<Commands>(self.ecs, |mut commands| {
1262            commands.trigger(MenuOpenedEvent {
1263                entity: self.player,
1264                window_id: p.container_id,
1265                menu_type: p.menu_type,
1266                title: p.title.to_owned(),
1267            });
1268        });
1269    }
1270
1271    pub fn open_sign_editor(&mut self, _p: &ClientboundOpenSignEditor) {}
1272
1273    pub fn ping(&mut self, p: &ClientboundPing) {
1274        debug!("Got ping packet {p:?}");
1275
1276        as_system::<Commands>(self.ecs, |mut commands| {
1277            commands.trigger(GamePingEvent {
1278                entity: self.player,
1279                packet: p.clone(),
1280            });
1281        });
1282    }
1283
1284    pub fn place_ghost_recipe(&mut self, _p: &ClientboundPlaceGhostRecipe) {}
1285
1286    pub fn player_combat_end(&mut self, _p: &ClientboundPlayerCombatEnd) {}
1287
1288    pub fn player_combat_enter(&mut self, _p: &ClientboundPlayerCombatEnter) {}
1289
1290    pub fn player_combat_kill(&mut self, p: &ClientboundPlayerCombatKill) {
1291        debug!("Got player kill packet {p:?}");
1292
1293        as_system::<(
1294            Commands,
1295            Query<(&MinecraftEntityId, Option<&Dead>)>,
1296            MessageWriter<_>,
1297        )>(self.ecs, |(mut commands, mut query, mut events)| {
1298            let (entity_id, dead) = query.get_mut(self.player).unwrap();
1299
1300            if *entity_id == p.player_id && dead.is_none() {
1301                commands.entity(self.player).insert(Dead);
1302                events.write(DeathEvent {
1303                    entity: self.player,
1304                    packet: Some(p.clone()),
1305                });
1306            }
1307        });
1308    }
1309
1310    pub fn player_look_at(&mut self, _p: &ClientboundPlayerLookAt) {}
1311
1312    pub fn remove_mob_effect(&mut self, p: &ClientboundRemoveMobEffect) {
1313        debug!("Got remove mob effect packet {p:?}");
1314
1315        as_system::<(
1316            Commands,
1317            Query<(&EntityIdIndex, &WorldHolder)>,
1318            EntityUpdateQuery,
1319        )>(self.ecs, |(mut commands, query, entity_update_query)| {
1320            let (entity_id_index, world_holder) = query.get(self.player).unwrap();
1321
1322            let Some(entity) = entity_id_index.get_by_minecraft_entity(p.entity_id) else {
1323                debug!(
1324                    "Got remove mob effect packet for unknown entity id {}",
1325                    p.entity_id
1326                );
1327                return;
1328            };
1329
1330            if !should_apply_entity_update(
1331                &mut commands,
1332                &mut world_holder.partial.write(),
1333                entity,
1334                entity_update_query,
1335            ) {
1336                return;
1337            }
1338
1339            commands.trigger(RemoveEffectsEvent {
1340                entity,
1341                effects: vec![p.effect],
1342            });
1343        });
1344    }
1345
1346    pub fn resource_pack_push(&mut self, p: &ClientboundResourcePackPush) {
1347        debug!("Got resource pack packet {p:?}");
1348
1349        as_system::<MessageWriter<_>>(self.ecs, |mut events| {
1350            events.write(ResourcePackEvent {
1351                entity: self.player,
1352                id: p.id,
1353                url: p.url.to_owned(),
1354                hash: p.hash.to_owned(),
1355                required: p.required,
1356                prompt: p.prompt.to_owned(),
1357            });
1358        });
1359    }
1360
1361    pub fn resource_pack_pop(&mut self, _p: &ClientboundResourcePackPop) {}
1362
1363    pub fn respawn(&mut self, p: &ClientboundRespawn) {
1364        debug!("Got respawn packet {p:?}");
1365
1366        as_system::<(
1367            Commands,
1368            Query<
1369                (
1370                    &mut WorldHolder,
1371                    &GameProfileComponent,
1372                    &ClientInformation,
1373                    Option<&mut WorldName>,
1374                ),
1375                With<LocalEntity>,
1376            >,
1377            MessageWriter<_>,
1378            ResMut<Worlds>,
1379            Query<&mut LoadedBy, Without<LocalEntity>>,
1380        )>(
1381            self.ecs,
1382            |(mut commands, mut query, mut events, mut worlds, mut loaded_by_query)| {
1383                let Ok((mut world_holder, game_profile, client_information, world_name)) =
1384                    query.get_mut(self.player)
1385                else {
1386                    warn!("Got respawn packet but player doesn't have the required components");
1387                    return;
1388                };
1389
1390                let new_world_name = WorldName(p.common.dimension.clone());
1391
1392                if let Some(mut world_name) = world_name {
1393                    *world_name = new_world_name.clone();
1394                } else {
1395                    commands.entity(self.player).insert(new_world_name.clone());
1396                }
1397
1398                let weak_world;
1399                {
1400                    let client_registries = &world_holder.shared.read().registries;
1401                    let Some((_dimension_type, dimension_data)) =
1402                        p.common.dimension_type(client_registries)
1403                    else {
1404                        return;
1405                    };
1406
1407                    // add this world to the `worlds` (or don't if it's already there)
1408                    weak_world = worlds.get_or_insert(
1409                        new_world_name.clone(),
1410                        dimension_data.height,
1411                        dimension_data.min_y,
1412                        client_registries,
1413                    );
1414                    events.write(WorldLoadedEvent {
1415                        entity: self.player,
1416                        name: new_world_name.clone(),
1417                        world: Arc::downgrade(&weak_world),
1418                    });
1419                }
1420
1421                // set the partial world to an empty world (when we add chunks or entities,
1422                // those will be in the `worlds`)
1423
1424                *world_holder.partial.write() = PartialWorld::new(
1425                    azalea_world::chunk::calculate_chunk_storage_range(
1426                        client_information.view_distance.into(),
1427                    ),
1428                    Some(self.player),
1429                );
1430                world_holder.shared = weak_world;
1431
1432                // every entity is now unloaded by this player
1433                for mut loaded_by in &mut loaded_by_query.iter_mut() {
1434                    loaded_by.remove(&self.player);
1435                }
1436
1437                // this resets a bunch of our components like physics and stuff
1438                let entity_bundle = EntityBundle::new(
1439                    game_profile.uuid,
1440                    Vec3::ZERO,
1441                    EntityKind::Player,
1442                    new_world_name,
1443                );
1444                // update the local gamemode and metadata things
1445                commands.entity(self.player).insert((
1446                    LocalGameMode {
1447                        current: p.common.game_type,
1448                        previous: p.common.previous_game_type.into(),
1449                    },
1450                    entity_bundle,
1451                ));
1452
1453                commands
1454                    .entity(self.player)
1455                    .remove::<(Dead, HasClientLoaded)>();
1456            },
1457        )
1458    }
1459
1460    pub fn start_configuration(&mut self, _p: &ClientboundStartConfiguration) {
1461        debug!("Got start configuration packet");
1462
1463        as_system::<(Commands, Query<(&mut RawConnection, &mut WorldHolder)>)>(
1464            self.ecs,
1465            |(mut commands, mut query)| {
1466                let Some((mut raw_conn, mut world_holder)) = query.get_mut(self.player).ok() else {
1467                    warn!("Got start configuration packet but player doesn't have a RawConnection");
1468                    return;
1469                };
1470                raw_conn.state = ConnectionProtocol::Configuration;
1471
1472                commands.trigger(SendGamePacketEvent::new(
1473                    self.player,
1474                    ServerboundConfigurationAcknowledged,
1475                ));
1476
1477                commands
1478                    .entity(self.player)
1479                    .insert(crate::client::InConfigState)
1480                    .remove::<crate::JoinedClientBundle>()
1481                    .remove::<EntityBundle>();
1482
1483                world_holder.reset();
1484            },
1485        );
1486    }
1487
1488    pub fn entity_position_sync(&mut self, p: &ClientboundEntityPositionSync) {
1489        as_system::<(
1490            Commands,
1491            Query<(&EntityIdIndex, &WorldHolder)>,
1492            Query<(
1493                &mut Physics,
1494                &mut Position,
1495                &mut LookDirection,
1496                Option<&LocalEntity>,
1497            )>,
1498            EntityUpdateQuery,
1499        )>(
1500            self.ecs,
1501            |(mut commands, mut query, mut entity_query, entity_update_query)| {
1502                let (entity_id_index, world_holder) = query.get_mut(self.player).unwrap();
1503
1504                let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
1505                    debug!("Got teleport entity packet for unknown entity id {}", p.id);
1506                    return;
1507                };
1508
1509                let new_position = p.values.pos;
1510                let new_on_ground = p.on_ground;
1511                let new_look_direction = p.values.look_direction;
1512
1513                if !should_apply_entity_update(
1514                    &mut commands,
1515                    &mut world_holder.partial.write(),
1516                    entity,
1517                    entity_update_query,
1518                ) {
1519                    return;
1520                }
1521
1522                let Ok((mut physics, mut position, mut look_direction, local_entity)) =
1523                    entity_query.get_mut(entity)
1524                else {
1525                    return;
1526                };
1527
1528                physics.vec_delta_codec.set_base(new_position);
1529
1530                let is_client_authoritative = local_entity.is_some();
1531                if is_client_authoritative {
1532                    debug!("Ignoring entity position sync packet for local player");
1533                    return;
1534                }
1535
1536                physics.set_on_ground(new_on_ground);
1537
1538                if **position != new_position {
1539                    **position = new_position;
1540                }
1541
1542                if *look_direction != new_look_direction {
1543                    *look_direction = new_look_direction;
1544                }
1545            },
1546        );
1547    }
1548
1549    pub fn select_advancements_tab(&mut self, _p: &ClientboundSelectAdvancementsTab) {}
1550    pub fn set_action_bar_text(&mut self, _p: &ClientboundSetActionBarText) {}
1551    pub fn set_border_center(&mut self, _p: &ClientboundSetBorderCenter) {}
1552    pub fn set_border_lerp_size(&mut self, _p: &ClientboundSetBorderLerpSize) {}
1553    pub fn set_border_size(&mut self, _p: &ClientboundSetBorderSize) {}
1554    pub fn set_border_warning_delay(&mut self, _p: &ClientboundSetBorderWarningDelay) {}
1555    pub fn set_border_warning_distance(&mut self, _p: &ClientboundSetBorderWarningDistance) {}
1556    pub fn set_camera(&mut self, _p: &ClientboundSetCamera) {}
1557    pub fn set_display_objective(&mut self, _p: &ClientboundSetDisplayObjective) {}
1558    pub fn set_objective(&mut self, _p: &ClientboundSetObjective) {}
1559    pub fn set_passengers(&mut self, _p: &ClientboundSetPassengers) {}
1560    pub fn set_player_team(&mut self, p: &ClientboundSetPlayerTeam) {
1561        debug!("Got set player team packet {p:?}");
1562    }
1563    pub fn set_score(&mut self, _p: &ClientboundSetScore) {}
1564    pub fn set_simulation_distance(&mut self, _p: &ClientboundSetSimulationDistance) {}
1565    pub fn set_subtitle_text(&mut self, _p: &ClientboundSetSubtitleText) {}
1566    pub fn set_title_text(&mut self, _p: &ClientboundSetTitleText) {}
1567    pub fn set_titles_animation(&mut self, _p: &ClientboundSetTitlesAnimation) {}
1568    pub fn clear_titles(&mut self, _p: &ClientboundClearTitles) {}
1569    pub fn sound_entity(&mut self, _p: &ClientboundSoundEntity) {}
1570    pub fn stop_sound(&mut self, _p: &ClientboundStopSound) {}
1571    pub fn tab_list(&mut self, _p: &ClientboundTabList) {}
1572    pub fn tag_query(&mut self, _p: &ClientboundTagQuery) {}
1573    pub fn take_item_entity(&mut self, _p: &ClientboundTakeItemEntity) {}
1574    pub fn bundle_delimiter(&mut self, _p: &ClientboundBundleDelimiter) {}
1575    pub fn damage_event(&mut self, _p: &ClientboundDamageEvent) {}
1576    pub fn hurt_animation(&mut self, _p: &ClientboundHurtAnimation) {}
1577    pub fn ticking_state(&mut self, _p: &ClientboundTickingState) {}
1578    pub fn ticking_step(&mut self, _p: &ClientboundTickingStep) {}
1579    pub fn reset_score(&mut self, _p: &ClientboundResetScore) {}
1580    pub fn cookie_request(&mut self, p: &ClientboundCookieRequest) {
1581        debug!("Got cookie request packet {p:?}");
1582        as_system::<Commands>(self.ecs, |mut commands| {
1583            commands.trigger(RequestCookieEvent {
1584                entity: self.player,
1585                key: p.key.clone(),
1586            });
1587        });
1588    }
1589    pub fn store_cookie(&mut self, p: &ClientboundStoreCookie) {
1590        debug!("Got store cookie packet {p:?}");
1591        as_system::<Commands>(self.ecs, |mut commands| {
1592            commands.trigger(StoreCookieEvent {
1593                entity: self.player,
1594                key: p.key.clone(),
1595                payload: p.payload.clone(),
1596            });
1597        });
1598    }
1599    pub fn debug_sample(&mut self, _p: &ClientboundDebugSample) {}
1600    pub fn pong_response(&mut self, _p: &ClientboundPongResponse) {}
1601    pub fn transfer(&mut self, _p: &ClientboundTransfer) {}
1602    pub fn move_minecart_along_track(&mut self, _p: &ClientboundMoveMinecartAlongTrack) {}
1603    pub fn set_held_slot(&mut self, p: &ClientboundSetHeldSlot) {
1604        debug!("Got set held slot packet {p:?}");
1605
1606        as_system::<Query<&mut Inventory>>(self.ecs, |mut query| {
1607            let mut inventory = query.get_mut(self.player).unwrap();
1608            if p.slot <= 8 {
1609                inventory.selected_hotbar_slot = p.slot as u8;
1610            }
1611        });
1612    }
1613    pub fn set_player_inventory(&mut self, _p: &ClientboundSetPlayerInventory) {}
1614    pub fn projectile_power(&mut self, _p: &ClientboundProjectilePower) {}
1615    pub fn custom_report_details(&mut self, _p: &ClientboundCustomReportDetails) {}
1616    pub fn server_links(&mut self, _p: &ClientboundServerLinks) {}
1617    pub fn player_rotation(&mut self, _p: &ClientboundPlayerRotation) {}
1618    pub fn recipe_book_add(&mut self, _p: &ClientboundRecipeBookAdd) {}
1619    pub fn recipe_book_remove(&mut self, _p: &ClientboundRecipeBookRemove) {}
1620    pub fn recipe_book_settings(&mut self, _p: &ClientboundRecipeBookSettings) {}
1621    pub fn test_instance_block_status(&mut self, _p: &ClientboundTestInstanceBlockStatus) {}
1622    pub fn waypoint(&mut self, _p: &ClientboundWaypoint) {}
1623
1624    pub fn clear_dialog(&mut self, p: &ClientboundClearDialog) {
1625        debug!("Got clear dialog packet {p:?}");
1626    }
1627    pub fn show_dialog(&mut self, p: &ClientboundShowDialog) {
1628        debug!("Got show dialog packet {p:?}");
1629    }
1630
1631    pub fn debug_block_value(&mut self, p: &ClientboundDebugBlockValue) {
1632        debug!("Got debug block value packet {p:?}");
1633    }
1634    pub fn debug_chunk_value(&mut self, p: &ClientboundDebugChunkValue) {
1635        debug!("Got debug chunk value packet {p:?}");
1636    }
1637    pub fn debug_entity_value(&mut self, p: &ClientboundDebugEntityValue) {
1638        debug!("Got debug entity value packet {p:?}");
1639    }
1640
1641    pub fn debug_event(&mut self, p: &ClientboundDebugEvent) {
1642        debug!("Got debug event packet {p:?}");
1643    }
1644    pub fn game_test_highlight_pos(&mut self, p: &ClientboundGameTestHighlightPos) {
1645        debug!("Got game test highlight pos packet {p:?}");
1646    }
1647}
1648
1649struct MoveEntity {
1650    pub entity_id: MinecraftEntityId,
1651    pub delta: Option<PositionDelta8>,
1652    pub look_direction: Option<CompactLookDirection>,
1653    pub on_ground: bool,
1654}
1655
1656type MoveEntityQuery<'world, 'state, 'a> =
1657    Query<'world, 'state, (&'a mut Physics, &'a mut Position, &'a mut LookDirection)>;
1658
1659fn move_entity(
1660    player_entity: Entity,
1661    mut commands: Commands,
1662    p: MoveEntity,
1663    player_query: Query<(&EntityIdIndex, &WorldHolder)>,
1664    mut entity_query: MoveEntityQuery,
1665    entity_update_query: EntityUpdateQuery,
1666) {
1667    let (entity_id_index, world_holder) = player_query.get(player_entity).unwrap();
1668
1669    let entity_id = p.entity_id;
1670    let entity = entity_id_index.get_by_minecraft_entity(entity_id);
1671
1672    let Some(entity) = entity else {
1673        // often triggered by hypixel :(
1674        debug!("Got move entity packet for unknown entity id {entity_id}");
1675        return;
1676    };
1677
1678    let Ok((mut physics, mut position, mut look_direction)) = entity_query.get_mut(entity) else {
1679        debug!("Got move entity packet for entity with missing components {entity_id}");
1680        return;
1681    };
1682
1683    if !should_apply_entity_update(
1684        &mut commands,
1685        &mut world_holder.partial.write(),
1686        entity,
1687        entity_update_query,
1688    ) {
1689        return;
1690    }
1691
1692    if let Some(new_delta) = p.delta {
1693        let new_position = physics.vec_delta_codec.decode(&new_delta);
1694        physics.vec_delta_codec.set_base(new_position);
1695
1696        if new_position != **position {
1697            **position = new_position;
1698        }
1699    }
1700
1701    if let Some(new_look_direction) = p.look_direction {
1702        let new_look_direction = new_look_direction.into();
1703        if new_look_direction != *look_direction {
1704            *look_direction = new_look_direction;
1705        }
1706    }
1707
1708    physics.set_on_ground(p.on_ground);
1709}