diff options
| -rw-r--r-- | CHANGELOG.md | 175 | ||||
| -rw-r--r-- | azalea-client/src/plugins/inventory.rs | 151 | ||||
| -rw-r--r-- | azalea-client/src/plugins/packet/game/mod.rs | 47 |
3 files changed, 193 insertions, 180 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c439b15b..88b6b056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,107 +10,108 @@ is breaking anyways, semantic versioning is not followed. ### Added -- Sneaking/crouching. -- `HitResult` now contains the entity that's being looked at. -- A `QueuedServerBlockUpdates` component that keeps track of block updates per `Update`. -- Local clients now have a `TicksConnected` component. (@Kumpelinus) -- There is now a `azalea_inventory::default_components::get_default_component` function to get the default value of a component for a registry item. -- `ItemStack` now has a `get_component` function that supports default components. -- `Client::nearest_entity_by`. -- `BitSet::len`, `BitSet::get`, `BitSet::iter_ones`. -- All packets are now `PartialEq`. +- Sneaking/crouching. +- `HitResult` now contains the entity that's being looked at. +- A `QueuedServerBlockUpdates` component that keeps track of block updates per `Update`. +- Local clients now have a `TicksConnected` component. (@Kumpelinus) +- There is now a `azalea_inventory::default_components::get_default_component` function to get the default value of a component for a registry item. +- `ItemStack` now has a `get_component` function that supports default components. +- `Client::nearest_entity_by`. +- `BitSet::len`, `BitSet::get`, `BitSet::iter_ones`. +- All packets are now `PartialEq`. ### Changed -- Update to Minecraft 1.21.8. -- Renamed `azalea_entity::EntityKind` to `EntityKindComponent` to disambiguate with `azalea_registry::EntityKind`. -- Moved functions and types related to hit results from `azalea::interact` to `azalea::interact::pick`. -- `Client::attack` now takes `Entity` instead of `MinecraftEntityId`. -- `ItemStackData::components` was renamed to `component_patch`. -- The fields in `LookDirection` have been replaced with getters. -- Renamed `Client::entity_by` to `any_entity_by`, and `Client::entities_by` to `nearest_entities_by`. -- `EyeHeight` was moved into `EntityDimensions`, and `EntityDimensions` is now its own component. -- Replaced `start_goto_without_mining` with `start_goto_with_opts`. -- Rename `send_chat_packet` / `send_command_packet` to `write_chat_packet` / `write_command_packet` (for consistency with `write_packet`). -- Split `ClientInformation` handling out of `BrandPlugin` to `ClientInformationPlugin`. -- `ClientBuilder::start` and `SwarmBuilder::start` now return a `Result<AppExit>` instead of `Result<!>`. +- Update to Minecraft 1.21.8. +- Renamed `azalea_entity::EntityKind` to `EntityKindComponent` to disambiguate with `azalea_registry::EntityKind`. +- Moved functions and types related to hit results from `azalea::interact` to `azalea::interact::pick`. +- `Client::attack` now takes `Entity` instead of `MinecraftEntityId`. +- `ItemStackData::components` was renamed to `component_patch`. +- The fields in `LookDirection` have been replaced with getters. +- Renamed `Client::entity_by` to `any_entity_by`, and `Client::entities_by` to `nearest_entities_by`. +- `EyeHeight` was moved into `EntityDimensions`, and `EntityDimensions` is now its own component. +- Replaced `start_goto_without_mining` with `start_goto_with_opts`. +- Rename `send_chat_packet` / `send_command_packet` to `write_chat_packet` / `write_command_packet` (for consistency with `write_packet`). +- Split `ClientInformation` handling out of `BrandPlugin` to `ClientInformationPlugin`. +- `ClientBuilder::start` and `SwarmBuilder::start` now return a `Result<AppExit>` instead of `Result<!>`. +- `ClientsideCloseContainerEvent`, `MenuOpenedEvent`, and `CloseContainerEvent` are now triggers instead of events. ### Fixed -- Fix packet order for loading (`PlayerLoaded`/`MovePlayerPos`) and sprinting (`PlayerInput`/`PlayerCommand`). -- Clients no longer send invalid look directions if the server teleports us with one. -- Look directions are now rounded based on the default Minecraft sensitivity, which may help avoid flagging anticheats. -- Movement code was updated with the changes from 1.21.5, so it no longer flags Grim. -- Clients can no longer sprint if their food level is too low. -- `azalea-chat` now handles arrays of integers in the `with` field. (@qwqawawow) -- `azalea-chat` no longer incorrectly persists styles of components in the "extra" field. -- Inventories now use the correct max stack sizes. -- Clients now send the correct data component checksums when interacting with items. -- Fix parsing some metadata fields of Display entities. -- Mining blocks in creative mode now works. (@qwqawawow) -- Improved matchers on the `ChatPacket` functions to work on more servers. (@ShayBox) -- Bevy's `AppExit` Event is now handled by Azalea's ECS runner. +- Fix packet order for loading (`PlayerLoaded`/`MovePlayerPos`) and sprinting (`PlayerInput`/`PlayerCommand`). +- Clients no longer send invalid look directions if the server teleports us with one. +- Look directions are now rounded based on the default Minecraft sensitivity, which may help avoid flagging anticheats. +- Movement code was updated with the changes from 1.21.5, so it no longer flags Grim. +- Clients can no longer sprint if their food level is too low. +- `azalea-chat` now handles arrays of integers in the `with` field. (@qwqawawow) +- `azalea-chat` no longer incorrectly persists styles of components in the "extra" field. +- Inventories now use the correct max stack sizes. +- Clients now send the correct data component checksums when interacting with items. +- Fix parsing some metadata fields of Display entities. +- Mining blocks in creative mode now works. (@qwqawawow) +- Improved matchers on the `ChatPacket` functions to work on more servers. (@ShayBox) +- Bevy's `AppExit` Event is now handled by Azalea's ECS runner. ## [0.13.0+mc1.21.5] - 2025-06-15 ### Added -- This changelog. To see changes before this update, look at the git commits. -- azalea and azalea-client now have a `packet-event` feature, which can be disabled for efficiency if you're not using `Event::Packet`. -- `StartJoinServerEvent` can now be used to join servers exclusively from the ECS without a Tokio runtime. -- Add `FormattedText::to_html` and `FormattedText::to_custom_format`. (@Kumpelinus) -- Non-standard legacy hex colors like `§#ff0000` are now supported in azalea-chat. -- Chat signing. -- Add auto-reconnecting which is enabled by default. -- `ClientBuilder` and `SwarmBuilder` are now Send. -- Add `Client::start_use_item`. -- The pathfinder no longer avoids slabs, stairs, and dirt path blocks. -- The pathfinder now immediately recalculates if blocks are placed in its path. -- Bots that use custom pathfinder moves can now keep arbitrary persistent state by using the `CustomPathfinderState` component and `PathfinderCtx::custom_state`. -- The reach distance for the pathfinder `ReachBlockPosGoal` is now configurable. (@x-osc) -- There is now a `retry_on_no_path` option in `GotoEvent` that can be set to false to make the pathfinder give up if no path could be found. -- azalea-brigadier now supports suggestions, command contexts, result consumers, and returning errors with `ArgumentBuilder::executes_result`. -- Proper support for getting biomes at coordinates. -- Add a new `Client::entities_by` which sorts entities that match a criteria by their distance to the client. -- New client event `Event::ReceiveChunk`. -- Several new functions for interacting with inventories (`Client::get_inventory`, `get_held_item`, `ContainerHandleRef::left_click`, `shift_click`, `right_click`, `slots`). -- Add `Client::mine_with_auto_tool`. -- Add `Client::set_selected_hotbar_slot` and `Client::selected_hotbar_slot`. -- Add `Client::attack_cooldown_remaining_ticks` to complement `has_attack_cooldown`. -- Add `BlockPos::length`, `distance_to`, and `center_bottom`. +- This changelog. To see changes before this update, look at the git commits. +- azalea and azalea-client now have a `packet-event` feature, which can be disabled for efficiency if you're not using `Event::Packet`. +- `StartJoinServerEvent` can now be used to join servers exclusively from the ECS without a Tokio runtime. +- Add `FormattedText::to_html` and `FormattedText::to_custom_format`. (@Kumpelinus) +- Non-standard legacy hex colors like `§#ff0000` are now supported in azalea-chat. +- Chat signing. +- Add auto-reconnecting which is enabled by default. +- `ClientBuilder` and `SwarmBuilder` are now Send. +- Add `Client::start_use_item`. +- The pathfinder no longer avoids slabs, stairs, and dirt path blocks. +- The pathfinder now immediately recalculates if blocks are placed in its path. +- Bots that use custom pathfinder moves can now keep arbitrary persistent state by using the `CustomPathfinderState` component and `PathfinderCtx::custom_state`. +- The reach distance for the pathfinder `ReachBlockPosGoal` is now configurable. (@x-osc) +- There is now a `retry_on_no_path` option in `GotoEvent` that can be set to false to make the pathfinder give up if no path could be found. +- azalea-brigadier now supports suggestions, command contexts, result consumers, and returning errors with `ArgumentBuilder::executes_result`. +- Proper support for getting biomes at coordinates. +- Add a new `Client::entities_by` which sorts entities that match a criteria by their distance to the client. +- New client event `Event::ReceiveChunk`. +- Several new functions for interacting with inventories (`Client::get_inventory`, `get_held_item`, `ContainerHandleRef::left_click`, `shift_click`, `right_click`, `slots`). +- Add `Client::mine_with_auto_tool`. +- Add `Client::set_selected_hotbar_slot` and `Client::selected_hotbar_slot`. +- Add `Client::attack_cooldown_remaining_ticks` to complement `has_attack_cooldown`. +- Add `BlockPos::length`, `distance_to`, and `center_bottom`. ### Changed -- `Client::goto` is now async and completes when the client reaches its destination. `Client::start_goto` should be used if the old behavior is desired. -- The `BlockState::id` field is now private, use `.id()` instead. -- Update to [Bevy 0.16](https://bevyengine.org/news/bevy-0-16/). -- Rename `InstanceContainer::insert` to `get_or_insert`. -- Replace `BlockInteractEvent` with the more general-purpose `StartUseItemEvent`. -- Replace `wait_one_tick` and `wait_one_update` with `wait_ticks` and `wait_updates`. -- Functions that took `&Vec3` or `&BlockPos` as arguments now only take them as owned types. -- Rename `azalea_block::Block` to `BlockTrait` to disambiguate with `azalea_registry::Block`. -- `GotoEvent` is now non-enhaustive and should instead be constructed by calling its methods. +- `Client::goto` is now async and completes when the client reaches its destination. `Client::start_goto` should be used if the old behavior is desired. +- The `BlockState::id` field is now private, use `.id()` instead. +- Update to [Bevy 0.16](https://bevyengine.org/news/bevy-0-16/). +- Rename `InstanceContainer::insert` to `get_or_insert`. +- Replace `BlockInteractEvent` with the more general-purpose `StartUseItemEvent`. +- Replace `wait_one_tick` and `wait_one_update` with `wait_ticks` and `wait_updates`. +- Functions that took `&Vec3` or `&BlockPos` as arguments now only take them as owned types. +- Rename `azalea_block::Block` to `BlockTrait` to disambiguate with `azalea_registry::Block`. +- `GotoEvent` is now non-enhaustive and should instead be constructed by calling its methods. ### Fixed -- Clients now validate incoming packets using the correct `MAXIMUM_UNCOMPRESSED_LENGTH` value. -- Several protocol fixes, including for `ClientboundSetPlayerTeam` and a few data components. -- No more chunk errors when the client joins another world with the same name but different height. -- Update the `InstanceName` component correctly when we receive a respawn or second login packet. -- azalea-chat now handles legacy color codes correctly when parsing from NBT. -- Send the correct UUID to servers in `ClientboundHello` when we're joining in offline-mode. -- Block shapes and some properties were using data from `1.20.3-pre4` due to using an old data generator (Pixlyzer), which has now been replaced with the data generator from [Pumpkin](https://github.com/Pumpkin-MC/Extractor). -- When patching the path, don't replace the move we're currently executing. -- The correct sequence number is now sent when interacting with blocks. -- Mining is now generally more reliable and doesn't flag Grim. -- Ghost blocks are now handled correctly due to implementing `ClientboundBlockChangedAck`. -- Player eye height was wrong due to being calculated from height instead of being a special case (was 1.53, should've been 1.62). -- The player inventory is now correctly updated when we close a container. -- Inventory interactions are now predicted on the client-side again, and the remaining click operations were implemented. -- `Client::open_container_at` now waits up to 10 ticks for the block to exist if you try to click air. -- Wrong physics collision code resulted in `HitResult` sometimes containing the wrong coordinates and `inside` value. -- Fix the client being unresponsive for a few seconds after joining due to not sending `ServerboundPlayerLoaded`. -- Fix panic when a client received `ClientboundAddEntity` and `ClientboundStartConfiguration` at the same time. -- Fix panic due to `ClientInformation` being inserted too late. -- `ClientboundTeleportEntity` did not handle relative teleports correctly. -- Pathfinder now gets stuck in water less by automatically trying to jump if it's in water. +- Clients now validate incoming packets using the correct `MAXIMUM_UNCOMPRESSED_LENGTH` value. +- Several protocol fixes, including for `ClientboundSetPlayerTeam` and a few data components. +- No more chunk errors when the client joins another world with the same name but different height. +- Update the `InstanceName` component correctly when we receive a respawn or second login packet. +- azalea-chat now handles legacy color codes correctly when parsing from NBT. +- Send the correct UUID to servers in `ClientboundHello` when we're joining in offline-mode. +- Block shapes and some properties were using data from `1.20.3-pre4` due to using an old data generator (Pixlyzer), which has now been replaced with the data generator from [Pumpkin](https://github.com/Pumpkin-MC/Extractor). +- When patching the path, don't replace the move we're currently executing. +- The correct sequence number is now sent when interacting with blocks. +- Mining is now generally more reliable and doesn't flag Grim. +- Ghost blocks are now handled correctly due to implementing `ClientboundBlockChangedAck`. +- Player eye height was wrong due to being calculated from height instead of being a special case (was 1.53, should've been 1.62). +- The player inventory is now correctly updated when we close a container. +- Inventory interactions are now predicted on the client-side again, and the remaining click operations were implemented. +- `Client::open_container_at` now waits up to 10 ticks for the block to exist if you try to click air. +- Wrong physics collision code resulted in `HitResult` sometimes containing the wrong coordinates and `inside` value. +- Fix the client being unresponsive for a few seconds after joining due to not sending `ServerboundPlayerLoaded`. +- Fix panic when a client received `ClientboundAddEntity` and `ClientboundStartConfiguration` at the same time. +- Fix panic due to `ClientInformation` being inserted too late. +- `ClientboundTeleportEntity` did not handle relative teleports correctly. +- Pathfinder now gets stuck in water less by automatically trying to jump if it's in water. diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs index 7dfe42c4..e6481577 100644 --- a/azalea-client/src/plugins/inventory.rs +++ b/azalea-client/src/plugins/inventory.rs @@ -28,7 +28,7 @@ use crate::{Client, packet::game::SendPacketEvent, respawn::perform_respawn}; pub struct InventoryPlugin; impl Plugin for InventoryPlugin { fn build(&self, app: &mut App) { - app.add_event::<ClientSideCloseContainerEvent>() + app.add_event::<ClientsideCloseContainerEvent>() .add_event::<MenuOpenedEvent>() .add_event::<CloseContainerEvent>() .add_event::<ContainerClickEvent>() @@ -39,7 +39,6 @@ impl Plugin for InventoryPlugin { ( handle_set_selected_hotbar_slot_event, handle_menu_opened_event, - handle_set_container_content_event, handle_container_click_event, handle_container_close_event, handle_client_side_close_container_event, @@ -51,7 +50,10 @@ impl Plugin for InventoryPlugin { .add_systems( GameTick, ensure_has_sent_carried_item.after(super::mining::handle_mining_queued), - ); + ) + .add_observer(handle_client_side_close_container_trigger) + .add_observer(handle_menu_opened_trigger) + .add_observer(handle_set_container_content_trigger); } } @@ -728,24 +730,28 @@ impl Default for Inventory { } } -/// Sent from the server when a menu (like a chest or crafting table) was -/// opened by the client. -#[derive(Event, Debug)] +/// A Bevy trigger that's fired when our client should show a new screen (like a +/// chest or crafting table). +/// +/// To watch for the menu being closed, you could use +/// [`ClientSideCloseContainerEvent`]. To close it manually, use +/// [`CloseContainerEvent`]. +#[derive(Event, Debug, Clone)] pub struct MenuOpenedEvent { pub entity: Entity, pub window_id: i32, pub menu_type: MenuKind, pub title: FormattedText, } -fn handle_menu_opened_event( - mut events: EventReader<MenuOpenedEvent>, - mut query: Query<&mut Inventory>, -) { +fn handle_menu_opened_trigger(event: Trigger<MenuOpenedEvent>, mut query: Query<&mut Inventory>) { + let mut inventory = query.get_mut(event.entity).unwrap(); + inventory.id = event.window_id; + inventory.container_menu = Some(Menu::from_kind(event.menu_type)); + inventory.container_menu_title = Some(event.title.clone()); +} +pub fn handle_menu_opened_event(mut events: EventReader<MenuOpenedEvent>, mut commands: Commands) { for event in events.read() { - let mut inventory = query.get_mut(event.entity).unwrap(); - inventory.id = event.window_id; - inventory.container_menu = Some(Menu::from_kind(event.menu_type)); - inventory.container_menu_title = Some(event.title.clone()); + commands.trigger(event.clone()); } } @@ -763,7 +769,7 @@ pub struct CloseContainerEvent { fn handle_container_close_event( query: Query<(Entity, &Inventory)>, mut events: EventReader<CloseContainerEvent>, - mut client_side_events: EventWriter<ClientSideCloseContainerEvent>, + mut client_side_events: EventWriter<ClientsideCloseContainerEvent>, mut commands: Commands, ) { for event in events.read() { @@ -782,52 +788,62 @@ fn handle_container_close_event( container_id: inventory.id, }, )); - client_side_events.write(ClientSideCloseContainerEvent { + client_side_events.write(ClientsideCloseContainerEvent { entity: event.entity, }); } } -/// Close a container without notifying the server. +/// A Bevy trigger that's fired when our client closed a container. /// -/// Note that this also gets fired when we get a [`CloseContainerEvent`]. -#[derive(Event)] -pub struct ClientSideCloseContainerEvent { +/// This can also be triggered directly to close a container silently without +/// sending any packets to the server. You probably don't want that though, and +/// should instead use [`CloseContainerEvent`]. +/// +/// If you want to watch for a container being opened, you should use +/// [`MenuOpenedEvent`]. +#[derive(Event, Clone)] +pub struct ClientsideCloseContainerEvent { pub entity: Entity, } -pub fn handle_client_side_close_container_event( - mut events: EventReader<ClientSideCloseContainerEvent>, +pub fn handle_client_side_close_container_trigger( + event: Trigger<ClientsideCloseContainerEvent>, mut query: Query<&mut Inventory>, ) { - for event in events.read() { - let mut inventory = query.get_mut(event.entity).unwrap(); - - // copy the Player part of the container_menu to the inventory_menu - if let Some(inventory_menu) = inventory.container_menu.take() { - // this isn't the same as what vanilla does. i believe vanilla synchronizes the - // slots between inventoryMenu and containerMenu by just having the player slots - // point to the same ItemStack in memory, but emulating this in rust would - // require us to wrap our `ItemStack`s as `Arc<Mutex<ItemStack>>` which would - // have kinda terrible ergonomics. - - // the simpler solution i chose to go with here is to only copy the player slots - // when the container is closed. this is perfectly fine for vanilla, but it - // might cause issues if a server modifies id 0 while we have a container - // open... - - // if we do encounter this issue in the wild then the simplest solution would - // probably be to just add logic for updating the container_menu when the server - // tries to modify id 0 for slots within `inventory`. not implemented for now - // because i'm not sure if that's worth worrying about. - - let new_inventory = - inventory_menu.slots()[inventory_menu.player_slots_range()].to_vec(); - let new_inventory = <[ItemStack; 36]>::try_from(new_inventory).unwrap(); - *inventory.inventory_menu.as_player_mut().inventory = new_inventory; - } + let mut inventory = query.get_mut(event.entity).unwrap(); + + // copy the Player part of the container_menu to the inventory_menu + if let Some(inventory_menu) = inventory.container_menu.take() { + // this isn't the same as what vanilla does. i believe vanilla synchronizes the + // slots between inventoryMenu and containerMenu by just having the player slots + // point to the same ItemStack in memory, but emulating this in rust would + // require us to wrap our `ItemStack`s as `Arc<Mutex<ItemStack>>` which would + // have kinda terrible ergonomics. + + // the simpler solution i chose to go with here is to only copy the player slots + // when the container is closed. this is perfectly fine for vanilla, but it + // might cause issues if a server modifies id 0 while we have a container + // open... + + // if we do encounter this issue in the wild then the simplest solution would + // probably be to just add logic for updating the container_menu when the server + // tries to modify id 0 for slots within `inventory`. not implemented for now + // because i'm not sure if that's worth worrying about. + + let new_inventory = inventory_menu.slots()[inventory_menu.player_slots_range()].to_vec(); + let new_inventory = <[ItemStack; 36]>::try_from(new_inventory).unwrap(); + *inventory.inventory_menu.as_player_mut().inventory = new_inventory; + } - inventory.id = 0; - inventory.container_menu_title = None; + inventory.id = 0; + inventory.container_menu_title = None; +} +pub fn handle_client_side_close_container_event( + mut commands: Commands, + mut events: EventReader<ClientsideCloseContainerEvent>, +) { + for event in events.read() { + commands.trigger(event.clone()); } } @@ -900,34 +916,33 @@ pub fn handle_container_click_event( } } -/// Sent from the server when the contents of a container are replaced. Usually -/// triggered by the `ContainerSetContent` packet. +/// Sent from the server when the contents of a container are replaced. +/// +/// Usually triggered by the `ContainerSetContent` packet. #[derive(Event)] pub struct SetContainerContentEvent { pub entity: Entity, pub slots: Vec<ItemStack>, pub container_id: i32, } -fn handle_set_container_content_event( - mut events: EventReader<SetContainerContentEvent>, +pub fn handle_set_container_content_trigger( + event: Trigger<SetContainerContentEvent>, mut query: Query<&mut Inventory>, ) { - for event in events.read() { - let mut inventory = query.get_mut(event.entity).unwrap(); + let mut inventory = query.get_mut(event.entity).unwrap(); - if event.container_id != inventory.id { - warn!( - "Got SetContainerContentEvent for container with ID {}, but the current container ID is {}", - event.container_id, inventory.id - ); - continue; - } + if event.container_id != inventory.id { + warn!( + "Got SetContainerContentEvent for container with ID {}, but the current container ID is {}", + event.container_id, inventory.id + ); + return; + } - let menu = inventory.menu_mut(); - for (i, slot) in event.slots.iter().enumerate() { - if let Some(slot_mut) = menu.slot_mut(i) { - *slot_mut = slot.clone(); - } + let menu = inventory.menu_mut(); + for (i, slot) in event.slots.iter().enumerate() { + if let Some(slot_mut) = menu.slot_mut(i) { + *slot_mut = slot.clone(); } } } diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index 49523002..bc1d1752 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -31,7 +31,7 @@ use crate::{ disconnect::DisconnectEvent, interact::BlockStatePredictionHandler, inventory::{ - ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent, + ClientsideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent, }, local_player::{Hunger, InstanceHolder, LocalGameMode, TabList}, movement::{KnockbackEvent, KnockbackType}, @@ -1136,28 +1136,25 @@ impl GamePacketHandler<'_> { pub fn container_set_content(&mut self, p: &ClientboundContainerSetContent) { debug!("Got container set content packet {p:?}"); - as_system::<(Query<&mut Inventory>, EventWriter<_>)>( - self.ecs, - |(mut query, mut events)| { - let mut inventory = query.get_mut(self.player).unwrap(); - - // container id 0 is always the player's inventory - if p.container_id == 0 { - // this is just so it has the same type as the `else` block - for (i, slot) in p.items.iter().enumerate() { - if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) { - *slot_mut = slot.clone(); - } + as_system::<(Commands, Query<&mut Inventory>)>(self.ecs, |(mut commands, mut query)| { + let mut inventory = query.get_mut(self.player).unwrap(); + + // container id 0 is always the player's inventory + if p.container_id == 0 { + // this is just so it has the same type as the `else` block + for (i, slot) in p.items.iter().enumerate() { + if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) { + *slot_mut = slot.clone(); } - } else { - events.write(SetContainerContentEvent { - entity: self.player, - slots: p.items.clone(), - container_id: p.container_id, - }); } - }, - ); + } else { + commands.trigger(SetContainerContentEvent { + entity: self.player, + slots: p.items.clone(), + container_id: p.container_id, + }); + } + }); } pub fn container_set_data(&mut self, p: &ClientboundContainerSetData) { @@ -1215,8 +1212,8 @@ impl GamePacketHandler<'_> { debug!("Got container close packet {p:?}"); - as_system::<EventWriter<_>>(self.ecs, |mut events| { - events.write(ClientSideCloseContainerEvent { + as_system::<Commands>(self.ecs, |mut commands| { + commands.trigger(ClientsideCloseContainerEvent { entity: self.player, }); }); @@ -1266,8 +1263,8 @@ impl GamePacketHandler<'_> { pub fn open_screen(&mut self, p: &ClientboundOpenScreen) { debug!("Got open screen packet {p:?}"); - as_system::<EventWriter<_>>(self.ecs, |mut events| { - events.write(MenuOpenedEvent { + as_system::<Commands>(self.ecs, |mut commands| { + commands.trigger(MenuOpenedEvent { entity: self.player, window_id: p.container_id, menu_type: p.menu_type, |
