1use core::ops::Not;
2use hashbrown::HashMap;
3use serde::{Deserialize, Serialize};
4use specs::{Component, DerefFlaggedStorage};
5use std::{cmp::Ordering, convert::TryFrom, mem, num::NonZeroU32, ops::Range};
6use tracing::{debug, trace, warn};
7use vek::Vec3;
8
9use crate::{
10 LoadoutBuilder,
11 comp::{
12 Item,
13 body::Body,
14 inventory::{
15 item::{
16 ItemDef, ItemDefinitionIdOwned, ItemKind, MaterialStatManifest, TagExampleInfo,
17 item_key::ItemKey, tool::AbilityMap,
18 },
19 loadout::Loadout,
20 recipe_book::RecipeBook,
21 slot::{EquipSlot, Slot, SlotError},
22 },
23 loot_owner::LootOwnerKind,
24 slot::{InvSlotId, SlotId},
25 },
26 recipe::{Recipe, RecipeBookManifest},
27 resources::Time,
28 terrain::SpriteKind,
29 uid::Uid,
30};
31
32use super::FrontendItem;
33
34pub mod item;
35pub mod loadout;
36pub mod loadout_builder;
37pub mod recipe_book;
38pub mod slot;
39#[cfg(test)] mod test;
40#[cfg(test)] mod test_helpers;
41pub mod trade_pricing;
42
43pub type InvSlot = Option<Item>;
44const DEFAULT_INVENTORY_SLOTS: usize = 18;
45
46#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct Inventory {
49 next_sort_order: InventorySortOrder,
50 loadout: Loadout,
51 slots: Vec<InvSlot>,
54 overflow_items: Vec<Item>,
59 recipe_book: RecipeBook,
61}
62
63#[derive(Debug)]
65pub enum Error {
66 Full(Vec<Item>),
69}
70
71impl Error {
72 pub fn returned_items(self) -> impl Iterator<Item = Item> {
73 match self {
74 Error::Full(items) => items.into_iter(),
75 }
76 }
77}
78
79#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
80pub enum InventorySortOrder {
81 Name,
82 Quality,
83 Category,
84 Tag,
85 Amount,
86}
87
88impl InventorySortOrder {
89 fn next(&self) -> InventorySortOrder {
90 match self {
91 InventorySortOrder::Name => InventorySortOrder::Quality,
92 InventorySortOrder::Quality => InventorySortOrder::Tag,
93 InventorySortOrder::Tag => InventorySortOrder::Category,
94 InventorySortOrder::Category => InventorySortOrder::Amount,
95 InventorySortOrder::Amount => InventorySortOrder::Name,
96 }
97 }
98}
99
100pub enum CustomOrder {
101 Name,
102 Quality,
103 KindPartial,
104 KindFull,
105 Tag,
106}
107
108impl Inventory {
122 pub fn with_empty() -> Inventory {
123 Self::with_loadout_humanoid(LoadoutBuilder::empty().build())
124 }
125
126 pub fn with_loadout(loadout: Loadout, body: Body) -> Inventory {
127 if let Body::Humanoid(_) = body {
128 Self::with_loadout_humanoid(loadout)
129 } else {
130 Self::with_loadout_animal(loadout)
131 }
132 }
133
134 pub fn with_loadout_humanoid(loadout: Loadout) -> Inventory {
135 Inventory {
136 next_sort_order: InventorySortOrder::Name,
137 loadout,
138 slots: vec![None; DEFAULT_INVENTORY_SLOTS],
139 overflow_items: Vec::new(),
140 recipe_book: RecipeBook::default(),
141 }
142 }
143
144 pub fn with_loadout_animal(loadout: Loadout) -> Inventory {
145 Inventory {
146 next_sort_order: InventorySortOrder::Name,
147 loadout,
148 slots: vec![None; 1],
149 overflow_items: Vec::new(),
150 recipe_book: RecipeBook::default(),
151 }
152 }
153
154 pub fn with_recipe_book(mut self, recipe_book: RecipeBook) -> Inventory {
155 self.recipe_book = recipe_book;
156 self
157 }
158
159 pub fn capacity(&self) -> usize { self.slots().count() }
161
162 pub fn slots(&self) -> impl Iterator<Item = &InvSlot> {
164 self.slots
165 .iter()
166 .chain(self.loadout.inv_slots_with_id().map(|(_, slot)| slot))
167 }
168
169 pub fn overflow_items(&self) -> impl Iterator<Item = &Item> { self.overflow_items.iter() }
171
172 fn slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
174 self.slots.iter_mut().chain(self.loadout.inv_slots_mut())
175 }
176
177 fn slots_mut_with_mutable_recently_unequipped_items(
178 &mut self,
179 ) -> (
180 impl Iterator<Item = &mut InvSlot>,
181 &mut HashMap<ItemDefinitionIdOwned, (Time, u8)>,
182 ) {
183 let (slots_mut, recently_unequipped) = self
184 .loadout
185 .inv_slots_mut_with_mutable_recently_unequipped_items();
186 (self.slots.iter_mut().chain(slots_mut), recently_unequipped)
187 }
188
189 pub fn slots_with_id(&self) -> impl Iterator<Item = (InvSlotId, &InvSlot)> {
191 self.slots
192 .iter()
193 .enumerate()
194 .map(|(i, slot)| ((InvSlotId::new(0, u16::try_from(i).unwrap())), slot))
195 .chain(
196 self.loadout
197 .inv_slots_with_id()
198 .map(|(loadout_slot_id, inv_slot)| (loadout_slot_id.into(), inv_slot)),
199 )
200 }
201
202 pub fn order_by_custom(custom_order: &[CustomOrder], a: &Item, b: &Item) -> Ordering {
204 let mut order = custom_order.iter();
205 let a_quality = a.quality();
206 let b_quality = b.quality();
207 let a_kind = a.kind().get_itemkind_string();
208 let b_kind = b.kind().get_itemkind_string();
209 let mut cmp = Ordering::Equal;
210 while cmp == Ordering::Equal {
211 match order.next() {
212 Some(CustomOrder::KindFull) => cmp = Ord::cmp(&a_kind, &b_kind),
213 Some(CustomOrder::KindPartial) => {
214 cmp = Ord::cmp(
215 &a_kind.split_once(':').unwrap().0,
216 &b_kind.split_once(':').unwrap().0,
217 )
218 },
219 Some(CustomOrder::Quality) => cmp = Ord::cmp(&b_quality, &a_quality),
220 #[expect(deprecated)]
221 Some(CustomOrder::Name) => cmp = Ord::cmp(&a.name(), &b.name()),
222 Some(CustomOrder::Tag) => {
223 cmp = Ord::cmp(
224 &a.tags().first().map_or("", |tag| tag.name()),
225 &b.tags().first().map_or("", |tag| tag.name()),
226 )
227 },
228 _ => break,
229 }
230 }
231 cmp
232 }
233
234 pub fn sort(&mut self) {
236 let sort_order = self.next_sort_order;
237 let mut items: Vec<Item> = self.slots_mut().filter_map(mem::take).collect();
238
239 items.sort_by(|a, b| match sort_order {
240 #[expect(deprecated)]
241 InventorySortOrder::Name => Ord::cmp(&a.name(), &b.name()),
242 InventorySortOrder::Quality => Ord::cmp(&b.quality(), &a.quality()),
244 InventorySortOrder::Category => {
245 let order = [
246 CustomOrder::KindPartial,
247 CustomOrder::Quality,
248 CustomOrder::KindFull,
249 CustomOrder::Name,
250 ];
251 Self::order_by_custom(&order, a, b)
252 },
253 InventorySortOrder::Tag => Ord::cmp(
254 &a.tags().first().map_or("", |tag| tag.name()),
255 &b.tags().first().map_or("", |tag| tag.name()),
256 ),
257 InventorySortOrder::Amount => Ord::cmp(&b.amount(), &a.amount()),
259 });
260
261 self.push_all(items.into_iter()).expect(
262 "It is impossible for there to be insufficient inventory space when sorting the \
263 inventory",
264 );
265
266 self.next_sort_order = self.next_sort_order.next();
267 }
268
269 pub fn next_sort_order(&self) -> InventorySortOrder { self.next_sort_order }
272
273 pub fn push(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
280 if item.is_stackable() {
285 let total_amount = item.amount();
286
287 let remaining = self
288 .slots_mut()
289 .filter_map(Option::as_mut)
290 .filter(|s| *s == &item)
291 .try_fold(total_amount, |remaining, current| {
292 debug_assert_eq!(
293 item.max_amount(),
294 current.max_amount(),
295 "max_amount of two equal items must match"
296 );
297
298 let new_remaining = remaining
301 .checked_sub(current.max_amount() - current.amount())
302 .filter(|&remaining| remaining > 0);
303 if new_remaining.is_some() {
304 current
307 .set_amount(current.max_amount())
308 .expect("max_amount() is always a valid amount");
309 } else {
310 current.increase_amount(remaining).expect(
312 "This item must be able to contain the remaining amount, because \
313 remaining < current.max_amount() - current.amount()",
314 );
315 }
316
317 new_remaining
318 });
319
320 if let Some(remaining) = remaining {
321 item.set_amount(remaining)
322 .expect("Remaining is known to be > 0");
323 self.insert(item)
324 .map_err(|item| (item, NonZeroU32::new(total_amount - remaining)))
325 } else {
326 Ok(())
327 }
328 } else {
329 self.insert(item).map_err(|item| (item, None))
331 }
332 }
333
334 pub fn push_all<I: Iterator<Item = Item>>(&mut self, items: I) -> Result<(), Error> {
337 let mut leftovers = Vec::new();
339 for item in items {
340 if let Err((item, _)) = self.push(item) {
341 leftovers.push(item);
342 }
343 }
344 if !leftovers.is_empty() {
345 Err(Error::Full(leftovers))
346 } else {
347 Ok(())
348 }
349 }
350
351 pub fn push_all_unique<I: Iterator<Item = Item>>(&mut self, mut items: I) -> Result<(), Error> {
361 let mut leftovers = Vec::new();
362 for item in &mut items {
363 if self.contains(&item).not() {
364 if let Err((overflow, _)) = self.push(item) {
365 leftovers.push(overflow);
366 }
367 } }
369 if !leftovers.is_empty() {
370 Err(Error::Full(leftovers))
371 } else {
372 Ok(())
373 }
374 }
375
376 pub fn insert_at(&mut self, inv_slot_id: InvSlotId, item: Item) -> Result<Option<Item>, Item> {
379 match self.slot_mut(inv_slot_id) {
380 Some(slot) => Ok(mem::replace(slot, Some(item))),
381 None => Err(item),
382 }
383 }
384
385 pub fn merge_stack_into(&mut self, src: InvSlotId, dst: InvSlotId) -> bool {
388 let mut amount = None;
389 if let (Some(srcitem), Some(dstitem)) = (self.get(src), self.get(dst)) {
390 if srcitem == dstitem && srcitem.is_stackable() {
394 amount = Some(srcitem.amount());
395 }
396 }
397 if let Some(amount) = amount {
398 let dstitem = self
399 .get_mut(dst)
400 .expect("self.get(dst) was Some right above this");
401 dstitem
402 .increase_amount(amount)
403 .map(|_| {
404 self.remove(src).expect("Already verified that src was populated.");
406 })
407 .is_ok()
409 } else {
410 false
411 }
412 }
413
414 pub fn insert_or_stack_at(
417 &mut self,
418 inv_slot_id: InvSlotId,
419 item: Item,
420 ) -> Result<Option<Item>, Item> {
421 if item.is_stackable() {
422 match self.slot_mut(inv_slot_id) {
423 Some(Some(slot_item)) => {
424 Ok(if slot_item == &item {
425 slot_item
426 .increase_amount(item.amount())
427 .err()
428 .and(Some(item))
429 } else {
430 let old_item = mem::replace(slot_item, item);
431 Some(old_item)
433 })
434 },
435 Some(None) => self.insert_at(inv_slot_id, item),
436 None => Err(item),
437 }
438 } else {
439 self.insert_at(inv_slot_id, item)
440 }
441 }
442
443 #[must_use = "Returned item will be lost if not used"]
446 pub fn try_equip(&mut self, item: Item) -> Result<(), Item> { self.loadout.try_equip(item) }
447
448 pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() }
449
450 pub fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() }
451
452 pub fn contains(&self, item: &Item) -> bool {
454 self.slots().any(|slot| slot.as_ref() == Some(item))
455 }
456
457 pub fn get_slot_of_item(&self, item: &Item) -> Option<InvSlotId> {
459 self.slots_with_id()
460 .find(|&(_, it)| {
461 if let Some(it) = it {
462 it.item_definition_id() == item.item_definition_id()
463 } else {
464 false
465 }
466 })
467 .map(|(slot, _)| slot)
468 }
469
470 pub fn get_slot_of_item_by_def_id(
471 &self,
472 item_def_id: &item::ItemDefinitionIdOwned,
473 ) -> Option<InvSlotId> {
474 self.slots_with_id()
475 .find(|&(_, it)| {
476 if let Some(it) = it {
477 it.item_definition_id() == *item_def_id
478 } else {
479 false
480 }
481 })
482 .map(|(slot, _)| slot)
483 }
484
485 pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
487 self.slot(inv_slot_id).and_then(Option::as_ref)
488 }
489
490 pub fn get_overflow(&self, overflow: usize) -> Option<&Item> {
492 self.overflow_items.get(overflow)
493 }
494
495 pub fn get_slot(&self, slot: Slot) -> Option<&Item> {
497 match slot {
498 Slot::Inventory(inv_slot) => self.get(inv_slot),
499 Slot::Equip(equip) => self.equipped(equip),
500 Slot::Overflow(overflow) => self.get_overflow(overflow),
501 }
502 }
503
504 pub fn get_by_hash(&self, item_hash: u64) -> Option<&Item> {
506 self.slots().flatten().find(|i| i.item_hash() == item_hash)
507 }
508
509 pub fn get_slot_from_hash(&self, item_hash: u64) -> Option<InvSlotId> {
511 let slot_with_id = self.slots_with_id().find(|slot| match slot.1 {
512 None => false,
513 Some(item) => item.item_hash() == item_hash,
514 });
515 slot_with_id.map(|s| s.0)
516 }
517
518 fn get_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut Item> {
520 self.slot_mut(inv_slot_id).and_then(Option::as_mut)
521 }
522
523 pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
525 self.loadout.equipped(equip_slot)
526 }
527
528 pub fn loadout_items_with_persistence_key(
529 &self,
530 ) -> impl Iterator<Item = (&str, Option<&Item>)> {
531 self.loadout.items_with_persistence_key()
532 }
533
534 pub fn get_slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
538 let offset = self.slots.len();
541 self.loadout
542 .slot_range_for_equip_slot(equip_slot)
543 .map(|loadout_range| (loadout_range.start + offset)..(loadout_range.end + offset))
544 }
545
546 pub fn swap_slots(&mut self, a: InvSlotId, b: InvSlotId) {
548 if self.slot(a).is_none() || self.slot(b).is_none() {
549 warn!("swap_slots called with non-existent inventory slot(s)");
550 return;
551 }
552
553 let slot_a = mem::take(self.slot_mut(a).unwrap());
554 let slot_b = mem::take(self.slot_mut(b).unwrap());
555 *self.slot_mut(a).unwrap() = slot_b;
556 *self.slot_mut(b).unwrap() = slot_a;
557 }
558
559 pub fn move_overflow_item(&mut self, overflow: usize, inv_slot: InvSlotId) {
561 match self.slot(inv_slot) {
562 Some(Some(_)) => {
563 warn!("Attempted to move from overflow slot to a filled inventory slot");
564 return;
565 },
566 None => {
567 warn!("Attempted to move from overflow slot to a non-existent inventory slot");
568 return;
569 },
570 Some(None) => {},
571 };
572
573 let item = self.overflow_items.remove(overflow);
574 *self.slot_mut(inv_slot).unwrap() = Some(item);
575 }
576
577 pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
579 self.slot_mut(inv_slot_id).and_then(|item| item.take())
580 }
581
582 #[must_use = "Returned items will be lost if not used"]
584 pub fn overflow_remove(&mut self, overflow_slot: usize) -> Option<Item> {
585 if overflow_slot < self.overflow_items.len() {
586 Some(self.overflow_items.remove(overflow_slot))
587 } else {
588 None
589 }
590 }
591
592 pub fn take(
594 &mut self,
595 inv_slot_id: InvSlotId,
596 ability_map: &AbilityMap,
597 msm: &MaterialStatManifest,
598 ) -> Option<Item> {
599 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
600 let mut return_item = item.duplicate(ability_map, msm);
601
602 if item.is_stackable() && item.amount() > 1 {
603 item.decrease_amount(1).ok()?;
604 return_item
605 .set_amount(1)
606 .expect("Items duplicated from a stackable item must be stackable.");
607 Some(return_item)
608 } else {
609 self.remove(inv_slot_id)
610 }
611 } else {
612 None
613 }
614 }
615
616 pub fn take_amount(
619 &mut self,
620 inv_slot_id: InvSlotId,
621 amount: NonZeroU32,
622 ability_map: &AbilityMap,
623 msm: &MaterialStatManifest,
624 ) -> Option<Item> {
625 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
626 if item.is_stackable() && item.amount() > amount.get() {
627 let mut return_item = item.duplicate(ability_map, msm);
628 let return_amount = amount.get();
629 let new_amount = item.amount() - return_amount;
631
632 return_item
633 .set_amount(return_amount)
634 .expect("We know that 0 < return_amount < item.amount()");
635 item.set_amount(new_amount)
636 .expect("new_amount must be > 0 since return item is < item.amount");
637
638 Some(return_item)
639 } else {
640 self.remove(inv_slot_id)
643 }
644 } else {
645 None
646 }
647 }
648
649 #[must_use = "Returned items will be lost if not used"]
651 pub fn take_half(
652 &mut self,
653 inv_slot_id: InvSlotId,
654 ability_map: &AbilityMap,
655 msm: &MaterialStatManifest,
656 ) -> Option<Item> {
657 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
658 item.take_half(ability_map, msm)
659 .or_else(|| self.remove(inv_slot_id))
660 } else {
661 None
662 }
663 }
664
665 #[must_use = "Returned items will be lost if not used"]
667 pub fn overflow_take_half(
668 &mut self,
669 overflow_slot: usize,
670 ability_map: &AbilityMap,
671 msm: &MaterialStatManifest,
672 ) -> Option<Item> {
673 if let Some(item) = self.overflow_items.get_mut(overflow_slot) {
674 item.take_half(ability_map, msm)
675 .or_else(|| self.overflow_remove(overflow_slot))
676 } else {
677 None
678 }
679 }
680
681 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
683 self.slots_mut()
684 .filter(|x| x.is_some())
685 .filter_map(mem::take)
686 }
687
688 pub fn item_count(&self, item_def: &ItemDef) -> u64 {
690 self.slots()
691 .flatten()
692 .filter(|it| it.is_same_item_def(item_def))
693 .map(|it| u64::from(it.amount()))
694 .sum()
695 }
696
697 pub fn has_space_for(&self, item_def: &ItemDef, amount: u32) -> bool {
700 let free_spaces = self
701 .slots()
702 .map(|i| {
703 if let Some(item) = i {
704 if item.is_same_item_def(item_def) {
705 0
706 } else {
707 item.max_amount().saturating_sub(item.amount()) as u64
710 }
711 } else {
712 amount as u64
716 }
717 })
718 .sum::<u64>();
719 free_spaces >= amount as u64
720 }
721
722 pub fn remove_item_amount(
730 &mut self,
731 item_def: &ItemDef,
732 amount: u32,
733 ability_map: &AbilityMap,
734 msm: &MaterialStatManifest,
735 ) -> Option<Vec<Item>> {
736 let mut amount = amount as u64;
737 if self.item_count(item_def) >= amount {
738 let mut removed_items = Vec::new();
739 for slot in self.slots_mut() {
740 if amount == 0 {
741 return Some(removed_items);
743 } else if let Some(item) = slot
744 && item.is_same_item_def(item_def)
745 {
746 if amount < item.amount() as u64 {
747 removed_items
752 .push(item.take_amount(ability_map, msm, amount as u32).unwrap());
753 return Some(removed_items);
754 } else {
755 amount -= item.amount() as u64;
757 removed_items.push(slot.take().unwrap());
758 }
759 }
760 }
761 Some(removed_items)
762 } else {
763 None
764 }
765 }
766
767 fn insert(&mut self, item: Item) -> Result<(), Item> {
771 match self.slots_mut().find(|slot| slot.is_none()) {
772 Some(slot) => {
773 *slot = Some(item);
774 Ok(())
775 },
776 None => Err(item),
777 }
778 }
779
780 pub fn slot(&self, inv_slot_id: InvSlotId) -> Option<&InvSlot> {
781 match SlotId::from(inv_slot_id) {
782 SlotId::Inventory(slot_idx) => self.slots.get(slot_idx),
783 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot(loadout_slot_id),
784 }
785 }
786
787 pub fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> {
788 match SlotId::from(inv_slot_id) {
789 SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx),
790 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot_mut(loadout_slot_id),
791 }
792 }
793
794 pub fn free_slots_minus_equipped_item(&self, equip_slot: EquipSlot) -> usize {
797 if let Some(mut equip_slot_idx) = self.loadout.loadout_idx_for_equip_slot(equip_slot) {
798 equip_slot_idx += 1;
800
801 self.slots_with_id()
802 .filter(|(inv_slot_id, slot)| {
803 inv_slot_id.loadout_idx() != equip_slot_idx && slot.is_none()
804 })
805 .count()
806 } else {
807 warn!(
809 "Attempted to fetch loadout index for non-existent EquipSlot: {:?}",
810 equip_slot
811 );
812 0
813 }
814 }
815
816 pub fn equipped_items(&self) -> impl Iterator<Item = &Item> { self.loadout.items() }
817
818 pub fn equipped_items_with_slot(&self) -> impl Iterator<Item = (EquipSlot, &Item)> {
819 self.loadout.items_with_slot()
820 }
821
822 pub fn replace_loadout_item(
825 &mut self,
826 equip_slot: EquipSlot,
827 replacement_item: Option<Item>,
828 time: Time,
829 ) -> Option<Item> {
830 self.loadout.swap(equip_slot, replacement_item, time)
831 }
832
833 #[must_use = "Returned items will be lost if not used"]
837 pub fn equip(&mut self, inv_slot: InvSlotId, time: Time) -> Vec<Item> {
838 self.get(inv_slot)
839 .and_then(|item| self.loadout.get_slot_to_equip_into(&item.kind()))
840 .map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot, time))
841 .unwrap_or_default()
842 }
843
844 pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
848 let (inv_slot_for_equipped, slots_from_equipped) = self
849 .get(inv_slot)
850 .and_then(|item| self.loadout.get_slot_to_equip_into(&item.kind()))
851 .and_then(|equip_slot| self.equipped(equip_slot))
852 .map_or((1, 0), |item| (0, item.slots().len()));
853
854 let slots_from_inv = self
855 .get(inv_slot)
856 .map(|item| item.slots().len())
857 .unwrap_or(0);
858
859 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
860 - i32::try_from(slots_from_equipped)
861 .expect("Equipped item with more than i32::MAX slots")
862 + i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
863 - i32::try_from(self.populated_slots())
864 .expect("Inventory item with more than i32::MAX used slots")
865 + inv_slot_for_equipped }
867
868 pub fn pickup_item(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
876 if item.is_stackable() {
877 return self.push(item);
878 }
879
880 if self.free_slots() < item.populated_slots() + 1 {
881 return Err((item, None));
882 }
883
884 item.drain().for_each(|item| {
888 self.push(item).unwrap();
889 });
890 self.push(item)
891 }
892
893 #[must_use = "Returned items will be lost if not used"]
896 #[expect(clippy::needless_collect)] pub fn unequip(
898 &mut self,
899 equip_slot: EquipSlot,
900 time: Time,
901 ) -> Result<Option<Vec<Item>>, SlotError> {
902 if self.free_slots_minus_equipped_item(equip_slot) == 0 {
904 return Err(SlotError::InventoryFull);
905 }
906
907 Ok(self
908 .loadout
909 .swap(equip_slot, None, time)
910 .and_then(|mut unequipped_item| {
911 let unloaded_items: Vec<Item> = unequipped_item.drain().collect();
912 self.push(unequipped_item)
913 .expect("Failed to push item to inventory, precondition failed?");
914
915 match self.push_all(unloaded_items.into_iter()) {
918 Err(Error::Full(leftovers)) => Some(leftovers),
919 Ok(()) => None,
920 }
921 }))
922 }
923
924 pub fn free_after_unequip(&self, equip_slot: EquipSlot) -> i32 {
927 let (inv_slot_for_unequipped, slots_from_equipped) = self
928 .equipped(equip_slot)
929 .map_or((0, 0), |item| (1, item.slots().len()));
930
931 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
932 - i32::try_from(slots_from_equipped)
933 .expect("Equipped item with more than i32::MAX slots")
934 - i32::try_from(self.populated_slots())
935 .expect("Inventory item with more than i32::MAX used slots")
936 - inv_slot_for_unequipped }
938
939 #[must_use = "Returned items will be lost if not used"]
942 pub fn swap(&mut self, slot_a: Slot, slot_b: Slot, time: Time) -> Vec<Item> {
943 match (slot_a, slot_b) {
944 (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
945 self.swap_slots(slot_a, slot_b);
946 Vec::new()
947 },
948 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
949 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
950 self.swap_inventory_loadout(inv_slot, equip_slot, time)
951 },
952 (Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
953 self.loadout.swap_slots(slot_a, slot_b, time);
954 Vec::new()
955 },
956 (Slot::Overflow(overflow_slot), Slot::Inventory(inv_slot))
957 | (Slot::Inventory(inv_slot), Slot::Overflow(overflow_slot)) => {
958 self.move_overflow_item(overflow_slot, inv_slot);
959 Vec::new()
960 },
961 (Slot::Overflow(_), Slot::Equip(_)) | (Slot::Equip(_), Slot::Overflow(_)) => Vec::new(),
963 (Slot::Overflow(_), Slot::Overflow(_)) => Vec::new(),
965 }
966 }
967
968 pub fn free_after_swap(&self, equip_slot: EquipSlot, inv_slot: InvSlotId) -> i32 {
971 let (inv_slot_for_equipped, slots_from_equipped) = self
972 .equipped(equip_slot)
973 .map_or((0, 0), |item| (1, item.slots().len()));
974 let (inv_slot_for_inv_item, slots_from_inv_item) = self
975 .get(inv_slot)
976 .map_or((0, 0), |item| (1, item.slots().len()));
977
978 i32::try_from(self.capacity())
981 .expect("inventory with more than i32::MAX slots")
982 - i32::try_from(slots_from_equipped)
983 .expect("equipped item with more than i32::MAX slots")
984 + i32::try_from(slots_from_inv_item)
985 .expect("inventory item with more than i32::MAX slots")
986 - i32::try_from(self.populated_slots())
987 .expect("inventory with more than i32::MAX used slots")
988 - inv_slot_for_equipped + inv_slot_for_inv_item }
991
992 #[must_use = "Returned items will be lost if not used"]
994 pub fn swap_inventory_loadout(
995 &mut self,
996 inv_slot_id: InvSlotId,
997 equip_slot: EquipSlot,
998 time: Time,
999 ) -> Vec<Item> {
1000 if !self.can_swap(inv_slot_id, equip_slot) {
1001 return Vec::new();
1002 }
1003
1004 let from_inv = self.remove(inv_slot_id);
1006
1007 let from_equip = self.loadout.swap(equip_slot, from_inv, time);
1009
1010 let unloaded_items = from_equip
1011 .map(|mut from_equip| {
1012 let mut items: Vec<Item> = from_equip.drain().collect();
1014
1015 if let Err(returned) = self.insert_at(inv_slot_id, from_equip) {
1020 items.insert(0, returned);
1021 }
1022
1023 items
1024 })
1025 .unwrap_or_default();
1026
1027 match equip_slot {
1030 EquipSlot::ActiveMainhand => {
1031 if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
1032 && self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
1033 {
1034 let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None, time);
1035 assert!(
1036 self.loadout
1037 .swap(EquipSlot::ActiveMainhand, offhand, time)
1038 .is_none()
1039 );
1040 }
1041 },
1042 EquipSlot::InactiveMainhand => {
1043 if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
1044 && self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
1045 {
1046 let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None, time);
1047 assert!(
1048 self.loadout
1049 .swap(EquipSlot::InactiveMainhand, offhand, time)
1050 .is_none()
1051 );
1052 }
1053 },
1054 _ => {},
1055 }
1056
1057 match self.push_all(unloaded_items.into_iter()) {
1061 Err(Error::Full(leftovers)) => leftovers,
1062 Ok(()) => Vec::new(),
1063 }
1064 }
1065
1066 pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
1071 if !self
1073 .get(inv_slot_id)
1074 .is_none_or(|item| self.loadout.slot_can_hold(equip_slot, Some(&*item.kind())))
1075 {
1076 trace!("can_swap = false, equip slot can't hold item");
1077 return false;
1078 }
1079
1080 if self.slot(inv_slot_id).is_none() {
1081 debug!(
1082 "can_swap = false, tried to swap into non-existent inventory slot: {:?}",
1083 inv_slot_id
1084 );
1085 return false;
1086 }
1087
1088 true
1089 }
1090
1091 pub fn equipped_items_replaceable_by<'a>(
1092 &'a self,
1093 item_kind: &'a ItemKind,
1094 ) -> impl Iterator<Item = &'a Item> {
1095 self.loadout.equipped_items_replaceable_by(item_kind)
1096 }
1097
1098 pub fn swap_equipped_weapons(&mut self, time: Time) { self.loadout.swap_equipped_weapons(time) }
1099
1100 pub fn persistence_update_all_item_states(
1103 &mut self,
1104 ability_map: &AbilityMap,
1105 msm: &MaterialStatManifest,
1106 ) {
1107 self.slots_mut().for_each(|slot| {
1108 if let Some(item) = slot {
1109 item.update_item_state(ability_map, msm);
1110 }
1111 });
1112 self.overflow_items
1113 .iter_mut()
1114 .for_each(|item| item.update_item_state(ability_map, msm));
1115 }
1116
1117 pub fn damage_items(
1120 &mut self,
1121 ability_map: &item::tool::AbilityMap,
1122 msm: &item::MaterialStatManifest,
1123 time: Time,
1124 ) {
1125 self.loadout.damage_items(ability_map, msm);
1126 self.loadout.cull_recently_unequipped_items(time);
1127
1128 let (slots_mut, recently_unequipped_items) =
1129 self.slots_mut_with_mutable_recently_unequipped_items();
1130 slots_mut.filter_map(|slot| slot.as_mut()).for_each(|item| {
1131 if item
1132 .durability_lost()
1133 .is_some_and(|dur| dur < Item::MAX_DURABILITY)
1134 && let Some((_unequip_time, count)) =
1135 recently_unequipped_items.get_mut(&item.item_definition_id())
1136 && *count > 0
1137 {
1138 *count -= 1;
1139 item.increment_damage(ability_map, msm);
1140 }
1141 });
1142 }
1143
1144 pub fn repair_item_at_slot(
1146 &mut self,
1147 slot: Slot,
1148 ability_map: &item::tool::AbilityMap,
1149 msm: &item::MaterialStatManifest,
1150 ) {
1151 match slot {
1152 Slot::Inventory(invslot) => {
1153 if let Some(Some(item)) = self.slot_mut(invslot) {
1154 item.reset_durability(ability_map, msm);
1155 }
1156 },
1157 Slot::Equip(equip_slot) => {
1158 self.loadout
1159 .repair_item_at_slot(equip_slot, ability_map, msm);
1160 },
1161 Slot::Overflow(_) => {},
1163 }
1164 }
1165
1166 pub fn persistence_push_overflow_items<I: Iterator<Item = Item>>(&mut self, overflow_items: I) {
1170 self.overflow_items.extend(overflow_items);
1171 }
1172
1173 pub fn recipes_iter(&self) -> impl ExactSizeIterator<Item = &String> { self.recipe_book.iter() }
1174
1175 pub fn recipe_groups_iter(&self) -> impl ExactSizeIterator<Item = &Item> {
1176 self.recipe_book.iter_groups()
1177 }
1178
1179 pub fn available_recipes_iter<'a>(
1180 &'a self,
1181 rbm: &'a RecipeBookManifest,
1182 ) -> impl Iterator<Item = (&'a String, &'a Recipe)> + 'a {
1183 self.recipe_book.get_available_iter(rbm)
1184 }
1185
1186 pub fn recipe_book_len(&self) -> usize { self.recipe_book.len() }
1187
1188 pub fn get_recipe<'a>(
1189 &'a self,
1190 recipe_key: &str,
1191 rbm: &'a RecipeBookManifest,
1192 ) -> Option<&'a Recipe> {
1193 self.recipe_book.get(recipe_key, rbm)
1194 }
1195
1196 pub fn push_recipe_group(&mut self, recipe_group: Item) -> Result<(), Item> {
1197 self.recipe_book.push_group(recipe_group)
1198 }
1199
1200 pub fn can_craft_recipe(
1203 &self,
1204 recipe_key: &str,
1205 amount: u32,
1206 rbm: &RecipeBookManifest,
1207 ) -> (bool, Option<SpriteKind>) {
1208 if let Some(recipe) = self.recipe_book.get(recipe_key, rbm) {
1209 (
1210 recipe.inventory_contains_ingredients(self, amount).is_ok(),
1211 recipe.craft_sprite,
1212 )
1213 } else {
1214 (false, None)
1215 }
1216 }
1217
1218 pub fn recipe_is_known(&self, recipe_key: &str) -> bool {
1219 self.recipe_book.is_known(recipe_key)
1220 }
1221
1222 pub fn reset_recipes(&mut self) { self.recipe_book.reset(); }
1223
1224 pub fn persistence_recipes_iter_with_index(&self) -> impl Iterator<Item = (usize, &Item)> {
1225 self.recipe_book.persistence_recipes_iter_with_index()
1226 }
1227}
1228
1229impl Component for Inventory {
1230 type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
1231}
1232
1233#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
1234pub enum CollectFailedReason {
1235 InventoryFull,
1236 LootOwned {
1237 owner: LootOwnerKind,
1238 expiry_secs: u64,
1239 },
1240}
1241
1242#[derive(Clone, Debug, Serialize, Deserialize)]
1243pub enum InventoryUpdateEvent {
1244 Init,
1245 Used,
1246 Consumed(ItemKey),
1247 Gave,
1248 Given,
1249 Swapped,
1250 Dropped,
1251 Collected(FrontendItem),
1252 BlockCollectFailed {
1253 pos: Vec3<i32>,
1254 reason: CollectFailedReason,
1255 },
1256 EntityCollectFailed {
1257 entity: Uid,
1258 reason: CollectFailedReason,
1259 },
1260 Possession,
1261 Debug,
1262 Craft,
1263}
1264
1265impl Default for InventoryUpdateEvent {
1266 fn default() -> Self { Self::Init }
1267}
1268
1269#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1270pub struct InventoryUpdate {
1271 events: Vec<InventoryUpdateEvent>,
1272}
1273
1274impl InventoryUpdate {
1275 pub fn new(event: InventoryUpdateEvent) -> Self {
1276 Self {
1277 events: vec![event],
1278 }
1279 }
1280
1281 pub fn push(&mut self, event: InventoryUpdateEvent) { self.events.push(event); }
1282
1283 pub fn take_events(&mut self) -> Vec<InventoryUpdateEvent> { std::mem::take(&mut self.events) }
1284}
1285
1286impl Component for InventoryUpdate {
1287 type Storage = specs::VecStorage<Self>;
1290}