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 fn push_prefer_slot(
277 &mut self,
278 mut item: Item,
279 slot: Option<InvSlotId>,
280 ) -> Result<(), (Item, Option<NonZeroU32>)> {
281 if item.is_stackable() {
286 let total_amount = item.amount();
287
288 let remaining = self
289 .slots_mut()
290 .filter_map(Option::as_mut)
291 .filter(|s| *s == &item)
292 .try_fold(total_amount, |remaining, current| {
293 debug_assert_eq!(
294 item.max_amount(),
295 current.max_amount(),
296 "max_amount of two equal items must match"
297 );
298
299 let new_remaining = remaining
302 .checked_sub(current.max_amount() - current.amount())
303 .filter(|&remaining| remaining > 0);
304 if new_remaining.is_some() {
305 current
308 .set_amount(current.max_amount())
309 .expect("max_amount() is always a valid amount");
310 } else {
311 current.increase_amount(remaining).expect(
313 "This item must be able to contain the remaining amount, because \
314 remaining < current.max_amount() - current.amount()",
315 );
316 }
317
318 new_remaining
319 });
320
321 if let Some(remaining) = remaining {
322 item.set_amount(remaining)
323 .expect("Remaining is known to be > 0");
324 self.insert_prefer_slot(item, slot)
325 .map_err(|item| (item, NonZeroU32::new(total_amount - remaining)))
326 } else {
327 Ok(())
328 }
329 } else {
330 self.insert_prefer_slot(item, slot)
332 .map_err(|item| (item, None))
333 }
334 }
335
336 pub fn push(&mut self, item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
343 self.push_prefer_slot(item, None)
344 }
345
346 pub fn push_all<I: Iterator<Item = Item>>(&mut self, items: I) -> Result<(), Error> {
349 let mut leftovers = Vec::new();
351 for item in items {
352 if let Err((item, _)) = self.push(item) {
353 leftovers.push(item);
354 }
355 }
356 if !leftovers.is_empty() {
357 Err(Error::Full(leftovers))
358 } else {
359 Ok(())
360 }
361 }
362
363 pub fn push_all_unique<I: Iterator<Item = Item>>(&mut self, mut items: I) -> Result<(), Error> {
373 let mut leftovers = Vec::new();
374 for item in &mut items {
375 if self.contains(&item).not() {
376 if let Err((overflow, _)) = self.push(item) {
377 leftovers.push(overflow);
378 }
379 } }
381 if !leftovers.is_empty() {
382 Err(Error::Full(leftovers))
383 } else {
384 Ok(())
385 }
386 }
387
388 pub fn insert_at(&mut self, inv_slot_id: InvSlotId, item: Item) -> Result<Option<Item>, Item> {
391 match self.slot_mut(inv_slot_id) {
392 Some(slot) => Ok(mem::replace(slot, Some(item))),
393 None => Err(item),
394 }
395 }
396
397 pub fn merge_stack_into(&mut self, src: InvSlotId, dst: InvSlotId) -> bool {
400 let mut amount = None;
401 if let (Some(srcitem), Some(dstitem)) = (self.get(src), self.get(dst)) {
402 if srcitem == dstitem && srcitem.is_stackable() {
406 amount = Some(srcitem.amount());
407 }
408 }
409 if let Some(amount) = amount {
410 let dstitem = self
411 .get_mut(dst)
412 .expect("self.get(dst) was Some right above this");
413 dstitem
414 .increase_amount(amount)
415 .map(|_| {
416 self.remove(src).expect("Already verified that src was populated.");
418 })
419 .is_ok()
421 } else {
422 false
423 }
424 }
425
426 pub fn insert_or_stack_at(
429 &mut self,
430 inv_slot_id: InvSlotId,
431 item: Item,
432 ) -> Result<Option<Item>, Item> {
433 if item.is_stackable() {
434 match self.slot_mut(inv_slot_id) {
435 Some(Some(slot_item)) => {
436 Ok(if slot_item == &item {
437 slot_item
438 .increase_amount(item.amount())
439 .err()
440 .and(Some(item))
441 } else {
442 let old_item = mem::replace(slot_item, item);
443 Some(old_item)
445 })
446 },
447 Some(None) => self.insert_at(inv_slot_id, item),
448 None => Err(item),
449 }
450 } else {
451 self.insert_at(inv_slot_id, item)
452 }
453 }
454
455 #[must_use = "Returned item will be lost if not used"]
458 pub fn try_equip(&mut self, item: Item) -> Result<(), Item> { self.loadout.try_equip(item) }
459
460 pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() }
461
462 pub fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() }
463
464 pub fn contains(&self, item: &Item) -> bool {
466 self.slots().any(|slot| slot.as_ref() == Some(item))
467 }
468
469 pub fn get_slot_of_item(&self, item: &Item) -> Option<InvSlotId> {
471 self.slots_with_id()
472 .find(|&(_, it)| {
473 if let Some(it) = it {
474 it.item_definition_id() == item.item_definition_id()
475 } else {
476 false
477 }
478 })
479 .map(|(slot, _)| slot)
480 }
481
482 pub fn get_slot_of_item_by_def_id(
483 &self,
484 item_def_id: &item::ItemDefinitionIdOwned,
485 ) -> Option<InvSlotId> {
486 self.slots_with_id()
487 .find(|&(_, it)| {
488 if let Some(it) = it {
489 it.item_definition_id() == *item_def_id
490 } else {
491 false
492 }
493 })
494 .map(|(slot, _)| slot)
495 }
496
497 pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
499 self.slot(inv_slot_id).and_then(Option::as_ref)
500 }
501
502 pub fn get_overflow(&self, overflow: usize) -> Option<&Item> {
504 self.overflow_items.get(overflow)
505 }
506
507 pub fn get_slot(&self, slot: Slot) -> Option<&Item> {
509 match slot {
510 Slot::Inventory(inv_slot) => self.get(inv_slot),
511 Slot::Equip(equip) => self.equipped(equip),
512 Slot::Overflow(overflow) => self.get_overflow(overflow),
513 }
514 }
515
516 pub fn get_by_hash(&self, item_hash: u64) -> Option<&Item> {
518 self.slots().flatten().find(|i| i.item_hash() == item_hash)
519 }
520
521 pub fn get_slot_from_hash(&self, item_hash: u64) -> Option<InvSlotId> {
523 let slot_with_id = self.slots_with_id().find(|slot| match slot.1 {
524 None => false,
525 Some(item) => item.item_hash() == item_hash,
526 });
527 slot_with_id.map(|s| s.0)
528 }
529
530 fn get_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut Item> {
532 self.slot_mut(inv_slot_id).and_then(Option::as_mut)
533 }
534
535 pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
537 self.loadout.equipped(equip_slot)
538 }
539
540 pub fn loadout_items_with_persistence_key(
541 &self,
542 ) -> impl Iterator<Item = (&str, Option<&Item>)> {
543 self.loadout.items_with_persistence_key()
544 }
545
546 pub fn get_slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
550 let offset = self.slots.len();
553 self.loadout
554 .slot_range_for_equip_slot(equip_slot)
555 .map(|loadout_range| (loadout_range.start + offset)..(loadout_range.end + offset))
556 }
557
558 pub fn swap_slots(&mut self, a: InvSlotId, b: InvSlotId) {
560 if self.slot(a).is_none() || self.slot(b).is_none() {
561 warn!("swap_slots called with non-existent inventory slot(s)");
562 return;
563 }
564
565 let slot_a = mem::take(self.slot_mut(a).unwrap());
566 let slot_b = mem::take(self.slot_mut(b).unwrap());
567 *self.slot_mut(a).unwrap() = slot_b;
568 *self.slot_mut(b).unwrap() = slot_a;
569 }
570
571 pub fn move_overflow_item(&mut self, overflow: usize, inv_slot: InvSlotId) {
573 match self.slot(inv_slot) {
574 Some(Some(_)) => {
575 warn!("Attempted to move from overflow slot to a filled inventory slot");
576 return;
577 },
578 None => {
579 warn!("Attempted to move from overflow slot to a non-existent inventory slot");
580 return;
581 },
582 Some(None) => {},
583 };
584
585 let item = self.overflow_items.remove(overflow);
586 *self.slot_mut(inv_slot).unwrap() = Some(item);
587 }
588
589 pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
591 self.slot_mut(inv_slot_id).and_then(|item| item.take())
592 }
593
594 #[must_use = "Returned items will be lost if not used"]
596 pub fn overflow_remove(&mut self, overflow_slot: usize) -> Option<Item> {
597 if overflow_slot < self.overflow_items.len() {
598 Some(self.overflow_items.remove(overflow_slot))
599 } else {
600 None
601 }
602 }
603
604 pub fn take(
606 &mut self,
607 inv_slot_id: InvSlotId,
608 ability_map: &AbilityMap,
609 msm: &MaterialStatManifest,
610 ) -> Option<Item> {
611 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
612 let mut return_item = item.duplicate(ability_map, msm);
613
614 if item.is_stackable() && item.amount() > 1 {
615 item.decrease_amount(1).ok()?;
616 return_item
617 .set_amount(1)
618 .expect("Items duplicated from a stackable item must be stackable.");
619 Some(return_item)
620 } else {
621 self.remove(inv_slot_id)
622 }
623 } else {
624 None
625 }
626 }
627
628 pub fn take_amount(
631 &mut self,
632 inv_slot_id: InvSlotId,
633 amount: NonZeroU32,
634 ability_map: &AbilityMap,
635 msm: &MaterialStatManifest,
636 ) -> Option<Item> {
637 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
638 if item.is_stackable() && item.amount() > amount.get() {
639 let mut return_item = item.duplicate(ability_map, msm);
640 let return_amount = amount.get();
641 let new_amount = item.amount() - return_amount;
643
644 return_item
645 .set_amount(return_amount)
646 .expect("We know that 0 < return_amount < item.amount()");
647 item.set_amount(new_amount)
648 .expect("new_amount must be > 0 since return item is < item.amount");
649
650 Some(return_item)
651 } else {
652 self.remove(inv_slot_id)
655 }
656 } else {
657 None
658 }
659 }
660
661 #[must_use = "Returned items will be lost if not used"]
663 pub fn take_half(
664 &mut self,
665 inv_slot_id: InvSlotId,
666 ability_map: &AbilityMap,
667 msm: &MaterialStatManifest,
668 ) -> Option<Item> {
669 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
670 item.take_half(ability_map, msm)
671 .or_else(|| self.remove(inv_slot_id))
672 } else {
673 None
674 }
675 }
676
677 #[must_use = "Returned items will be lost if not used"]
679 pub fn overflow_take_half(
680 &mut self,
681 overflow_slot: usize,
682 ability_map: &AbilityMap,
683 msm: &MaterialStatManifest,
684 ) -> Option<Item> {
685 if let Some(item) = self.overflow_items.get_mut(overflow_slot) {
686 item.take_half(ability_map, msm)
687 .or_else(|| self.overflow_remove(overflow_slot))
688 } else {
689 None
690 }
691 }
692
693 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
695 self.slots_mut()
696 .filter(|x| x.is_some())
697 .filter_map(mem::take)
698 }
699
700 pub fn item_count(&self, item_def: &ItemDef) -> u64 {
702 self.slots()
703 .flatten()
704 .filter(|it| it.is_same_item_def(item_def))
705 .map(|it| u64::from(it.amount()))
706 .sum()
707 }
708
709 pub fn has_space_for(&self, item_def: &ItemDef, amount: u32) -> bool {
712 let mut free_space = 0u32;
713 self.slots().any(|i| {
714 free_space = free_space.saturating_add(if let Some(item) = i {
715 if item.is_same_item_def(item_def) {
716 item.max_amount().saturating_sub(item.amount())
719 } else {
720 0
721 }
722 } else {
723 item_def.max_amount()
725 });
726 free_space >= amount
727 })
728 }
729
730 pub fn can_stack(&self, item: &Item) -> bool {
733 let mut free_space = 0u32;
734 self.slots().any(|i| {
735 free_space = free_space.saturating_add(if let Some(inv_item) = i {
736 if inv_item == item {
737 inv_item.max_amount().saturating_sub(inv_item.amount())
740 } else {
741 0
742 }
743 } else {
744 0
745 });
746 free_space >= item.amount()
747 })
748 }
749
750 pub fn remove_item_amount(
758 &mut self,
759 item_def: &ItemDef,
760 amount: u32,
761 ability_map: &AbilityMap,
762 msm: &MaterialStatManifest,
763 ) -> Option<Vec<Item>> {
764 let mut amount = amount;
765 if self.item_count(item_def) >= u64::from(amount) {
766 let mut removed_items = Vec::new();
767 for slot in self.slots_mut() {
768 if amount == 0 {
769 return Some(removed_items);
771 } else if let Some(item) = slot
772 && item.is_same_item_def(item_def)
773 {
774 if amount < item.amount() {
775 removed_items.push(item.take_amount(ability_map, msm, amount).unwrap());
778 return Some(removed_items);
779 } else {
780 amount -= item.amount();
782 removed_items.push(slot.take().unwrap());
783 }
784 }
785 }
786 debug_assert_eq!(amount, 0);
787 Some(removed_items)
788 } else {
789 None
790 }
791 }
792
793 fn insert_prefer_slot(&mut self, item: Item, slot: Option<InvSlotId>) -> Result<(), Item> {
797 if let Some(slot @ None) = slot.and_then(|slot| self.slot_mut(slot)) {
798 *slot = Some(item);
799 Ok(())
800 } else {
801 self.insert(item)
802 }
803 }
804
805 fn insert(&mut self, item: Item) -> Result<(), Item> {
808 match self.slots_mut().find(|slot| slot.is_none()) {
809 Some(slot) => {
810 *slot = Some(item);
811 Ok(())
812 },
813 None => Err(item),
814 }
815 }
816
817 pub fn slot(&self, inv_slot_id: InvSlotId) -> Option<&InvSlot> {
818 match SlotId::from(inv_slot_id) {
819 SlotId::Inventory(slot_idx) => self.slots.get(slot_idx),
820 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot(loadout_slot_id),
821 }
822 }
823
824 pub fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> {
825 match SlotId::from(inv_slot_id) {
826 SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx),
827 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot_mut(loadout_slot_id),
828 }
829 }
830
831 pub fn free_slots_minus_equipped_item(&self, equip_slot: EquipSlot) -> usize {
834 if let Some(mut equip_slot_idx) = self.loadout.loadout_idx_for_equip_slot(equip_slot) {
835 equip_slot_idx += 1;
837
838 self.slots_with_id()
839 .filter(|(inv_slot_id, slot)| {
840 inv_slot_id.loadout_idx() != equip_slot_idx && slot.is_none()
841 })
842 .count()
843 } else {
844 warn!(
846 "Attempted to fetch loadout index for non-existent EquipSlot: {:?}",
847 equip_slot
848 );
849 0
850 }
851 }
852
853 pub fn equipped_items(&self) -> impl Iterator<Item = &Item> { self.loadout.items() }
854
855 pub fn equipped_items_with_slot(&self) -> impl Iterator<Item = (EquipSlot, &Item)> {
856 self.loadout.items_with_slot()
857 }
858
859 pub fn replace_loadout_item(
862 &mut self,
863 equip_slot: EquipSlot,
864 replacement_item: Option<Item>,
865 time: Time,
866 ) -> Option<Item> {
867 self.loadout.swap(equip_slot, replacement_item, time)
868 }
869
870 #[must_use = "Returned items will be lost if not used"]
876 pub fn equip(
877 &mut self,
878 inv_slot: InvSlotId,
879 time: Time,
880 ability_map: &AbilityMap,
881 msm: &MaterialStatManifest,
882 ) -> Result<Option<Vec<Item>>, SlotError> {
883 let Some(item) = self.get(inv_slot) else {
884 return Ok(None);
885 };
886
887 let Some(equip_slot) = self.loadout.get_slot_to_equip_into(&item.kind()) else {
888 return Ok(None);
889 };
890
891 let item = self
892 .take(inv_slot, ability_map, msm)
893 .expect("We got this successfully above");
894
895 if let Some(mut unequipped_item) = self.replace_loadout_item(equip_slot, Some(item), time) {
896 let mut unloaded_items: Vec<Item> = unequipped_item.drain().collect();
897 if let Err((item, _)) = self.push_prefer_slot(unequipped_item, Some(inv_slot)) {
898 unloaded_items.insert(0, item);
900 }
901 match self.push_all(unloaded_items.into_iter()) {
904 Ok(()) => {},
905 Err(Error::Full(items)) => return Ok(Some(items)),
906 }
907 }
908
909 Ok(None)
910 }
911
912 pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
916 let (inv_slot_for_equipped, slots_from_equipped) = self
917 .get(inv_slot)
918 .and_then(|item| self.loadout.get_slot_to_equip_into(&item.kind()))
919 .and_then(|equip_slot| self.equipped(equip_slot))
920 .map_or((1, 0), |item| {
921 (
922 if item.is_stackable() && self.can_stack(item) {
923 1
924 } else {
925 0
926 },
927 item.slots().len(),
928 )
929 });
930
931 let (inv_slot_for_inv, slots_from_inv) = self.get(inv_slot).map_or((0, 0), |item| {
932 (if item.amount() > 1 { -1 } else { 0 }, item.slots().len())
933 });
934
935 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
936 - i32::try_from(slots_from_equipped)
937 .expect("Equipped item with more than i32::MAX slots")
938 + i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
939 - i32::try_from(self.populated_slots())
940 .expect("Inventory item with more than i32::MAX used slots")
941 + inv_slot_for_equipped + inv_slot_for_inv
943 }
944
945 pub fn pickup_item(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
953 if item.is_stackable() {
954 return self.push(item);
955 }
956
957 if self.free_slots() < item.populated_slots() + 1 {
958 return Err((item, None));
959 }
960
961 item.drain().for_each(|item| {
965 self.push(item).unwrap();
966 });
967 self.push(item)
968 }
969
970 #[must_use = "Returned items will be lost if not used"]
973 #[expect(clippy::needless_collect)] pub fn unequip(
975 &mut self,
976 equip_slot: EquipSlot,
977 time: Time,
978 ) -> Result<Option<Vec<Item>>, SlotError> {
979 if self.free_slots_minus_equipped_item(equip_slot) == 0 {
981 return Err(SlotError::InventoryFull);
982 }
983
984 Ok(self
985 .loadout
986 .swap(equip_slot, None, time)
987 .and_then(|mut unequipped_item| {
988 let unloaded_items: Vec<Item> = unequipped_item.drain().collect();
989 self.push(unequipped_item)
990 .expect("Failed to push item to inventory, precondition failed?");
991
992 match self.push_all(unloaded_items.into_iter()) {
995 Err(Error::Full(leftovers)) => Some(leftovers),
996 Ok(()) => None,
997 }
998 }))
999 }
1000
1001 pub fn free_after_unequip(&self, equip_slot: EquipSlot) -> i32 {
1004 let (inv_slot_for_unequipped, slots_from_equipped) = self
1005 .equipped(equip_slot)
1006 .map_or((0, 0), |item| (1, item.slots().len()));
1007
1008 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
1009 - i32::try_from(slots_from_equipped)
1010 .expect("Equipped item with more than i32::MAX slots")
1011 - i32::try_from(self.populated_slots())
1012 .expect("Inventory item with more than i32::MAX used slots")
1013 - inv_slot_for_unequipped }
1015
1016 #[must_use = "Returned items will be lost if not used"]
1019 pub fn swap(&mut self, slot_a: Slot, slot_b: Slot, time: Time) -> Vec<Item> {
1020 match (slot_a, slot_b) {
1021 (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
1022 self.swap_slots(slot_a, slot_b);
1023 Vec::new()
1024 },
1025 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1026 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1027 self.swap_inventory_loadout(inv_slot, equip_slot, time)
1028 },
1029 (Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
1030 self.loadout.swap_slots(slot_a, slot_b, time);
1031 Vec::new()
1032 },
1033 (Slot::Overflow(overflow_slot), Slot::Inventory(inv_slot))
1034 | (Slot::Inventory(inv_slot), Slot::Overflow(overflow_slot)) => {
1035 self.move_overflow_item(overflow_slot, inv_slot);
1036 Vec::new()
1037 },
1038 (Slot::Overflow(_), Slot::Equip(_)) | (Slot::Equip(_), Slot::Overflow(_)) => Vec::new(),
1040 (Slot::Overflow(_), Slot::Overflow(_)) => Vec::new(),
1042 }
1043 }
1044
1045 pub fn free_after_swap(&self, equip_slot: EquipSlot, inv_slot: InvSlotId) -> i32 {
1048 let (inv_slot_for_equipped, slots_from_equipped) = self
1049 .equipped(equip_slot)
1050 .map_or((0, 0), |item| (1, item.slots().len()));
1051 let (inv_slot_for_inv_item, slots_from_inv_item) = self
1052 .get(inv_slot)
1053 .map_or((0, 0), |item| (1, item.slots().len()));
1054
1055 i32::try_from(self.capacity())
1058 .expect("inventory with more than i32::MAX slots")
1059 - i32::try_from(slots_from_equipped)
1060 .expect("equipped item with more than i32::MAX slots")
1061 + i32::try_from(slots_from_inv_item)
1062 .expect("inventory item with more than i32::MAX slots")
1063 - i32::try_from(self.populated_slots())
1064 .expect("inventory with more than i32::MAX used slots")
1065 - inv_slot_for_equipped + inv_slot_for_inv_item }
1068
1069 #[must_use = "Returned items will be lost if not used"]
1071 pub fn swap_inventory_loadout(
1072 &mut self,
1073 inv_slot_id: InvSlotId,
1074 equip_slot: EquipSlot,
1075 time: Time,
1076 ) -> Vec<Item> {
1077 if !self.can_swap(inv_slot_id, equip_slot) {
1078 return Vec::new();
1079 }
1080
1081 let from_inv = self.remove(inv_slot_id);
1083
1084 let from_equip = self.loadout.swap(equip_slot, from_inv, time);
1086
1087 let unloaded_items = from_equip
1088 .map(|mut from_equip| {
1089 let mut items: Vec<Item> = from_equip.drain().collect();
1091
1092 if let Err(returned) = self.insert_at(inv_slot_id, from_equip) {
1097 items.insert(0, returned);
1098 }
1099
1100 items
1101 })
1102 .unwrap_or_default();
1103
1104 match equip_slot {
1107 EquipSlot::ActiveMainhand => {
1108 if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
1109 && self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
1110 {
1111 let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None, time);
1112 assert!(
1113 self.loadout
1114 .swap(EquipSlot::ActiveMainhand, offhand, time)
1115 .is_none()
1116 );
1117 }
1118 },
1119 EquipSlot::InactiveMainhand => {
1120 if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
1121 && self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
1122 {
1123 let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None, time);
1124 assert!(
1125 self.loadout
1126 .swap(EquipSlot::InactiveMainhand, offhand, time)
1127 .is_none()
1128 );
1129 }
1130 },
1131 _ => {},
1132 }
1133
1134 match self.push_all(unloaded_items.into_iter()) {
1138 Err(Error::Full(leftovers)) => leftovers,
1139 Ok(()) => Vec::new(),
1140 }
1141 }
1142
1143 pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
1148 if !self
1150 .get(inv_slot_id)
1151 .is_none_or(|item| self.loadout.slot_can_hold(equip_slot, Some(&*item.kind())))
1152 {
1153 trace!("can_swap = false, equip slot can't hold item");
1154 return false;
1155 }
1156
1157 if self.slot(inv_slot_id).is_none() {
1158 debug!(
1159 "can_swap = false, tried to swap into non-existent inventory slot: {:?}",
1160 inv_slot_id
1161 );
1162 return false;
1163 }
1164
1165 if self.get(inv_slot_id).is_some_and(|item| item.amount() > 1) {
1166 trace!("can_swap = false, equip slot can't hold more than one item");
1167 return false;
1168 }
1169
1170 true
1171 }
1172
1173 pub fn equipped_items_replaceable_by<'a>(
1174 &'a self,
1175 item_kind: &'a ItemKind,
1176 ) -> impl Iterator<Item = &'a Item> {
1177 self.loadout.equipped_items_replaceable_by(item_kind)
1178 }
1179
1180 pub fn swap_equipped_weapons(&mut self, time: Time) { self.loadout.swap_equipped_weapons(time) }
1181
1182 pub fn persistence_update_all_item_states(
1185 &mut self,
1186 ability_map: &AbilityMap,
1187 msm: &MaterialStatManifest,
1188 ) {
1189 self.slots_mut().for_each(|slot| {
1190 if let Some(item) = slot {
1191 item.update_item_state(ability_map, msm);
1192 }
1193 });
1194 self.overflow_items
1195 .iter_mut()
1196 .for_each(|item| item.update_item_state(ability_map, msm));
1197 }
1198
1199 pub fn damage_items(
1202 &mut self,
1203 ability_map: &item::tool::AbilityMap,
1204 msm: &item::MaterialStatManifest,
1205 time: Time,
1206 ) {
1207 self.loadout.damage_items(ability_map, msm);
1208 self.loadout.cull_recently_unequipped_items(time);
1209
1210 let (slots_mut, recently_unequipped_items) =
1211 self.slots_mut_with_mutable_recently_unequipped_items();
1212 slots_mut.filter_map(|slot| slot.as_mut()).for_each(|item| {
1213 if item
1214 .durability_lost()
1215 .is_some_and(|dur| dur < Item::MAX_DURABILITY)
1216 && let Some((_unequip_time, count)) =
1217 recently_unequipped_items.get_mut(&item.item_definition_id())
1218 && *count > 0
1219 {
1220 *count -= 1;
1221 item.increment_damage(ability_map, msm);
1222 }
1223 });
1224 }
1225
1226 pub fn repair_item_at_slot(
1228 &mut self,
1229 slot: Slot,
1230 ability_map: &item::tool::AbilityMap,
1231 msm: &item::MaterialStatManifest,
1232 ) {
1233 match slot {
1234 Slot::Inventory(invslot) => {
1235 if let Some(Some(item)) = self.slot_mut(invslot) {
1236 item.reset_durability(ability_map, msm);
1237 }
1238 },
1239 Slot::Equip(equip_slot) => {
1240 self.loadout
1241 .repair_item_at_slot(equip_slot, ability_map, msm);
1242 },
1243 Slot::Overflow(_) => {},
1245 }
1246 }
1247
1248 pub fn persistence_push_overflow_items<I: Iterator<Item = Item>>(&mut self, overflow_items: I) {
1252 self.overflow_items.extend(overflow_items);
1253 }
1254
1255 pub fn recipes_iter(&self) -> impl ExactSizeIterator<Item = &String> { self.recipe_book.iter() }
1256
1257 pub fn recipe_groups_iter(&self) -> impl ExactSizeIterator<Item = &Item> {
1258 self.recipe_book.iter_groups()
1259 }
1260
1261 pub fn available_recipes_iter<'a>(
1262 &'a self,
1263 rbm: &'a RecipeBookManifest,
1264 ) -> impl Iterator<Item = (&'a String, &'a Recipe)> + 'a {
1265 self.recipe_book.get_available_iter(rbm)
1266 }
1267
1268 pub fn recipe_book_len(&self) -> usize { self.recipe_book.len() }
1269
1270 pub fn get_recipe<'a>(
1271 &'a self,
1272 recipe_key: &str,
1273 rbm: &'a RecipeBookManifest,
1274 ) -> Option<&'a Recipe> {
1275 self.recipe_book.get(recipe_key, rbm)
1276 }
1277
1278 pub fn push_recipe_group(&mut self, recipe_group: Item) -> Result<(), Item> {
1279 self.recipe_book.push_group(recipe_group)
1280 }
1281
1282 pub fn can_craft_recipe(
1285 &self,
1286 recipe_key: &str,
1287 amount: u32,
1288 rbm: &RecipeBookManifest,
1289 ) -> (bool, Option<SpriteKind>) {
1290 if let Some(recipe) = self.recipe_book.get(recipe_key, rbm) {
1291 (
1292 recipe.inventory_contains_ingredients(self, amount).is_ok(),
1293 recipe.craft_sprite,
1294 )
1295 } else {
1296 (false, None)
1297 }
1298 }
1299
1300 pub fn recipe_is_known(&self, recipe_key: &str) -> bool {
1301 self.recipe_book.is_known(recipe_key)
1302 }
1303
1304 pub fn reset_recipes(&mut self) { self.recipe_book.reset(); }
1305
1306 pub fn persistence_recipes_iter_with_index(&self) -> impl Iterator<Item = (usize, &Item)> {
1307 self.recipe_book.persistence_recipes_iter_with_index()
1308 }
1309}
1310
1311impl Component for Inventory {
1312 type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
1313}
1314
1315#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
1316pub enum CollectFailedReason {
1317 InventoryFull,
1318 LootOwned {
1319 owner: LootOwnerKind,
1320 expiry_secs: u64,
1321 },
1322}
1323
1324#[derive(Clone, Debug, Serialize, Deserialize)]
1325pub enum InventoryUpdateEvent {
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
1347impl Default for InventoryUpdateEvent {
1348 fn default() -> Self { Self::Init }
1349}
1350
1351#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1352pub struct InventoryUpdate {
1353 events: Vec<InventoryUpdateEvent>,
1354}
1355
1356impl InventoryUpdate {
1357 pub fn new(event: InventoryUpdateEvent) -> Self {
1358 Self {
1359 events: vec![event],
1360 }
1361 }
1362
1363 pub fn push(&mut self, event: InventoryUpdateEvent) { self.events.push(event); }
1364
1365 pub fn take_events(&mut self) -> Vec<InventoryUpdateEvent> { std::mem::take(&mut self.events) }
1366}
1367
1368impl Component for InventoryUpdate {
1369 type Storage = specs::VecStorage<Self>;
1372}