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