steel_core/player/player_inventory/
core.rs1use std::{array, ops::Range};
2
3use simdnbt::owned::{NbtList, NbtTag};
4use steel_registry::{enchantment_effect::EnchantmentEffectComponent, item_stack::ItemStack};
5use steel_utils::{DowncastType, DowncastTypeKey};
6
7use crate::inventory::{
8 container::Container,
9 equipment::{EntityEquipment, EquipmentSlot},
10};
11
12use super::container::InvalidHotbarSlot;
13
14pub struct PlayerInventory {
19 pub(super) items: [ItemStack; Self::CONTAINER_SIZE],
21 pub(super) selected: u8,
23 pub(super) times_changed: u32,
25}
26
27impl Default for PlayerInventory {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33unsafe impl DowncastType for PlayerInventory {
35 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:container/player_inventory");
36}
37
38impl PlayerInventory {
39 pub(crate) fn take_death_drops(&mut self) -> Vec<ItemStack> {
40 (0..self.get_container_size())
41 .filter_map(|slot| {
42 let item = self.get_item(slot).clone();
43 if item.is_empty() {
44 return None;
45 }
46 self.set_item(slot, ItemStack::empty());
47 (!item.has_enchantment_effect(EnchantmentEffectComponent::PreventEquipmentDrop))
48 .then_some(item)
49 })
50 .collect()
51 }
52
53 pub const INVENTORY_SIZE: usize = 36;
55 pub const CONTAINER_SIZE: usize = 43;
57 pub const SELECTION_SIZE: u8 = 9;
59 pub const SLOT_OFFHAND: usize = 40;
61 pub const HOTBAR: Range<usize> = 0..9;
63 pub const MAIN: Range<usize> = 9..36;
65 pub const ARMOR_TOP_DOWN: [usize; 4] = [39, 38, 37, 36];
67
68 #[must_use]
70 pub fn new() -> Self {
71 Self {
72 items: array::from_fn(|_| ItemStack::empty()),
73 selected: 0,
74 times_changed: 0,
75 }
76 }
77
78 #[must_use]
80 pub(crate) fn replacement_copy(&self) -> Self {
81 Self {
82 items: array::from_fn(|slot| {
83 let item = &self.items[slot];
84 item.copy_with_count(item.count())
85 }),
86 selected: self.selected,
87 times_changed: 0,
88 }
89 }
90
91 #[must_use]
93 pub const fn is_hotbar_slot(slot: u8) -> bool {
94 slot < Self::SELECTION_SIZE
95 }
96
97 #[must_use]
99 pub const fn get_selected_slot(&self) -> u8 {
100 self.selected
101 }
102
103 #[must_use]
105 pub(crate) fn to_vanilla_inventory_nbt(&self) -> NbtList {
106 let items = self.items[..Self::INVENTORY_SIZE]
107 .iter()
108 .enumerate()
109 .filter_map(|(slot, item)| {
110 if item.is_empty() {
111 return None;
112 }
113 let NbtTag::Compound(mut nbt) = item.to_nbt_tag_ref() else {
114 return None;
115 };
116 nbt.insert("Slot", NbtTag::Byte(slot as i8));
117 Some(nbt)
118 })
119 .collect();
120 NbtList::Compound(items)
121 }
122
123 pub fn set_selected_slot(&mut self, slot: u8) {
129 if Self::is_hotbar_slot(slot) {
130 if self.selected != slot {
131 self.selected = slot;
132 }
133 } else {
134 panic!("Invalid hotbar slot: {slot}");
135 }
136 }
137
138 pub fn try_set_selected_slot_from_packet(
143 &mut self,
144 slot: i16,
145 ) -> Result<(), InvalidHotbarSlot> {
146 let Ok(slot) = u8::try_from(slot) else {
147 return Err(InvalidHotbarSlot);
148 };
149 if !Self::is_hotbar_slot(slot) {
150 return Err(InvalidHotbarSlot);
151 }
152
153 self.set_selected_slot(slot);
154 Ok(())
155 }
156
157 pub fn with_selected_item<R>(&self, f: impl FnOnce(&ItemStack) -> R) -> R {
159 f(&self.items[self.selected as usize])
160 }
161
162 #[must_use]
164 pub const fn get_selected_item(&self) -> &ItemStack {
165 &self.items[self.selected as usize]
166 }
167
168 pub fn get_selected_item_mut(&mut self) -> &mut ItemStack {
170 EntityEquipment::get_mut(self, EquipmentSlot::MainHand)
171 }
172
173 pub fn set_selected_item(&mut self, item: ItemStack) {
175 let _ = EntityEquipment::set(self, EquipmentSlot::MainHand, item);
176 }
177
178 #[must_use]
180 pub fn get_offhand_item(&self) -> &ItemStack {
181 EntityEquipment::get_ref(self, EquipmentSlot::OffHand)
182 }
183
184 pub fn get_offhand_item_mut(&mut self) -> &mut ItemStack {
186 EntityEquipment::get_mut(self, EquipmentSlot::OffHand)
187 }
188
189 pub fn set_offhand_item(&mut self, item: ItemStack) {
191 let _ = EntityEquipment::set(self, EquipmentSlot::OffHand, item);
192 }
193
194 pub fn with_selected_item_mut<R>(&mut self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
196 self.with_equipment_item_mut(EquipmentSlot::MainHand, f)
197 }
198
199 pub(in crate::player) fn with_equipment_item_mut<R>(
200 &mut self,
201 slot: EquipmentSlot,
202 f: impl FnOnce(&mut ItemStack) -> R,
203 ) -> R {
204 let inventory_index = self.equipment_slot_index(slot);
205 let previous = self.items[inventory_index].clone();
206 let result = f(&mut self.items[inventory_index]);
207 if !ItemStack::matches(&self.items[inventory_index], &previous) {
208 Container::set_changed(self);
209 }
210 result
211 }
212
213 #[must_use]
215 pub const fn get_times_changed(&self) -> u32 {
216 self.times_changed
217 }
218
219 #[must_use]
221 pub fn get_items(&self) -> &[ItemStack; Self::INVENTORY_SIZE] {
222 let Some(items) = self.items.first_chunk::<{ Self::INVENTORY_SIZE }>() else {
223 unreachable!("the player inventory always contains its 36 main slots");
224 };
225 items
226 }
227
228 #[must_use]
230 pub fn get_free_slot(&self) -> Option<u8> {
231 (0..Self::INVENTORY_SIZE)
232 .find(|&i| self.items[i].is_empty())
233 .map(|i| i as u8)
234 }
235
236 #[must_use]
242 pub fn get_suitable_hotbar_slot(&self) -> u8 {
243 for slot in 0..Self::SELECTION_SIZE {
244 let index = (self.selected + slot) % Self::SELECTION_SIZE;
245 if self.items[usize::from(index)].is_empty() {
246 return index;
247 }
248 }
249
250 for slot in 0..Self::SELECTION_SIZE {
251 let index = (self.selected + slot) % Self::SELECTION_SIZE;
252 if !self.items[usize::from(index)].is_enchanted() {
253 return index;
254 }
255 }
256
257 self.selected
258 }
259
260 #[must_use]
262 pub fn find_slot_matching_item(&self, stack: &ItemStack) -> Option<u8> {
263 (0..Self::INVENTORY_SIZE)
264 .find(|&i| !self.items[i].is_empty() && ItemStack::is_same_item(&self.items[i], stack))
265 .map(|i| i as u8)
266 }
267
268 #[must_use]
270 pub fn find_slot_matching_item_with_same_components(&self, stack: &ItemStack) -> Option<u8> {
271 (0..Self::INVENTORY_SIZE)
272 .find(|&i| {
273 !self.items[i].is_empty()
274 && ItemStack::is_same_item_same_components(&self.items[i], stack)
275 })
276 .map(|i| i as u8)
277 }
278
279 pub fn pick_slot(&mut self, slot: u8) {
282 let slot = slot as usize;
283 if slot >= Self::INVENTORY_SIZE {
284 return;
285 }
286 let selected = self.selected as usize;
287 self.items.swap(selected, slot);
288 self.set_changed();
289 }
290
291 pub fn add_and_pick_item(&mut self, stack: ItemStack) -> bool {
294 for i in 0..Self::SELECTION_SIZE {
296 let j = usize::from(i);
297 if self.items[j].is_empty() {
298 self.items[j] = stack;
299 self.selected = i;
300 self.set_changed();
301 return true;
302 }
303 }
304 self.items[self.selected as usize] = stack;
306 self.set_changed();
307 true
308 }
309}