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