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 loadout: Loadout,
50 slots: Vec<InvSlot>,
53 overflow_items: Vec<Item>,
58 recipe_book: RecipeBook,
60}
61
62#[derive(Debug)]
64pub enum Error {
65 Full(Vec<Item>),
68}
69
70impl Error {
71 pub fn returned_items(self) -> impl Iterator<Item = Item> {
72 match self {
73 Error::Full(items) => items.into_iter(),
74 }
75 }
76}
77
78#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
79pub enum InventorySortOrder {
80 Name,
81 Quality,
82 Category,
83 Tag,
84 Amount,
85}
86
87impl InventorySortOrder {
88 pub fn next(&self) -> InventorySortOrder {
89 match self {
90 InventorySortOrder::Name => InventorySortOrder::Quality,
91 InventorySortOrder::Quality => InventorySortOrder::Tag,
92 InventorySortOrder::Tag => InventorySortOrder::Category,
93 InventorySortOrder::Category => InventorySortOrder::Amount,
94 InventorySortOrder::Amount => InventorySortOrder::Name,
95 }
96 }
97}
98
99pub enum CustomOrder {
100 Name,
101 Quality,
102 KindPartial,
103 KindFull,
104 Tag,
105}
106
107impl Inventory {
121 pub fn with_empty() -> Inventory {
122 Self::with_loadout_humanoid(LoadoutBuilder::empty().build())
123 }
124
125 pub fn with_loadout(loadout: Loadout, body: Body) -> Inventory {
126 if let Body::Humanoid(_) = body {
127 Self::with_loadout_humanoid(loadout)
128 } else {
129 Self::with_loadout_animal(loadout)
130 }
131 }
132
133 pub fn with_loadout_humanoid(loadout: Loadout) -> Inventory {
134 Inventory {
135 loadout,
136 slots: vec![None; DEFAULT_INVENTORY_SLOTS],
137 overflow_items: Vec::new(),
138 recipe_book: RecipeBook::default(),
139 }
140 }
141
142 pub fn with_loadout_animal(loadout: Loadout) -> Inventory {
143 Inventory {
144 loadout,
145 slots: vec![None; 1],
146 overflow_items: Vec::new(),
147 recipe_book: RecipeBook::default(),
148 }
149 }
150
151 pub fn with_recipe_book(mut self, recipe_book: RecipeBook) -> Inventory {
152 self.recipe_book = recipe_book;
153 self
154 }
155
156 pub fn capacity(&self) -> usize { self.slots().count() }
158
159 pub fn slots(&self) -> impl Iterator<Item = &InvSlot> {
161 self.slots
162 .iter()
163 .chain(self.loadout.inv_slots_with_id().map(|(_, slot)| slot))
164 }
165
166 pub fn overflow_items(&self) -> impl Iterator<Item = &Item> { self.overflow_items.iter() }
168
169 fn slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
171 self.slots.iter_mut().chain(self.loadout.inv_slots_mut())
172 }
173
174 fn slots_mut_with_mutable_recently_unequipped_items(
175 &mut self,
176 ) -> (
177 impl Iterator<Item = &mut InvSlot>,
178 &mut HashMap<ItemDefinitionIdOwned, (Time, u8)>,
179 ) {
180 let (slots_mut, recently_unequipped) = self
181 .loadout
182 .inv_slots_mut_with_mutable_recently_unequipped_items();
183 (self.slots.iter_mut().chain(slots_mut), recently_unequipped)
184 }
185
186 pub fn slots_with_id(&self) -> impl Iterator<Item = (InvSlotId, &InvSlot)> {
188 self.slots
189 .iter()
190 .enumerate()
191 .map(|(i, slot)| ((InvSlotId::new(0, u16::try_from(i).unwrap())), slot))
192 .chain(
193 self.loadout
194 .inv_slots_with_id()
195 .map(|(loadout_slot_id, inv_slot)| (loadout_slot_id.into(), inv_slot)),
196 )
197 }
198
199 pub fn order_by_custom(custom_order: &[CustomOrder], a: &Item, b: &Item) -> Ordering {
201 let mut order = custom_order.iter();
202 let a_quality = a.quality();
203 let b_quality = b.quality();
204 let a_kind = a.kind().get_itemkind_string();
205 let b_kind = b.kind().get_itemkind_string();
206 let mut cmp = Ordering::Equal;
207 while cmp == Ordering::Equal {
208 match order.next() {
209 Some(CustomOrder::KindFull) => cmp = Ord::cmp(&a_kind, &b_kind),
210 Some(CustomOrder::KindPartial) => {
211 cmp = Ord::cmp(
212 &a_kind.split_once(':').unwrap().0,
213 &b_kind.split_once(':').unwrap().0,
214 )
215 },
216 Some(CustomOrder::Quality) => cmp = Ord::cmp(&b_quality, &a_quality),
217 #[expect(deprecated)]
220 Some(CustomOrder::Name) => cmp = Ord::cmp(&a.legacy_name(), &b.legacy_name()),
221 Some(CustomOrder::Tag) => {
222 cmp = Ord::cmp(
223 &a.tags().first().map_or("", |tag| tag.name()),
224 &b.tags().first().map_or("", |tag| tag.name()),
225 )
226 },
227 _ => break,
228 }
229 }
230 cmp
231 }
232
233 pub fn sort(&mut self, sort_order: InventorySortOrder) {
235 let mut items: Vec<Item> = self.slots_mut().filter_map(mem::take).collect();
236
237 #[expect(deprecated)]
242 items.sort_by(|a, b| Ord::cmp(&a.legacy_name(), &b.legacy_name()));
243
244 items.sort_by(|a, b| match sort_order {
245 #[expect(deprecated)]
246 InventorySortOrder::Name => Ord::cmp(&a.legacy_name(), &b.legacy_name()),
247 InventorySortOrder::Quality => Ord::cmp(&b.quality(), &a.quality()),
249 InventorySortOrder::Category => {
250 let order = [
251 CustomOrder::KindPartial,
252 CustomOrder::Quality,
253 CustomOrder::KindFull,
254 CustomOrder::Name,
255 ];
256 Self::order_by_custom(&order, a, b)
257 },
258 InventorySortOrder::Tag => Ord::cmp(
259 &a.tags().first().map_or("", |tag| tag.name()),
260 &b.tags().first().map_or("", |tag| tag.name()),
261 ),
262 InventorySortOrder::Amount => Ord::cmp(&b.amount(), &a.amount()),
264 });
265
266 self.push_all(items.into_iter()).expect(
267 "It is impossible for there to be insufficient inventory space when sorting the \
268 inventory",
269 );
270 }
271
272 fn push_prefer_slot(
276 &mut self,
277 mut item: Item,
278 slot: Option<InvSlotId>,
279 ) -> 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_prefer_slot(item, slot)
324 .map_err(|item| (item, NonZeroU32::new(total_amount - remaining)))
325 } else {
326 Ok(())
327 }
328 } else {
329 self.insert_prefer_slot(item, slot)
331 .map_err(|item| (item, None))
332 }
333 }
334
335 pub fn push(&mut self, item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
342 self.push_prefer_slot(item, None)
343 }
344
345 pub fn push_all<I: Iterator<Item = Item>>(&mut self, items: I) -> Result<(), Error> {
348 let mut leftovers = Vec::new();
350 for item in items {
351 if let Err((item, _)) = self.push(item) {
352 leftovers.push(item);
353 }
354 }
355 if !leftovers.is_empty() {
356 Err(Error::Full(leftovers))
357 } else {
358 Ok(())
359 }
360 }
361
362 pub fn push_all_unique<I: Iterator<Item = Item>>(&mut self, mut items: I) -> Result<(), Error> {
372 let mut leftovers = Vec::new();
373 for item in &mut items {
374 if self.contains(&item).not()
375 && let Err((overflow, _)) = self.push(item)
376 {
377 leftovers.push(overflow);
378 } }
380 if !leftovers.is_empty() {
381 Err(Error::Full(leftovers))
382 } else {
383 Ok(())
384 }
385 }
386
387 pub fn insert_at(&mut self, inv_slot_id: InvSlotId, item: Item) -> Result<Option<Item>, Item> {
390 match self.slot_mut(inv_slot_id) {
391 Some(slot) => Ok(slot.replace(item)),
392 None => Err(item),
393 }
394 }
395
396 pub fn merge_stack_into(&mut self, src: InvSlotId, dst: InvSlotId) -> bool {
399 let mut amount = None;
400 if let (Some(srcitem), Some(dstitem)) = (self.get(src), self.get(dst)) {
401 if srcitem == dstitem && srcitem.is_stackable() {
405 amount = Some(srcitem.amount());
406 }
407 }
408 if let Some(amount) = amount {
409 let dstitem = self
410 .get_mut(dst)
411 .expect("self.get(dst) was Some right above this");
412 dstitem
413 .increase_amount(amount)
414 .map(|_| {
415 self.remove(src).expect("Already verified that src was populated.");
417 })
418 .is_ok()
420 } else {
421 false
422 }
423 }
424
425 pub fn insert_or_stack_at(
428 &mut self,
429 inv_slot_id: InvSlotId,
430 item: Item,
431 ) -> Result<Option<Item>, Item> {
432 if item.is_stackable() {
433 match self.slot_mut(inv_slot_id) {
434 Some(Some(slot_item)) => {
435 Ok(if slot_item == &item {
436 slot_item
437 .increase_amount(item.amount())
438 .err()
439 .and(Some(item))
440 } else {
441 let old_item = mem::replace(slot_item, item);
442 Some(old_item)
444 })
445 },
446 Some(None) => self.insert_at(inv_slot_id, item),
447 None => Err(item),
448 }
449 } else {
450 self.insert_at(inv_slot_id, item)
451 }
452 }
453
454 #[must_use = "Returned item will be lost if not used"]
457 pub fn try_equip(&mut self, item: Item) -> Result<(), Item> { self.loadout.try_equip(item) }
458
459 pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() }
460
461 pub fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() }
462
463 pub fn contains(&self, item: &Item) -> bool {
465 self.slots().any(|slot| slot.as_ref() == Some(item))
466 }
467
468 pub fn get_slot_of_item(&self, item: &Item) -> Option<InvSlotId> {
470 self.slots_with_id()
471 .find(|&(_, it)| {
472 if let Some(it) = it {
473 it.item_definition_id() == item.item_definition_id()
474 } else {
475 false
476 }
477 })
478 .map(|(slot, _)| slot)
479 }
480
481 pub fn get_slot_of_item_by_def_id(
482 &self,
483 item_def_id: &item::ItemDefinitionIdOwned,
484 ) -> Option<InvSlotId> {
485 self.slots_with_id()
486 .find(|&(_, it)| {
487 if let Some(it) = it {
488 it.item_definition_id() == *item_def_id
489 } else {
490 false
491 }
492 })
493 .map(|(slot, _)| slot)
494 }
495
496 pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
498 self.slot(inv_slot_id).and_then(Option::as_ref)
499 }
500
501 pub fn get_overflow(&self, overflow: usize) -> Option<&Item> {
503 self.overflow_items.get(overflow)
504 }
505
506 pub fn get_slot(&self, slot: Slot) -> Option<&Item> {
508 match slot {
509 Slot::Inventory(inv_slot) => self.get(inv_slot),
510 Slot::Equip(equip) => self.equipped(equip),
511 Slot::Overflow(overflow) => self.get_overflow(overflow),
512 }
513 }
514
515 pub fn get_by_hash(&self, item_hash: u64) -> Option<&Item> {
517 self.slots().flatten().find(|i| i.item_hash() == item_hash)
518 }
519
520 pub fn get_slot_from_hash(&self, item_hash: u64) -> Option<InvSlotId> {
522 let slot_with_id = self.slots_with_id().find(|slot| match slot.1 {
523 None => false,
524 Some(item) => item.item_hash() == item_hash,
525 });
526 slot_with_id.map(|s| s.0)
527 }
528
529 fn get_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut Item> {
531 self.slot_mut(inv_slot_id).and_then(Option::as_mut)
532 }
533
534 pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
536 self.loadout.equipped(equip_slot)
537 }
538
539 pub fn loadout_items_with_persistence_key(
540 &self,
541 ) -> impl Iterator<Item = (&str, Option<&Item>)> {
542 self.loadout.items_with_persistence_key()
543 }
544
545 pub fn get_slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
549 let offset = self.slots.len();
552 self.loadout
553 .slot_range_for_equip_slot(equip_slot)
554 .map(|loadout_range| (loadout_range.start + offset)..(loadout_range.end + offset))
555 }
556
557 pub fn swap_slots(&mut self, a: InvSlotId, b: InvSlotId) {
559 if self.slot(a).is_none() || self.slot(b).is_none() {
560 warn!("swap_slots called with non-existent inventory slot(s)");
561 return;
562 }
563
564 let slot_a = mem::take(self.slot_mut(a).unwrap());
565 let slot_b = mem::take(self.slot_mut(b).unwrap());
566 *self.slot_mut(a).unwrap() = slot_b;
567 *self.slot_mut(b).unwrap() = slot_a;
568 }
569
570 pub fn move_overflow_item(&mut self, overflow: usize, inv_slot: InvSlotId) {
572 match self.slot(inv_slot) {
573 Some(Some(_)) => {
574 warn!("Attempted to move from overflow slot to a filled inventory slot");
575 return;
576 },
577 None => {
578 warn!("Attempted to move from overflow slot to a non-existent inventory slot");
579 return;
580 },
581 Some(None) => {},
582 };
583
584 let item = self.overflow_items.remove(overflow);
585 *self.slot_mut(inv_slot).unwrap() = Some(item);
586 }
587
588 pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
590 self.slot_mut(inv_slot_id).and_then(|item| item.take())
591 }
592
593 #[must_use = "Returned items will be lost if not used"]
595 pub fn overflow_remove(&mut self, overflow_slot: usize) -> Option<Item> {
596 if overflow_slot < self.overflow_items.len() {
597 Some(self.overflow_items.remove(overflow_slot))
598 } else {
599 None
600 }
601 }
602
603 pub fn take(
605 &mut self,
606 inv_slot_id: InvSlotId,
607 ability_map: &AbilityMap,
608 msm: &MaterialStatManifest,
609 ) -> Option<Item> {
610 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
611 let mut return_item = item.duplicate(ability_map, msm);
612
613 if item.is_stackable() && item.amount() > 1 {
614 item.decrease_amount(1).ok()?;
615 return_item
616 .set_amount(1)
617 .expect("Items duplicated from a stackable item must be stackable.");
618 Some(return_item)
619 } else {
620 self.remove(inv_slot_id)
621 }
622 } else {
623 None
624 }
625 }
626
627 pub fn take_amount(
630 &mut self,
631 inv_slot_id: InvSlotId,
632 amount: NonZeroU32,
633 ability_map: &AbilityMap,
634 msm: &MaterialStatManifest,
635 ) -> Option<Item> {
636 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
637 if item.is_stackable() && item.amount() > amount.get() {
638 let mut return_item = item.duplicate(ability_map, msm);
639 let return_amount = amount.get();
640 let new_amount = item.amount() - return_amount;
642
643 return_item
644 .set_amount(return_amount)
645 .expect("We know that 0 < return_amount < item.amount()");
646 item.set_amount(new_amount)
647 .expect("new_amount must be > 0 since return item is < item.amount");
648
649 Some(return_item)
650 } else {
651 self.remove(inv_slot_id)
654 }
655 } else {
656 None
657 }
658 }
659
660 #[must_use = "Returned items will be lost if not used"]
662 pub fn take_half(
663 &mut self,
664 inv_slot_id: InvSlotId,
665 ability_map: &AbilityMap,
666 msm: &MaterialStatManifest,
667 ) -> Option<Item> {
668 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
669 item.take_half(ability_map, msm)
670 .or_else(|| self.remove(inv_slot_id))
671 } else {
672 None
673 }
674 }
675
676 #[must_use = "Returned items will be lost if not used"]
678 pub fn overflow_take_half(
679 &mut self,
680 overflow_slot: usize,
681 ability_map: &AbilityMap,
682 msm: &MaterialStatManifest,
683 ) -> Option<Item> {
684 if let Some(item) = self.overflow_items.get_mut(overflow_slot) {
685 item.take_half(ability_map, msm)
686 .or_else(|| self.overflow_remove(overflow_slot))
687 } else {
688 None
689 }
690 }
691
692 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
694 self.slots_mut()
695 .filter(|x| x.is_some())
696 .filter_map(mem::take)
697 }
698
699 pub fn item_count(&self, item_def: &ItemDef) -> u64 {
701 self.slots()
702 .flatten()
703 .filter(|it| it.is_same_item_def(item_def))
704 .map(|it| u64::from(it.amount()))
705 .sum()
706 }
707
708 pub fn has_space_for(&self, item_def: &ItemDef, amount: u32) -> bool {
711 let mut free_space = 0u32;
712 self.slots().any(|i| {
713 free_space = free_space.saturating_add(if let Some(item) = i {
714 if item.is_same_item_def(item_def) {
715 item.max_amount().saturating_sub(item.amount())
718 } else {
719 0
720 }
721 } else {
722 item_def.max_amount()
724 });
725 free_space >= amount
726 })
727 }
728
729 pub fn can_stack(&self, item: &Item) -> bool {
732 let mut free_space = 0u32;
733 self.slots().any(|i| {
734 free_space = free_space.saturating_add(if let Some(inv_item) = i {
735 if inv_item == item {
736 inv_item.max_amount().saturating_sub(inv_item.amount())
739 } else {
740 0
741 }
742 } else {
743 0
744 });
745 free_space >= item.amount()
746 })
747 }
748
749 pub fn remove_item_amount(
757 &mut self,
758 item_def: &ItemDef,
759 amount: u32,
760 ability_map: &AbilityMap,
761 msm: &MaterialStatManifest,
762 ) -> Option<Vec<Item>> {
763 let mut amount = amount;
764 if self.item_count(item_def) >= u64::from(amount) {
765 let mut removed_items = Vec::new();
766 for slot in self.slots_mut() {
767 if amount == 0 {
768 return Some(removed_items);
770 } else if let Some(item) = slot
771 && item.is_same_item_def(item_def)
772 {
773 if amount < item.amount() {
774 removed_items.push(item.take_amount(ability_map, msm, amount).unwrap());
777 return Some(removed_items);
778 } else {
779 amount -= item.amount();
781 removed_items.push(slot.take().unwrap());
782 }
783 }
784 }
785 debug_assert_eq!(amount, 0);
786 Some(removed_items)
787 } else {
788 None
789 }
790 }
791
792 fn insert_prefer_slot(&mut self, item: Item, slot: Option<InvSlotId>) -> Result<(), Item> {
796 if let Some(slot @ None) = slot.and_then(|slot| self.slot_mut(slot)) {
797 *slot = Some(item);
798 Ok(())
799 } else {
800 self.insert(item)
801 }
802 }
803
804 fn insert(&mut self, item: Item) -> Result<(), Item> {
807 match self.slots_mut().find(|slot| slot.is_none()) {
808 Some(slot) => {
809 *slot = Some(item);
810 Ok(())
811 },
812 None => Err(item),
813 }
814 }
815
816 pub fn slot(&self, inv_slot_id: InvSlotId) -> Option<&InvSlot> {
817 match SlotId::from(inv_slot_id) {
818 SlotId::Inventory(slot_idx) => self.slots.get(slot_idx),
819 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot(loadout_slot_id),
820 }
821 }
822
823 pub fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> {
824 match SlotId::from(inv_slot_id) {
825 SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx),
826 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot_mut(loadout_slot_id),
827 }
828 }
829
830 pub fn free_slots_minus_equipped_item(&self, equip_slot: EquipSlot) -> usize {
833 if let Some(mut equip_slot_idx) = self.loadout.loadout_idx_for_equip_slot(equip_slot) {
834 equip_slot_idx += 1;
836
837 self.slots_with_id()
838 .filter(|(inv_slot_id, slot)| {
839 inv_slot_id.loadout_idx() != equip_slot_idx && slot.is_none()
840 })
841 .count()
842 } else {
843 warn!(
845 "Attempted to fetch loadout index for non-existent EquipSlot: {:?}",
846 equip_slot
847 );
848 0
849 }
850 }
851
852 pub fn equipped_items(&self) -> impl Iterator<Item = &Item> { self.loadout.items() }
853
854 pub fn equipped_items_with_slot(&self) -> impl Iterator<Item = (EquipSlot, &Item)> {
855 self.loadout.items_with_slot()
856 }
857
858 pub fn replace_loadout_item(
861 &mut self,
862 equip_slot: EquipSlot,
863 replacement_item: Option<Item>,
864 time: Time,
865 ) -> Option<Item> {
866 self.loadout.swap(equip_slot, replacement_item, time)
867 }
868
869 #[must_use = "Returned items will be lost if not used"]
875 pub fn equip(
876 &mut self,
877 inv_slot: InvSlotId,
878 time: Time,
879 ability_map: &AbilityMap,
880 msm: &MaterialStatManifest,
881 ) -> Result<Option<Vec<Item>>, SlotError> {
882 let Some(item) = self.get(inv_slot) else {
883 return Ok(None);
884 };
885
886 let Some(equip_slot) = self.loadout.get_slot_to_equip_into(item) else {
887 return Ok(None);
888 };
889
890 let item = self
891 .take(inv_slot, ability_map, msm)
892 .expect("We got this successfully above");
893
894 if let Some(mut unequipped_item) = self.replace_loadout_item(equip_slot, Some(item), time) {
895 let mut unloaded_items: Vec<Item> = unequipped_item.drain().collect();
896 if let Err((item, _)) = self.push_prefer_slot(unequipped_item, Some(inv_slot)) {
897 unloaded_items.insert(0, item);
899 }
900 match self.push_all(unloaded_items.into_iter()) {
903 Ok(()) => {},
904 Err(Error::Full(items)) => return Ok(Some(items)),
905 }
906 }
907
908 Ok(None)
909 }
910
911 pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
915 let (inv_slot_for_equipped, slots_from_equipped) = self
916 .get(inv_slot)
917 .and_then(|item| self.loadout.get_slot_to_equip_into(item))
918 .and_then(|equip_slot| self.equipped(equip_slot))
919 .map_or((1, 0), |item| {
920 (
921 if item.is_stackable() && self.can_stack(item) {
922 1
923 } else {
924 0
925 },
926 item.slots().len(),
927 )
928 });
929
930 let (inv_slot_for_inv, slots_from_inv) = self.get(inv_slot).map_or((0, 0), |item| {
931 (if item.amount() > 1 { -1 } else { 0 }, item.slots().len())
932 });
933
934 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
935 - i32::try_from(slots_from_equipped)
936 .expect("Equipped item with more than i32::MAX slots")
937 + i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
938 - i32::try_from(self.populated_slots())
939 .expect("Inventory item with more than i32::MAX used slots")
940 + inv_slot_for_equipped + inv_slot_for_inv
942 }
943
944 pub fn pickup_item(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
952 if item.is_stackable() {
953 return self.push(item);
954 }
955
956 if self.free_slots() < item.populated_slots() + 1 {
957 return Err((item, None));
958 }
959
960 item.drain().for_each(|item| {
964 self.push(item).unwrap();
965 });
966 self.push(item)
967 }
968
969 #[must_use = "Returned items will be lost if not used"]
972 #[expect(clippy::needless_collect)] pub fn unequip(
974 &mut self,
975 equip_slot: EquipSlot,
976 time: Time,
977 ) -> Result<Option<Vec<Item>>, SlotError> {
978 if self.free_slots_minus_equipped_item(equip_slot) == 0 {
980 return Err(SlotError::InventoryFull);
981 }
982
983 Ok(self
984 .loadout
985 .swap(equip_slot, None, time)
986 .and_then(|mut unequipped_item| {
987 let unloaded_items: Vec<Item> = unequipped_item.drain().collect();
988 self.push(unequipped_item)
989 .expect("Failed to push item to inventory, precondition failed?");
990
991 match self.push_all(unloaded_items.into_iter()) {
994 Err(Error::Full(leftovers)) => Some(leftovers),
995 Ok(()) => None,
996 }
997 }))
998 }
999
1000 pub fn free_after_unequip(&self, equip_slot: EquipSlot) -> i32 {
1003 let (inv_slot_for_unequipped, slots_from_equipped) = self
1004 .equipped(equip_slot)
1005 .map_or((0, 0), |item| (1, item.slots().len()));
1006
1007 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
1008 - i32::try_from(slots_from_equipped)
1009 .expect("Equipped item with more than i32::MAX slots")
1010 - i32::try_from(self.populated_slots())
1011 .expect("Inventory item with more than i32::MAX used slots")
1012 - inv_slot_for_unequipped }
1014
1015 #[must_use = "Returned items will be lost if not used"]
1018 pub fn swap(&mut self, slot_a: Slot, slot_b: Slot, time: Time) -> Vec<Item> {
1019 match (slot_a, slot_b) {
1020 (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
1021 self.swap_slots(slot_a, slot_b);
1022 Vec::new()
1023 },
1024 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1025 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1026 self.swap_inventory_loadout(inv_slot, equip_slot, time)
1027 },
1028 (Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
1029 self.loadout.swap_slots(slot_a, slot_b, time);
1030 Vec::new()
1031 },
1032 (Slot::Overflow(overflow_slot), Slot::Inventory(inv_slot))
1033 | (Slot::Inventory(inv_slot), Slot::Overflow(overflow_slot)) => {
1034 self.move_overflow_item(overflow_slot, inv_slot);
1035 Vec::new()
1036 },
1037 (Slot::Overflow(_), Slot::Equip(_)) | (Slot::Equip(_), Slot::Overflow(_)) => Vec::new(),
1039 (Slot::Overflow(_), Slot::Overflow(_)) => Vec::new(),
1041 }
1042 }
1043
1044 pub fn free_after_swap(&self, equip_slot: EquipSlot, inv_slot: InvSlotId) -> i32 {
1047 let (inv_slot_for_equipped, slots_from_equipped) = self
1048 .equipped(equip_slot)
1049 .map_or((0, 0), |item| (1, item.slots().len()));
1050 let (inv_slot_for_inv_item, slots_from_inv_item) = self
1051 .get(inv_slot)
1052 .map_or((0, 0), |item| (1, item.slots().len()));
1053
1054 i32::try_from(self.capacity())
1057 .expect("inventory with more than i32::MAX slots")
1058 - i32::try_from(slots_from_equipped)
1059 .expect("equipped item with more than i32::MAX slots")
1060 + i32::try_from(slots_from_inv_item)
1061 .expect("inventory item with more than i32::MAX slots")
1062 - i32::try_from(self.populated_slots())
1063 .expect("inventory with more than i32::MAX used slots")
1064 - inv_slot_for_equipped + inv_slot_for_inv_item }
1067
1068 #[must_use = "Returned items will be lost if not used"]
1070 pub fn swap_inventory_loadout(
1071 &mut self,
1072 inv_slot_id: InvSlotId,
1073 equip_slot: EquipSlot,
1074 time: Time,
1075 ) -> Vec<Item> {
1076 if !self.can_swap(inv_slot_id, equip_slot) {
1077 return Vec::new();
1078 }
1079
1080 let from_inv = self.remove(inv_slot_id);
1082
1083 let from_equip = self.loadout.swap(equip_slot, from_inv, time);
1085
1086 let unloaded_items = from_equip
1087 .map(|mut from_equip| {
1088 let mut items: Vec<Item> = from_equip.drain().collect();
1090
1091 if let Err(returned) = self.insert_at(inv_slot_id, from_equip) {
1096 items.insert(0, returned);
1097 }
1098
1099 items
1100 })
1101 .unwrap_or_default();
1102
1103 match equip_slot {
1106 EquipSlot::ActiveMainhand => {
1107 if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
1108 && self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
1109 {
1110 let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None, time);
1111 assert!(
1112 self.loadout
1113 .swap(EquipSlot::ActiveMainhand, offhand, time)
1114 .is_none()
1115 );
1116 }
1117 },
1118 EquipSlot::InactiveMainhand => {
1119 if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
1120 && self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
1121 {
1122 let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None, time);
1123 assert!(
1124 self.loadout
1125 .swap(EquipSlot::InactiveMainhand, offhand, time)
1126 .is_none()
1127 );
1128 }
1129 },
1130 _ => {},
1131 }
1132
1133 match self.push_all(unloaded_items.into_iter()) {
1137 Err(Error::Full(leftovers)) => leftovers,
1138 Ok(()) => Vec::new(),
1139 }
1140 }
1141
1142 pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
1147 if !self
1149 .get(inv_slot_id)
1150 .is_none_or(|item| self.loadout.slot_can_hold(equip_slot, Some(&*item.kind())))
1151 {
1152 trace!("can_swap = false, equip slot can't hold item");
1153 return false;
1154 }
1155
1156 if self.slot(inv_slot_id).is_none() {
1157 debug!(
1158 "can_swap = false, tried to swap into non-existent inventory slot: {:?}",
1159 inv_slot_id
1160 );
1161 return false;
1162 }
1163
1164 if self.get(inv_slot_id).is_some_and(|item| item.amount() > 1) {
1165 trace!("can_swap = false, equip slot can't hold more than one item");
1166 return false;
1167 }
1168
1169 true
1170 }
1171
1172 pub fn equipped_items_replaceable_by<'a>(
1173 &'a self,
1174 item_kind: &'a ItemKind,
1175 ) -> impl Iterator<Item = &'a Item> {
1176 self.loadout.equipped_items_replaceable_by(item_kind)
1177 }
1178
1179 pub fn swap_equipped_weapons(&mut self, time: Time) { self.loadout.swap_equipped_weapons(time) }
1180
1181 pub fn persistence_update_all_item_states(
1184 &mut self,
1185 ability_map: &AbilityMap,
1186 msm: &MaterialStatManifest,
1187 ) {
1188 self.slots_mut().for_each(|slot| {
1189 if let Some(item) = slot {
1190 item.update_item_state(ability_map, msm);
1191 }
1192 });
1193 self.overflow_items
1194 .iter_mut()
1195 .for_each(|item| item.update_item_state(ability_map, msm));
1196 }
1197
1198 pub fn damage_items(
1201 &mut self,
1202 ability_map: &item::tool::AbilityMap,
1203 msm: &item::MaterialStatManifest,
1204 time: Time,
1205 ) {
1206 self.loadout.damage_items(ability_map, msm);
1207 self.loadout.cull_recently_unequipped_items(time);
1208
1209 let (slots_mut, recently_unequipped_items) =
1210 self.slots_mut_with_mutable_recently_unequipped_items();
1211 slots_mut.filter_map(|slot| slot.as_mut()).for_each(|item| {
1212 if item
1213 .durability_lost()
1214 .is_some_and(|dur| dur < Item::MAX_DURABILITY)
1215 && let Some((_unequip_time, count)) =
1216 recently_unequipped_items.get_mut(&item.item_definition_id())
1217 && *count > 0
1218 {
1219 *count -= 1;
1220 item.increment_damage(ability_map, msm);
1221 }
1222 });
1223 }
1224
1225 pub fn repair_item_at_slot(
1227 &mut self,
1228 slot: Slot,
1229 ability_map: &item::tool::AbilityMap,
1230 msm: &item::MaterialStatManifest,
1231 ) {
1232 match slot {
1233 Slot::Inventory(invslot) => {
1234 if let Some(Some(item)) = self.slot_mut(invslot) {
1235 item.reset_durability(ability_map, msm);
1236 }
1237 },
1238 Slot::Equip(equip_slot) => {
1239 self.loadout
1240 .repair_item_at_slot(equip_slot, ability_map, msm);
1241 },
1242 Slot::Overflow(_) => {},
1244 }
1245 }
1246
1247 pub fn persistence_push_overflow_items<I: Iterator<Item = Item>>(&mut self, overflow_items: I) {
1251 self.overflow_items.extend(overflow_items);
1252 }
1253
1254 pub fn recipes_iter(&self) -> impl ExactSizeIterator<Item = &String> { self.recipe_book.iter() }
1255
1256 pub fn recipe_groups_iter(&self) -> impl ExactSizeIterator<Item = &Item> {
1257 self.recipe_book.iter_groups()
1258 }
1259
1260 pub fn available_recipes_iter<'a>(
1261 &'a self,
1262 rbm: &'a RecipeBookManifest,
1263 ) -> impl Iterator<Item = (&'a String, &'a Recipe)> + 'a {
1264 self.recipe_book.get_available_iter(rbm)
1265 }
1266
1267 pub fn recipe_book_len(&self) -> usize { self.recipe_book.len() }
1268
1269 pub fn get_recipe<'a>(
1270 &'a self,
1271 recipe_key: &str,
1272 rbm: &'a RecipeBookManifest,
1273 ) -> Option<&'a Recipe> {
1274 self.recipe_book.get(recipe_key, rbm)
1275 }
1276
1277 pub fn push_recipe_group(&mut self, recipe_group: Item) -> Result<(), Item> {
1278 self.recipe_book.push_group(recipe_group)
1279 }
1280
1281 pub fn can_craft_recipe(
1284 &self,
1285 recipe_key: &str,
1286 amount: u32,
1287 rbm: &RecipeBookManifest,
1288 ) -> (bool, Option<SpriteKind>) {
1289 if let Some(recipe) = self.recipe_book.get(recipe_key, rbm) {
1290 (
1291 recipe.inventory_contains_ingredients(self, amount).is_ok(),
1292 recipe.craft_sprite,
1293 )
1294 } else {
1295 (false, None)
1296 }
1297 }
1298
1299 pub fn recipe_is_known(&self, recipe_key: &str) -> bool {
1300 self.recipe_book.is_known(recipe_key)
1301 }
1302
1303 pub fn reset_recipes(&mut self) { self.recipe_book.reset(); }
1304
1305 pub fn persistence_recipes_iter_with_index(&self) -> impl Iterator<Item = (usize, &Item)> {
1306 self.recipe_book.persistence_recipes_iter_with_index()
1307 }
1308}
1309
1310impl Component for Inventory {
1311 type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
1312}
1313
1314#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
1315pub enum CollectFailedReason {
1316 InventoryFull,
1317 LootOwned {
1318 owner: LootOwnerKind,
1319 expiry_secs: u64,
1320 },
1321}
1322
1323#[derive(Clone, Debug, Serialize, Deserialize, Default)]
1324pub enum InventoryUpdateEvent {
1325 #[default]
1326 Init,
1327 Used,
1328 Consumed(ItemKey),
1329 Gave,
1330 Given,
1331 Swapped,
1332 Dropped,
1333 Collected(FrontendItem),
1334 BlockCollectFailed {
1335 pos: Vec3<i32>,
1336 reason: CollectFailedReason,
1337 },
1338 EntityCollectFailed {
1339 entity: Uid,
1340 reason: CollectFailedReason,
1341 },
1342 Possession,
1343 Debug,
1344 Craft,
1345}
1346
1347#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1348pub struct InventoryUpdate {
1349 events: Vec<InventoryUpdateEvent>,
1350}
1351
1352impl InventoryUpdate {
1353 pub fn new(event: InventoryUpdateEvent) -> Self {
1354 Self {
1355 events: vec![event],
1356 }
1357 }
1358
1359 pub fn push(&mut self, event: InventoryUpdateEvent) { self.events.push(event); }
1360
1361 pub fn take_events(&mut self) -> Vec<InventoryUpdateEvent> { std::mem::take(&mut self.events) }
1362}
1363
1364impl Component for InventoryUpdate {
1365 type Storage = specs::VecStorage<Self>;
1368}