azalea_client/plugins/packet/game/
mod.rs

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