aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/inventory.rs
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-02-22 21:45:26 -0600
committerGitHub <noreply@github.com>2025-02-22 21:45:26 -0600
commite21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7 (patch)
treeadd6f8bfce40d0c07845d8aa4c9945a0b918444c /azalea-client/src/inventory.rs
parentf8130c3c92946d2293634ba4e252d6bc93026c3c (diff)
downloadazalea-drasl-e21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7.tar.xz
Refactor azalea-client (#205)
* start organizing packet_handling more by moving packet handlers into their own functions * finish writing all the handler functions for packets * use macro for generating match statement for packet handler functions * fix set_entity_data * update config state to also use handler functions * organize az-client file structure by moving things into plugins directory * fix merge issues
Diffstat (limited to 'azalea-client/src/inventory.rs')
-rw-r--r--azalea-client/src/inventory.rs768
1 files changed, 0 insertions, 768 deletions
diff --git a/azalea-client/src/inventory.rs b/azalea-client/src/inventory.rs
deleted file mode 100644
index 4d796c9c..00000000
--- a/azalea-client/src/inventory.rs
+++ /dev/null
@@ -1,768 +0,0 @@
-use std::collections::{HashMap, HashSet};
-
-use azalea_chat::FormattedText;
-pub use azalea_inventory::*;
-use azalea_inventory::{
- item::MaxStackSizeExt,
- operations::{
- ClickOperation, CloneClick, PickupAllClick, PickupClick, QuickCraftKind, QuickCraftStatus,
- QuickCraftStatusKind, QuickMoveClick, ThrowClick,
- },
-};
-use azalea_protocol::packets::game::{
- s_container_click::ServerboundContainerClick, s_container_close::ServerboundContainerClose,
- s_set_carried_item::ServerboundSetCarriedItem,
-};
-use azalea_registry::MenuKind;
-use bevy_app::{App, Plugin, Update};
-use bevy_ecs::{
- component::Component,
- entity::Entity,
- event::EventReader,
- prelude::{Event, EventWriter},
- schedule::{IntoSystemConfigs, SystemSet},
- system::Query,
-};
-use tracing::warn;
-
-use crate::{
- Client,
- local_player::PlayerAbilities,
- packet_handling::game::{SendPacketEvent, handle_send_packet_event},
- respawn::perform_respawn,
-};
-
-pub struct InventoryPlugin;
-impl Plugin for InventoryPlugin {
- fn build(&self, app: &mut App) {
- app.add_event::<ClientSideCloseContainerEvent>()
- .add_event::<MenuOpenedEvent>()
- .add_event::<CloseContainerEvent>()
- .add_event::<ContainerClickEvent>()
- .add_event::<SetContainerContentEvent>()
- .add_event::<SetSelectedHotbarSlotEvent>()
- .add_systems(
- Update,
- (
- handle_set_selected_hotbar_slot_event,
- handle_menu_opened_event,
- handle_set_container_content_event,
- handle_container_click_event,
- handle_container_close_event.before(handle_send_packet_event),
- handle_client_side_close_container_event,
- )
- .chain()
- .in_set(InventorySet)
- .before(perform_respawn),
- );
- }
-}
-
-#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
-pub struct InventorySet;
-
-impl Client {
- /// Return the menu that is currently open. If no menu is open, this will
- /// have the player's inventory.
- pub fn menu(&self) -> Menu {
- let mut ecs = self.ecs.lock();
- let inventory = self.query::<&Inventory>(&mut ecs);
- inventory.menu().clone()
- }
-}
-
-/// A component present on all local players that have an inventory.
-#[derive(Component, Debug, Clone)]
-pub struct Inventory {
- /// A component that contains the player's inventory menu. This is
- /// guaranteed to be a `Menu::Player`.
- ///
- /// We keep it as a [`Menu`] since `Menu` has some useful functions that
- /// bare [`azalea_inventory::Player`] doesn't have.
- pub inventory_menu: azalea_inventory::Menu,
-
- /// The ID of the container that's currently open. Its value is not
- /// guaranteed to be anything specific, and may change every time you open a
- /// container (unless it's 0, in which case it means that no container is
- /// open).
- pub id: i32,
- /// The current container menu that the player has open. If no container is
- /// open, this will be `None`.
- pub container_menu: Option<azalea_inventory::Menu>,
- /// The custom name of the menu that's currently open. This is Some when
- /// `container_menu` is Some.
- pub container_menu_title: Option<FormattedText>,
- /// The item that is currently held by the cursor. `Slot::Empty` if nothing
- /// is currently being held.
- ///
- /// This is different from [`Self::selected_hotbar_slot`], which is the
- /// item that's selected in the hotbar.
- pub carried: ItemStack,
- /// An identifier used by the server to track client inventory desyncs. This
- /// is sent on every container click, and it's only ever updated when the
- /// server sends a new container update.
- pub state_id: u32,
-
- pub quick_craft_status: QuickCraftStatusKind,
- pub quick_craft_kind: QuickCraftKind,
- /// A set of the indexes of the slots that have been right clicked in
- /// this "quick craft".
- pub quick_craft_slots: HashSet<u16>,
-
- /// The index of the item in the hotbar that's currently being held by the
- /// player. This MUST be in the range 0..9 (not including 9).
- ///
- /// In a vanilla client this is changed by pressing the number keys or using
- /// the scroll wheel.
- pub selected_hotbar_slot: u8,
-}
-
-impl Inventory {
- /// Returns a reference to the currently active menu. If a container is open
- /// it'll return [`Self::container_menu`], otherwise
- /// [`Self::inventory_menu`].
- ///
- /// Use [`Self::menu_mut`] if you need a mutable reference.
- pub fn menu(&self) -> &azalea_inventory::Menu {
- match &self.container_menu {
- Some(menu) => menu,
- _ => &self.inventory_menu,
- }
- }
-
- /// Returns a mutable reference to the currently active menu. If a container
- /// is open it'll return [`Self::container_menu`], otherwise
- /// [`Self::inventory_menu`].
- ///
- /// Use [`Self::menu`] if you don't need a mutable reference.
- pub fn menu_mut(&mut self) -> &mut azalea_inventory::Menu {
- match &mut self.container_menu {
- Some(menu) => menu,
- _ => &mut self.inventory_menu,
- }
- }
-
- /// Modify the inventory as if the given operation was performed on it.
- pub fn simulate_click(
- &mut self,
- operation: &ClickOperation,
- player_abilities: &PlayerAbilities,
- ) {
- if let ClickOperation::QuickCraft(quick_craft) = operation {
- let last_quick_craft_status_tmp = self.quick_craft_status.clone();
- self.quick_craft_status = last_quick_craft_status_tmp.clone();
- let last_quick_craft_status = last_quick_craft_status_tmp;
-
- // no carried item, reset
- if self.carried.is_empty() {
- return self.reset_quick_craft();
- }
- // if we were starting or ending, or now we aren't ending and the status
- // changed, reset
- if (last_quick_craft_status == QuickCraftStatusKind::Start
- || last_quick_craft_status == QuickCraftStatusKind::End
- || self.quick_craft_status != QuickCraftStatusKind::End)
- && (self.quick_craft_status != last_quick_craft_status)
- {
- return self.reset_quick_craft();
- }
- if self.quick_craft_status == QuickCraftStatusKind::Start {
- self.quick_craft_kind = quick_craft.kind.clone();
- if self.quick_craft_kind == QuickCraftKind::Middle && player_abilities.instant_break
- {
- self.quick_craft_status = QuickCraftStatusKind::Add;
- self.quick_craft_slots.clear();
- } else {
- self.reset_quick_craft();
- }
- return;
- }
- if let QuickCraftStatus::Add { slot } = quick_craft.status {
- let slot_item = self.menu().slot(slot as usize);
- if let Some(slot_item) = slot_item {
- if let ItemStack::Present(carried) = &self.carried {
- // minecraft also checks slot.may_place(carried) and
- // menu.can_drag_to(slot)
- // but they always return true so they're not relevant for us
- if can_item_quick_replace(slot_item, &self.carried, true)
- && (self.quick_craft_kind == QuickCraftKind::Right
- || carried.count as usize > self.quick_craft_slots.len())
- {
- self.quick_craft_slots.insert(slot);
- }
- }
- }
- return;
- }
- if self.quick_craft_status == QuickCraftStatusKind::End {
- if !self.quick_craft_slots.is_empty() {
- if self.quick_craft_slots.len() == 1 {
- // if we only clicked one slot, then turn this
- // QuickCraftClick into a PickupClick
- let slot = *self.quick_craft_slots.iter().next().unwrap();
- self.reset_quick_craft();
- self.simulate_click(
- &match self.quick_craft_kind {
- QuickCraftKind::Left => {
- PickupClick::Left { slot: Some(slot) }.into()
- }
- QuickCraftKind::Right => {
- PickupClick::Left { slot: Some(slot) }.into()
- }
- QuickCraftKind::Middle => {
- // idk just do nothing i guess
- return;
- }
- },
- player_abilities,
- );
- return;
- }
-
- let ItemStack::Present(mut carried) = self.carried.clone() else {
- // this should never happen
- return self.reset_quick_craft();
- };
-
- let mut carried_count = carried.count;
- let mut quick_craft_slots_iter = self.quick_craft_slots.iter();
-
- loop {
- let mut slot: &ItemStack;
- let mut slot_index: u16;
- let mut item_stack: &ItemStack;
-
- loop {
- let Some(&next_slot) = quick_craft_slots_iter.next() else {
- carried.count = carried_count;
- self.carried = ItemStack::Present(carried);
- return self.reset_quick_craft();
- };
-
- slot = self.menu().slot(next_slot as usize).unwrap();
- slot_index = next_slot;
- item_stack = &self.carried;
-
- if slot.is_present()
- && can_item_quick_replace(slot, item_stack, true)
- // this always returns true in most cases
- // && slot.may_place(item_stack)
- && (
- self.quick_craft_kind == QuickCraftKind::Middle
- || item_stack.count() >= self.quick_craft_slots.len() as i32
- )
- {
- break;
- }
- }
-
- // get the ItemStackData for the slot
- let ItemStack::Present(slot) = slot else {
- unreachable!("the loop above requires the slot to be present to break")
- };
-
- // if self.can_drag_to(slot) {
- let mut new_carried = carried.clone();
- let slot_item_count = slot.count;
- get_quick_craft_slot_count(
- &self.quick_craft_slots,
- &self.quick_craft_kind,
- &mut new_carried,
- slot_item_count,
- );
- let max_stack_size = i32::min(
- new_carried.kind.max_stack_size(),
- i32::min(
- new_carried.kind.max_stack_size(),
- slot.kind.max_stack_size(),
- ),
- );
- if new_carried.count > max_stack_size {
- new_carried.count = max_stack_size;
- }
-
- carried_count -= new_carried.count - slot_item_count;
- // we have to inline self.menu_mut() here to avoid the borrow checker
- // complaining
- let menu = match &mut self.container_menu {
- Some(menu) => menu,
- _ => &mut self.inventory_menu,
- };
- *menu.slot_mut(slot_index as usize).unwrap() =
- ItemStack::Present(new_carried);
- }
- }
- } else {
- return self.reset_quick_craft();
- }
- }
- // the quick craft status should always be in start if we're not in quick craft
- // mode
- if self.quick_craft_status != QuickCraftStatusKind::Start {
- return self.reset_quick_craft();
- }
-
- match operation {
- // left clicking outside inventory
- ClickOperation::Pickup(PickupClick::Left { slot: None }) => {
- if self.carried.is_present() {
- // vanilla has `player.drop`s but they're only used
- // server-side
- // they're included as comments here in case you want to adapt this for a server
- // implementation
-
- // player.drop(self.carried, true);
- self.carried = ItemStack::Empty;
- }
- }
- ClickOperation::Pickup(PickupClick::Right { slot: None }) => {
- if self.carried.is_present() {
- let _item = self.carried.split(1);
- // player.drop(item, true);
- }
- }
- ClickOperation::Pickup(
- PickupClick::Left { slot: Some(slot) } | PickupClick::Right { slot: Some(slot) },
- ) => {
- let Some(slot_item) = self.menu().slot(*slot as usize) else {
- return;
- };
- let carried = &self.carried;
- // vanilla does a check called tryItemClickBehaviourOverride
- // here
- // i don't understand it so i didn't implement it
- match slot_item {
- ItemStack::Empty => if carried.is_present() {},
- ItemStack::Present(_) => todo!(),
- }
- }
- ClickOperation::QuickMove(
- QuickMoveClick::Left { slot } | QuickMoveClick::Right { slot },
- ) => {
- // in vanilla it also tests if QuickMove has a slot index of -999
- // but i don't think that's ever possible so it's not covered here
- loop {
- let new_slot_item = self.menu_mut().quick_move_stack(*slot as usize);
- let slot_item = self.menu().slot(*slot as usize).unwrap();
- if new_slot_item.is_empty() || slot_item != &new_slot_item {
- break;
- }
- }
- }
- ClickOperation::Swap(s) => {
- let source_slot_index = s.source_slot as usize;
- let target_slot_index = s.target_slot as usize;
-
- let Some(source_slot) = self.menu().slot(source_slot_index) else {
- return;
- };
- let Some(target_slot) = self.menu().slot(target_slot_index) else {
- return;
- };
- if source_slot.is_empty() && target_slot.is_empty() {
- return;
- }
-
- if target_slot.is_empty() {
- if self.menu().may_pickup(source_slot_index) {
- let source_slot = source_slot.clone();
- let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
- *target_slot = source_slot;
- }
- } else if source_slot.is_empty() {
- let ItemStack::Present(target_item) = target_slot else {
- unreachable!("target slot is not empty but is not present");
- };
- if self.menu().may_place(source_slot_index, target_item) {
- // get the target_item but mutable
- let source_max_stack_size = self.menu().max_stack_size(source_slot_index);
-
- let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
- let new_source_slot = target_slot.split(source_max_stack_size);
- *self.menu_mut().slot_mut(source_slot_index).unwrap() = new_source_slot;
- }
- } else if self.menu().may_pickup(source_slot_index) {
- let ItemStack::Present(target_item) = target_slot else {
- unreachable!("target slot is not empty but is not present");
- };
- if self.menu().may_place(source_slot_index, target_item) {
- let source_max_stack = self.menu().max_stack_size(source_slot_index);
- if target_slot.count() > source_max_stack as i32 {
- // if there's more than the max stack size in the target slot
-
- let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
- let new_source_slot = target_slot.split(source_max_stack);
- *self.menu_mut().slot_mut(source_slot_index).unwrap() = new_source_slot;
- // if !self.inventory_menu.add(new_source_slot) {
- // player.drop(new_source_slot, true);
- // }
- } else {
- // normal swap
- let new_target_slot = source_slot.clone();
- let new_source_slot = target_slot.clone();
-
- let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
- *target_slot = new_target_slot;
-
- let source_slot = self.menu_mut().slot_mut(source_slot_index).unwrap();
- *source_slot = new_source_slot;
- }
- }
- }
- }
- ClickOperation::Clone(CloneClick { slot }) => {
- if !player_abilities.instant_break || self.carried.is_present() {
- return;
- }
- let Some(source_slot) = self.menu().slot(*slot as usize) else {
- return;
- };
- let ItemStack::Present(source_item) = source_slot else {
- return;
- };
- let mut new_carried = source_item.clone();
- new_carried.count = new_carried.kind.max_stack_size();
- self.carried = ItemStack::Present(new_carried);
- }
- ClickOperation::Throw(c) => {
- if self.carried.is_present() {
- return;
- }
-
- let (ThrowClick::Single { slot: slot_index }
- | ThrowClick::All { slot: slot_index }) = c;
- let slot_index = *slot_index as usize;
-
- let Some(slot) = self.menu_mut().slot_mut(slot_index) else {
- return;
- };
- let ItemStack::Present(slot_item) = slot else {
- return;
- };
-
- let dropping_count = match c {
- ThrowClick::Single { .. } => 1,
- ThrowClick::All { .. } => slot_item.count,
- };
-
- let _dropping = slot_item.split(dropping_count as u32);
- // player.drop(dropping, true);
- }
- ClickOperation::PickupAll(PickupAllClick {
- slot: source_slot_index,
- reversed,
- }) => {
- let source_slot_index = *source_slot_index as usize;
-
- let source_slot = self.menu().slot(source_slot_index).unwrap();
- let target_slot = self.carried.clone();
-
- if target_slot.is_empty()
- || (source_slot.is_present() && self.menu().may_pickup(source_slot_index))
- {
- return;
- }
-
- let ItemStack::Present(target_slot_item) = &target_slot else {
- unreachable!("target slot is not empty but is not present");
- };
-
- for round in 0..2 {
- let iterator: Box<dyn Iterator<Item = usize>> = if *reversed {
- Box::new((0..self.menu().len()).rev())
- } else {
- Box::new(0..self.menu().len())
- };
-
- for i in iterator {
- if target_slot_item.count < target_slot_item.kind.max_stack_size() {
- let checking_slot = self.menu().slot(i).unwrap();
- if let ItemStack::Present(checking_item) = checking_slot {
- if can_item_quick_replace(checking_slot, &target_slot, true)
- && self.menu().may_pickup(i)
- && (round != 0
- || checking_item.count
- != checking_item.kind.max_stack_size())
- {
- // get the checking_slot and checking_item again but mutable
- let checking_slot = self.menu_mut().slot_mut(i).unwrap();
-
- let taken_item =
- checking_slot.split(checking_slot.count() as u32);
-
- // now extend the carried item
- let target_slot = &mut self.carried;
- let ItemStack::Present(target_slot_item) = target_slot else {
- unreachable!("target slot is not empty but is not present");
- };
- target_slot_item.count += taken_item.count();
- }
- }
- }
- }
- }
- }
- _ => {}
- }
- }
-
- fn reset_quick_craft(&mut self) {
- self.quick_craft_status = QuickCraftStatusKind::Start;
- self.quick_craft_slots.clear();
- }
-
- /// Get the item in the player's hotbar that is currently being held.
- pub fn held_item(&self) -> ItemStack {
- let inventory = &self.inventory_menu;
- let hotbar_items = &inventory.slots()[inventory.hotbar_slots_range()];
- hotbar_items[self.selected_hotbar_slot as usize].clone()
- }
-}
-
-fn can_item_quick_replace(
- target_slot: &ItemStack,
- item: &ItemStack,
- ignore_item_count: bool,
-) -> bool {
- let ItemStack::Present(target_slot) = target_slot else {
- return false;
- };
- let ItemStack::Present(item) = item else {
- // i *think* this is what vanilla does
- // not 100% sure lol probably doesn't matter though
- return false;
- };
-
- if !item.is_same_item_and_components(target_slot) {
- return false;
- }
- let count = target_slot.count as u16
- + if ignore_item_count {
- 0
- } else {
- item.count as u16
- };
- count <= item.kind.max_stack_size() as u16
-}
-
-fn get_quick_craft_slot_count(
- quick_craft_slots: &HashSet<u16>,
- quick_craft_kind: &QuickCraftKind,
- item: &mut ItemStackData,
- slot_item_count: i32,
-) {
- item.count = match quick_craft_kind {
- QuickCraftKind::Left => item.count / quick_craft_slots.len() as i32,
- QuickCraftKind::Right => 1,
- QuickCraftKind::Middle => item.kind.max_stack_size(),
- };
- item.count += slot_item_count;
-}
-
-impl Default for Inventory {
- fn default() -> Self {
- Inventory {
- inventory_menu: Menu::Player(azalea_inventory::Player::default()),
- id: 0,
- container_menu: None,
- container_menu_title: None,
- carried: ItemStack::Empty,
- state_id: 0,
- quick_craft_status: QuickCraftStatusKind::Start,
- quick_craft_kind: QuickCraftKind::Middle,
- quick_craft_slots: HashSet::new(),
- selected_hotbar_slot: 0,
- }
- }
-}
-
-/// Sent from the server when a menu (like a chest or crafting table) was
-/// opened by the client.
-#[derive(Event, Debug)]
-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>,
-) {
- 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());
- }
-}
-
-/// Tell the server that we want to close a container.
-///
-/// Note that this is also sent when the client closes its own inventory, even
-/// though there is no packet for opening its inventory.
-#[derive(Event)]
-pub struct CloseContainerEvent {
- pub entity: Entity,
- /// The ID of the container to close. 0 for the player's inventory. If this
- /// is not the same as the currently open inventory, nothing will happen.
- pub id: i32,
-}
-fn handle_container_close_event(
- query: Query<(Entity, &Inventory)>,
- mut events: EventReader<CloseContainerEvent>,
- mut client_side_events: EventWriter<ClientSideCloseContainerEvent>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
-) {
- for event in events.read() {
- let (entity, inventory) = query.get(event.entity).unwrap();
- if event.id != inventory.id {
- warn!(
- "Tried to close container with ID {}, but the current container ID is {}",
- event.id, inventory.id
- );
- continue;
- }
-
- send_packet_events.send(SendPacketEvent::new(
- entity,
- ServerboundContainerClose {
- container_id: inventory.id,
- },
- ));
- client_side_events.send(ClientSideCloseContainerEvent {
- entity: event.entity,
- });
- }
-}
-
-/// Close a container without notifying the server.
-///
-/// Note that this also gets fired when we get a [`CloseContainerEvent`].
-#[derive(Event)]
-pub struct ClientSideCloseContainerEvent {
- pub entity: Entity,
-}
-pub fn handle_client_side_close_container_event(
- mut events: EventReader<ClientSideCloseContainerEvent>,
- mut query: Query<&mut Inventory>,
-) {
- for event in events.read() {
- let mut inventory = query.get_mut(event.entity).unwrap();
- inventory.container_menu = None;
- inventory.id = 0;
- inventory.container_menu_title = None;
- }
-}
-
-#[derive(Event, Debug)]
-pub struct ContainerClickEvent {
- pub entity: Entity,
- pub window_id: i32,
- pub operation: ClickOperation,
-}
-pub fn handle_container_click_event(
- mut query: Query<(Entity, &mut Inventory)>,
- mut events: EventReader<ContainerClickEvent>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
-) {
- for event in events.read() {
- let (entity, mut inventory) = query.get_mut(event.entity).unwrap();
- if inventory.id != event.window_id {
- warn!(
- "Tried to click container with ID {}, but the current container ID is {}",
- event.window_id, inventory.id
- );
- continue;
- }
-
- let menu = inventory.menu_mut();
- let old_slots = menu.slots().clone();
-
- // menu.click(&event.operation);
-
- // see which slots changed after clicking and put them in the hashmap
- // the server uses this to check if we desynced
- let mut changed_slots: HashMap<u16, ItemStack> = HashMap::new();
- for (slot_index, old_slot) in old_slots.iter().enumerate() {
- let new_slot = &menu.slots()[slot_index];
- if old_slot != new_slot {
- changed_slots.insert(slot_index as u16, new_slot.clone());
- }
- }
-
- send_packet_events.send(SendPacketEvent::new(
- entity,
- ServerboundContainerClick {
- container_id: event.window_id,
- state_id: inventory.state_id,
- slot_num: event.operation.slot_num().map(|n| n as i16).unwrap_or(-999),
- button_num: event.operation.button_num(),
- click_type: event.operation.click_type(),
- changed_slots,
- carried_item: inventory.carried.clone(),
- },
- ));
- }
-}
-
-/// 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>,
- mut query: Query<&mut Inventory>,
-) {
- for event in events.read() {
- let mut inventory = query.get_mut(event.entity).unwrap();
-
- if event.container_id != inventory.id {
- warn!(
- "Tried to set container content with ID {}, but the current container ID is {}",
- event.container_id, inventory.id
- );
- continue;
- }
-
- 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();
- }
- }
- }
-}
-
-#[derive(Event)]
-pub struct SetSelectedHotbarSlotEvent {
- pub entity: Entity,
- /// The hotbar slot to select. This should be in the range 0..=8.
- pub slot: u8,
-}
-fn handle_set_selected_hotbar_slot_event(
- mut events: EventReader<SetSelectedHotbarSlotEvent>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
- mut query: Query<&mut Inventory>,
-) {
- for event in events.read() {
- let mut inventory = query.get_mut(event.entity).unwrap();
-
- // if the slot is already selected, don't send a packet
- if inventory.selected_hotbar_slot == event.slot {
- continue;
- }
-
- inventory.selected_hotbar_slot = event.slot;
- send_packet_events.send(SendPacketEvent::new(
- event.entity,
- ServerboundSetCarriedItem {
- slot: event.slot as u16,
- },
- ));
- }
-}