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)]
218 Some(CustomOrder::Name) => cmp = Ord::cmp(&a.name(), &b.name()),
219 Some(CustomOrder::Tag) => {
220 cmp = Ord::cmp(
221 &a.tags().first().map_or("", |tag| tag.name()),
222 &b.tags().first().map_or("", |tag| tag.name()),
223 )
224 },
225 _ => break,
226 }
227 }
228 cmp
229 }
230
231 pub fn sort(&mut self, sort_order: InventorySortOrder) {
233 let mut items: Vec<Item> = self.slots_mut().filter_map(mem::take).collect();
234
235 #[expect(deprecated)]
238 items.sort_by(|a, b| Ord::cmp(&a.name(), &b.name()));
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
267 fn push_prefer_slot(
271 &mut self,
272 mut item: Item,
273 slot: Option<InvSlotId>,
274 ) -> Result<(), (Item, Option<NonZeroU32>)> {
275 if item.is_stackable() {
280 let total_amount = item.amount();
281
282 let remaining = self
283 .slots_mut()
284 .filter_map(Option::as_mut)
285 .filter(|s| *s == &item)
286 .try_fold(total_amount, |remaining, current| {
287 debug_assert_eq!(
288 item.max_amount(),
289 current.max_amount(),
290 "max_amount of two equal items must match"
291 );
292
293 let new_remaining = remaining
296 .checked_sub(current.max_amount() - current.amount())
297 .filter(|&remaining| remaining > 0);
298 if new_remaining.is_some() {
299 current
302 .set_amount(current.max_amount())
303 .expect("max_amount() is always a valid amount");
304 } else {
305 current.increase_amount(remaining).expect(
307 "This item must be able to contain the remaining amount, because \
308 remaining < current.max_amount() - current.amount()",
309 );
310 }
311
312 new_remaining
313 });
314
315 if let Some(remaining) = remaining {
316 item.set_amount(remaining)
317 .expect("Remaining is known to be > 0");
318 self.insert_prefer_slot(item, slot)
319 .map_err(|item| (item, NonZeroU32::new(total_amount - remaining)))
320 } else {
321 Ok(())
322 }
323 } else {
324 self.insert_prefer_slot(item, slot)
326 .map_err(|item| (item, None))
327 }
328 }
329
330 pub fn push(&mut self, item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
337 self.push_prefer_slot(item, None)
338 }
339
340 pub fn push_all<I: Iterator<Item = Item>>(&mut self, items: I) -> Result<(), Error> {
343 let mut leftovers = Vec::new();
345 for item in items {
346 if let Err((item, _)) = self.push(item) {
347 leftovers.push(item);
348 }
349 }
350 if !leftovers.is_empty() {
351 Err(Error::Full(leftovers))
352 } else {
353 Ok(())
354 }
355 }
356
357 pub fn push_all_unique<I: Iterator<Item = Item>>(&mut self, mut items: I) -> Result<(), Error> {
367 let mut leftovers = Vec::new();
368 for item in &mut items {
369 if self.contains(&item).not() {
370 if let Err((overflow, _)) = self.push(item) {
371 leftovers.push(overflow);
372 }
373 } }
375 if !leftovers.is_empty() {
376 Err(Error::Full(leftovers))
377 } else {
378 Ok(())
379 }
380 }
381
382 pub fn insert_at(&mut self, inv_slot_id: InvSlotId, item: Item) -> Result<Option<Item>, Item> {
385 match self.slot_mut(inv_slot_id) {
386 Some(slot) => Ok(mem::replace(slot, Some(item))),
387 None => Err(item),
388 }
389 }
390
391 pub fn merge_stack_into(&mut self, src: InvSlotId, dst: InvSlotId) -> bool {
394 let mut amount = None;
395 if let (Some(srcitem), Some(dstitem)) = (self.get(src), self.get(dst)) {
396 if srcitem == dstitem && srcitem.is_stackable() {
400 amount = Some(srcitem.amount());
401 }
402 }
403 if let Some(amount) = amount {
404 let dstitem = self
405 .get_mut(dst)
406 .expect("self.get(dst) was Some right above this");
407 dstitem
408 .increase_amount(amount)
409 .map(|_| {
410 self.remove(src).expect("Already verified that src was populated.");
412 })
413 .is_ok()
415 } else {
416 false
417 }
418 }
419
420 pub fn insert_or_stack_at(
423 &mut self,
424 inv_slot_id: InvSlotId,
425 item: Item,
426 ) -> Result<Option<Item>, Item> {
427 if item.is_stackable() {
428 match self.slot_mut(inv_slot_id) {
429 Some(Some(slot_item)) => {
430 Ok(if slot_item == &item {
431 slot_item
432 .increase_amount(item.amount())
433 .err()
434 .and(Some(item))
435 } else {
436 let old_item = mem::replace(slot_item, item);
437 Some(old_item)
439 })
440 },
441 Some(None) => self.insert_at(inv_slot_id, item),
442 None => Err(item),
443 }
444 } else {
445 self.insert_at(inv_slot_id, item)
446 }
447 }
448
449 #[must_use = "Returned item will be lost if not used"]
452 pub fn try_equip(&mut self, item: Item) -> Result<(), Item> { self.loadout.try_equip(item) }
453
454 pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() }
455
456 pub fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() }
457
458 pub fn contains(&self, item: &Item) -> bool {
460 self.slots().any(|slot| slot.as_ref() == Some(item))
461 }
462
463 pub fn get_slot_of_item(&self, item: &Item) -> Option<InvSlotId> {
465 self.slots_with_id()
466 .find(|&(_, it)| {
467 if let Some(it) = it {
468 it.item_definition_id() == item.item_definition_id()
469 } else {
470 false
471 }
472 })
473 .map(|(slot, _)| slot)
474 }
475
476 pub fn get_slot_of_item_by_def_id(
477 &self,
478 item_def_id: &item::ItemDefinitionIdOwned,
479 ) -> Option<InvSlotId> {
480 self.slots_with_id()
481 .find(|&(_, it)| {
482 if let Some(it) = it {
483 it.item_definition_id() == *item_def_id
484 } else {
485 false
486 }
487 })
488 .map(|(slot, _)| slot)
489 }
490
491 pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
493 self.slot(inv_slot_id).and_then(Option::as_ref)
494 }
495
496 pub fn get_overflow(&self, overflow: usize) -> Option<&Item> {
498 self.overflow_items.get(overflow)
499 }
500
501 pub fn get_slot(&self, slot: Slot) -> Option<&Item> {
503 match slot {
504 Slot::Inventory(inv_slot) => self.get(inv_slot),
505 Slot::Equip(equip) => self.equipped(equip),
506 Slot::Overflow(overflow) => self.get_overflow(overflow),
507 }
508 }
509
510 pub fn get_by_hash(&self, item_hash: u64) -> Option<&Item> {
512 self.slots().flatten().find(|i| i.item_hash() == item_hash)
513 }
514
515 pub fn get_slot_from_hash(&self, item_hash: u64) -> Option<InvSlotId> {
517 let slot_with_id = self.slots_with_id().find(|slot| match slot.1 {
518 None => false,
519 Some(item) => item.item_hash() == item_hash,
520 });
521 slot_with_id.map(|s| s.0)
522 }
523
524 fn get_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut Item> {
526 self.slot_mut(inv_slot_id).and_then(Option::as_mut)
527 }
528
529 pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
531 self.loadout.equipped(equip_slot)
532 }
533
534 pub fn loadout_items_with_persistence_key(
535 &self,
536 ) -> impl Iterator<Item = (&str, Option<&Item>)> {
537 self.loadout.items_with_persistence_key()
538 }
539
540 pub fn get_slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
544 let offset = self.slots.len();
547 self.loadout
548 .slot_range_for_equip_slot(equip_slot)
549 .map(|loadout_range| (loadout_range.start + offset)..(loadout_range.end + offset))
550 }
551
552 pub fn swap_slots(&mut self, a: InvSlotId, b: InvSlotId) {
554 if self.slot(a).is_none() || self.slot(b).is_none() {
555 warn!("swap_slots called with non-existent inventory slot(s)");
556 return;
557 }
558
559 let slot_a = mem::take(self.slot_mut(a).unwrap());
560 let slot_b = mem::take(self.slot_mut(b).unwrap());
561 *self.slot_mut(a).unwrap() = slot_b;
562 *self.slot_mut(b).unwrap() = slot_a;
563 }
564
565 pub fn move_overflow_item(&mut self, overflow: usize, inv_slot: InvSlotId) {
567 match self.slot(inv_slot) {
568 Some(Some(_)) => {
569 warn!("Attempted to move from overflow slot to a filled inventory slot");
570 return;
571 },
572 None => {
573 warn!("Attempted to move from overflow slot to a non-existent inventory slot");
574 return;
575 },
576 Some(None) => {},
577 };
578
579 let item = self.overflow_items.remove(overflow);
580 *self.slot_mut(inv_slot).unwrap() = Some(item);
581 }
582
583 pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
585 self.slot_mut(inv_slot_id).and_then(|item| item.take())
586 }
587
588 #[must_use = "Returned items will be lost if not used"]
590 pub fn overflow_remove(&mut self, overflow_slot: usize) -> Option<Item> {
591 if overflow_slot < self.overflow_items.len() {
592 Some(self.overflow_items.remove(overflow_slot))
593 } else {
594 None
595 }
596 }
597
598 pub fn take(
600 &mut self,
601 inv_slot_id: InvSlotId,
602 ability_map: &AbilityMap,
603 msm: &MaterialStatManifest,
604 ) -> Option<Item> {
605 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
606 let mut return_item = item.duplicate(ability_map, msm);
607
608 if item.is_stackable() && item.amount() > 1 {
609 item.decrease_amount(1).ok()?;
610 return_item
611 .set_amount(1)
612 .expect("Items duplicated from a stackable item must be stackable.");
613 Some(return_item)
614 } else {
615 self.remove(inv_slot_id)
616 }
617 } else {
618 None
619 }
620 }
621
622 pub fn take_amount(
625 &mut self,
626 inv_slot_id: InvSlotId,
627 amount: NonZeroU32,
628 ability_map: &AbilityMap,
629 msm: &MaterialStatManifest,
630 ) -> Option<Item> {
631 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
632 if item.is_stackable() && item.amount() > amount.get() {
633 let mut return_item = item.duplicate(ability_map, msm);
634 let return_amount = amount.get();
635 let new_amount = item.amount() - return_amount;
637
638 return_item
639 .set_amount(return_amount)
640 .expect("We know that 0 < return_amount < item.amount()");
641 item.set_amount(new_amount)
642 .expect("new_amount must be > 0 since return item is < item.amount");
643
644 Some(return_item)
645 } else {
646 self.remove(inv_slot_id)
649 }
650 } else {
651 None
652 }
653 }
654
655 #[must_use = "Returned items will be lost if not used"]
657 pub fn take_half(
658 &mut self,
659 inv_slot_id: InvSlotId,
660 ability_map: &AbilityMap,
661 msm: &MaterialStatManifest,
662 ) -> Option<Item> {
663 if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
664 item.take_half(ability_map, msm)
665 .or_else(|| self.remove(inv_slot_id))
666 } else {
667 None
668 }
669 }
670
671 #[must_use = "Returned items will be lost if not used"]
673 pub fn overflow_take_half(
674 &mut self,
675 overflow_slot: usize,
676 ability_map: &AbilityMap,
677 msm: &MaterialStatManifest,
678 ) -> Option<Item> {
679 if let Some(item) = self.overflow_items.get_mut(overflow_slot) {
680 item.take_half(ability_map, msm)
681 .or_else(|| self.overflow_remove(overflow_slot))
682 } else {
683 None
684 }
685 }
686
687 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
689 self.slots_mut()
690 .filter(|x| x.is_some())
691 .filter_map(mem::take)
692 }
693
694 pub fn item_count(&self, item_def: &ItemDef) -> u64 {
696 self.slots()
697 .flatten()
698 .filter(|it| it.is_same_item_def(item_def))
699 .map(|it| u64::from(it.amount()))
700 .sum()
701 }
702
703 pub fn has_space_for(&self, item_def: &ItemDef, amount: u32) -> bool {
706 let mut free_space = 0u32;
707 self.slots().any(|i| {
708 free_space = free_space.saturating_add(if let Some(item) = i {
709 if item.is_same_item_def(item_def) {
710 item.max_amount().saturating_sub(item.amount())
713 } else {
714 0
715 }
716 } else {
717 item_def.max_amount()
719 });
720 free_space >= amount
721 })
722 }
723
724 pub fn can_stack(&self, item: &Item) -> bool {
727 let mut free_space = 0u32;
728 self.slots().any(|i| {
729 free_space = free_space.saturating_add(if let Some(inv_item) = i {
730 if inv_item == item {
731 inv_item.max_amount().saturating_sub(inv_item.amount())
734 } else {
735 0
736 }
737 } else {
738 0
739 });
740 free_space >= item.amount()
741 })
742 }
743
744 pub fn remove_item_amount(
752 &mut self,
753 item_def: &ItemDef,
754 amount: u32,
755 ability_map: &AbilityMap,
756 msm: &MaterialStatManifest,
757 ) -> Option<Vec<Item>> {
758 let mut amount = amount;
759 if self.item_count(item_def) >= u64::from(amount) {
760 let mut removed_items = Vec::new();
761 for slot in self.slots_mut() {
762 if amount == 0 {
763 return Some(removed_items);
765 } else if let Some(item) = slot
766 && item.is_same_item_def(item_def)
767 {
768 if amount < item.amount() {
769 removed_items.push(item.take_amount(ability_map, msm, amount).unwrap());
772 return Some(removed_items);
773 } else {
774 amount -= item.amount();
776 removed_items.push(slot.take().unwrap());
777 }
778 }
779 }
780 debug_assert_eq!(amount, 0);
781 Some(removed_items)
782 } else {
783 None
784 }
785 }
786
787 fn insert_prefer_slot(&mut self, item: Item, slot: Option<InvSlotId>) -> Result<(), Item> {
791 if let Some(slot @ None) = slot.and_then(|slot| self.slot_mut(slot)) {
792 *slot = Some(item);
793 Ok(())
794 } else {
795 self.insert(item)
796 }
797 }
798
799 fn insert(&mut self, item: Item) -> Result<(), Item> {
802 match self.slots_mut().find(|slot| slot.is_none()) {
803 Some(slot) => {
804 *slot = Some(item);
805 Ok(())
806 },
807 None => Err(item),
808 }
809 }
810
811 pub fn slot(&self, inv_slot_id: InvSlotId) -> Option<&InvSlot> {
812 match SlotId::from(inv_slot_id) {
813 SlotId::Inventory(slot_idx) => self.slots.get(slot_idx),
814 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot(loadout_slot_id),
815 }
816 }
817
818 pub fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> {
819 match SlotId::from(inv_slot_id) {
820 SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx),
821 SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot_mut(loadout_slot_id),
822 }
823 }
824
825 pub fn free_slots_minus_equipped_item(&self, equip_slot: EquipSlot) -> usize {
828 if let Some(mut equip_slot_idx) = self.loadout.loadout_idx_for_equip_slot(equip_slot) {
829 equip_slot_idx += 1;
831
832 self.slots_with_id()
833 .filter(|(inv_slot_id, slot)| {
834 inv_slot_id.loadout_idx() != equip_slot_idx && slot.is_none()
835 })
836 .count()
837 } else {
838 warn!(
840 "Attempted to fetch loadout index for non-existent EquipSlot: {:?}",
841 equip_slot
842 );
843 0
844 }
845 }
846
847 pub fn equipped_items(&self) -> impl Iterator<Item = &Item> { self.loadout.items() }
848
849 pub fn equipped_items_with_slot(&self) -> impl Iterator<Item = (EquipSlot, &Item)> {
850 self.loadout.items_with_slot()
851 }
852
853 pub fn replace_loadout_item(
856 &mut self,
857 equip_slot: EquipSlot,
858 replacement_item: Option<Item>,
859 time: Time,
860 ) -> Option<Item> {
861 self.loadout.swap(equip_slot, replacement_item, time)
862 }
863
864 #[must_use = "Returned items will be lost if not used"]
870 pub fn equip(
871 &mut self,
872 inv_slot: InvSlotId,
873 time: Time,
874 ability_map: &AbilityMap,
875 msm: &MaterialStatManifest,
876 ) -> Result<Option<Vec<Item>>, SlotError> {
877 let Some(item) = self.get(inv_slot) else {
878 return Ok(None);
879 };
880
881 let Some(equip_slot) = self.loadout.get_slot_to_equip_into(item) else {
882 return Ok(None);
883 };
884
885 let item = self
886 .take(inv_slot, ability_map, msm)
887 .expect("We got this successfully above");
888
889 if let Some(mut unequipped_item) = self.replace_loadout_item(equip_slot, Some(item), time) {
890 let mut unloaded_items: Vec<Item> = unequipped_item.drain().collect();
891 if let Err((item, _)) = self.push_prefer_slot(unequipped_item, Some(inv_slot)) {
892 unloaded_items.insert(0, item);
894 }
895 match self.push_all(unloaded_items.into_iter()) {
898 Ok(()) => {},
899 Err(Error::Full(items)) => return Ok(Some(items)),
900 }
901 }
902
903 Ok(None)
904 }
905
906 pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
910 let (inv_slot_for_equipped, slots_from_equipped) = self
911 .get(inv_slot)
912 .and_then(|item| self.loadout.get_slot_to_equip_into(item))
913 .and_then(|equip_slot| self.equipped(equip_slot))
914 .map_or((1, 0), |item| {
915 (
916 if item.is_stackable() && self.can_stack(item) {
917 1
918 } else {
919 0
920 },
921 item.slots().len(),
922 )
923 });
924
925 let (inv_slot_for_inv, slots_from_inv) = self.get(inv_slot).map_or((0, 0), |item| {
926 (if item.amount() > 1 { -1 } else { 0 }, item.slots().len())
927 });
928
929 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
930 - i32::try_from(slots_from_equipped)
931 .expect("Equipped item with more than i32::MAX slots")
932 + i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
933 - i32::try_from(self.populated_slots())
934 .expect("Inventory item with more than i32::MAX used slots")
935 + inv_slot_for_equipped + inv_slot_for_inv
937 }
938
939 pub fn pickup_item(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
947 if item.is_stackable() {
948 return self.push(item);
949 }
950
951 if self.free_slots() < item.populated_slots() + 1 {
952 return Err((item, None));
953 }
954
955 item.drain().for_each(|item| {
959 self.push(item).unwrap();
960 });
961 self.push(item)
962 }
963
964 #[must_use = "Returned items will be lost if not used"]
967 #[expect(clippy::needless_collect)] pub fn unequip(
969 &mut self,
970 equip_slot: EquipSlot,
971 time: Time,
972 ) -> Result<Option<Vec<Item>>, SlotError> {
973 if self.free_slots_minus_equipped_item(equip_slot) == 0 {
975 return Err(SlotError::InventoryFull);
976 }
977
978 Ok(self
979 .loadout
980 .swap(equip_slot, None, time)
981 .and_then(|mut unequipped_item| {
982 let unloaded_items: Vec<Item> = unequipped_item.drain().collect();
983 self.push(unequipped_item)
984 .expect("Failed to push item to inventory, precondition failed?");
985
986 match self.push_all(unloaded_items.into_iter()) {
989 Err(Error::Full(leftovers)) => Some(leftovers),
990 Ok(()) => None,
991 }
992 }))
993 }
994
995 pub fn free_after_unequip(&self, equip_slot: EquipSlot) -> i32 {
998 let (inv_slot_for_unequipped, slots_from_equipped) = self
999 .equipped(equip_slot)
1000 .map_or((0, 0), |item| (1, item.slots().len()));
1001
1002 i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
1003 - i32::try_from(slots_from_equipped)
1004 .expect("Equipped item with more than i32::MAX slots")
1005 - i32::try_from(self.populated_slots())
1006 .expect("Inventory item with more than i32::MAX used slots")
1007 - inv_slot_for_unequipped }
1009
1010 #[must_use = "Returned items will be lost if not used"]
1013 pub fn swap(&mut self, slot_a: Slot, slot_b: Slot, time: Time) -> Vec<Item> {
1014 match (slot_a, slot_b) {
1015 (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
1016 self.swap_slots(slot_a, slot_b);
1017 Vec::new()
1018 },
1019 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1020 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1021 self.swap_inventory_loadout(inv_slot, equip_slot, time)
1022 },
1023 (Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
1024 self.loadout.swap_slots(slot_a, slot_b, time);
1025 Vec::new()
1026 },
1027 (Slot::Overflow(overflow_slot), Slot::Inventory(inv_slot))
1028 | (Slot::Inventory(inv_slot), Slot::Overflow(overflow_slot)) => {
1029 self.move_overflow_item(overflow_slot, inv_slot);
1030 Vec::new()
1031 },
1032 (Slot::Overflow(_), Slot::Equip(_)) | (Slot::Equip(_), Slot::Overflow(_)) => Vec::new(),
1034 (Slot::Overflow(_), Slot::Overflow(_)) => Vec::new(),
1036 }
1037 }
1038
1039 pub fn free_after_swap(&self, equip_slot: EquipSlot, inv_slot: InvSlotId) -> i32 {
1042 let (inv_slot_for_equipped, slots_from_equipped) = self
1043 .equipped(equip_slot)
1044 .map_or((0, 0), |item| (1, item.slots().len()));
1045 let (inv_slot_for_inv_item, slots_from_inv_item) = self
1046 .get(inv_slot)
1047 .map_or((0, 0), |item| (1, item.slots().len()));
1048
1049 i32::try_from(self.capacity())
1052 .expect("inventory with more than i32::MAX slots")
1053 - i32::try_from(slots_from_equipped)
1054 .expect("equipped item with more than i32::MAX slots")
1055 + i32::try_from(slots_from_inv_item)
1056 .expect("inventory item with more than i32::MAX slots")
1057 - i32::try_from(self.populated_slots())
1058 .expect("inventory with more than i32::MAX used slots")
1059 - inv_slot_for_equipped + inv_slot_for_inv_item }
1062
1063 #[must_use = "Returned items will be lost if not used"]
1065 pub fn swap_inventory_loadout(
1066 &mut self,
1067 inv_slot_id: InvSlotId,
1068 equip_slot: EquipSlot,
1069 time: Time,
1070 ) -> Vec<Item> {
1071 if !self.can_swap(inv_slot_id, equip_slot) {
1072 return Vec::new();
1073 }
1074
1075 let from_inv = self.remove(inv_slot_id);
1077
1078 let from_equip = self.loadout.swap(equip_slot, from_inv, time);
1080
1081 let unloaded_items = from_equip
1082 .map(|mut from_equip| {
1083 let mut items: Vec<Item> = from_equip.drain().collect();
1085
1086 if let Err(returned) = self.insert_at(inv_slot_id, from_equip) {
1091 items.insert(0, returned);
1092 }
1093
1094 items
1095 })
1096 .unwrap_or_default();
1097
1098 match equip_slot {
1101 EquipSlot::ActiveMainhand => {
1102 if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
1103 && self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
1104 {
1105 let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None, time);
1106 assert!(
1107 self.loadout
1108 .swap(EquipSlot::ActiveMainhand, offhand, time)
1109 .is_none()
1110 );
1111 }
1112 },
1113 EquipSlot::InactiveMainhand => {
1114 if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
1115 && self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
1116 {
1117 let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None, time);
1118 assert!(
1119 self.loadout
1120 .swap(EquipSlot::InactiveMainhand, offhand, time)
1121 .is_none()
1122 );
1123 }
1124 },
1125 _ => {},
1126 }
1127
1128 match self.push_all(unloaded_items.into_iter()) {
1132 Err(Error::Full(leftovers)) => leftovers,
1133 Ok(()) => Vec::new(),
1134 }
1135 }
1136
1137 pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
1142 if !self
1144 .get(inv_slot_id)
1145 .is_none_or(|item| self.loadout.slot_can_hold(equip_slot, Some(&*item.kind())))
1146 {
1147 trace!("can_swap = false, equip slot can't hold item");
1148 return false;
1149 }
1150
1151 if self.slot(inv_slot_id).is_none() {
1152 debug!(
1153 "can_swap = false, tried to swap into non-existent inventory slot: {:?}",
1154 inv_slot_id
1155 );
1156 return false;
1157 }
1158
1159 if self.get(inv_slot_id).is_some_and(|item| item.amount() > 1) {
1160 trace!("can_swap = false, equip slot can't hold more than one item");
1161 return false;
1162 }
1163
1164 true
1165 }
1166
1167 pub fn equipped_items_replaceable_by<'a>(
1168 &'a self,
1169 item_kind: &'a ItemKind,
1170 ) -> impl Iterator<Item = &'a Item> {
1171 self.loadout.equipped_items_replaceable_by(item_kind)
1172 }
1173
1174 pub fn swap_equipped_weapons(&mut self, time: Time) { self.loadout.swap_equipped_weapons(time) }
1175
1176 pub fn persistence_update_all_item_states(
1179 &mut self,
1180 ability_map: &AbilityMap,
1181 msm: &MaterialStatManifest,
1182 ) {
1183 self.slots_mut().for_each(|slot| {
1184 if let Some(item) = slot {
1185 item.update_item_state(ability_map, msm);
1186 }
1187 });
1188 self.overflow_items
1189 .iter_mut()
1190 .for_each(|item| item.update_item_state(ability_map, msm));
1191 }
1192
1193 pub fn damage_items(
1196 &mut self,
1197 ability_map: &item::tool::AbilityMap,
1198 msm: &item::MaterialStatManifest,
1199 time: Time,
1200 ) {
1201 self.loadout.damage_items(ability_map, msm);
1202 self.loadout.cull_recently_unequipped_items(time);
1203
1204 let (slots_mut, recently_unequipped_items) =
1205 self.slots_mut_with_mutable_recently_unequipped_items();
1206 slots_mut.filter_map(|slot| slot.as_mut()).for_each(|item| {
1207 if item
1208 .durability_lost()
1209 .is_some_and(|dur| dur < Item::MAX_DURABILITY)
1210 && let Some((_unequip_time, count)) =
1211 recently_unequipped_items.get_mut(&item.item_definition_id())
1212 && *count > 0
1213 {
1214 *count -= 1;
1215 item.increment_damage(ability_map, msm);
1216 }
1217 });
1218 }
1219
1220 pub fn repair_item_at_slot(
1222 &mut self,
1223 slot: Slot,
1224 ability_map: &item::tool::AbilityMap,
1225 msm: &item::MaterialStatManifest,
1226 ) {
1227 match slot {
1228 Slot::Inventory(invslot) => {
1229 if let Some(Some(item)) = self.slot_mut(invslot) {
1230 item.reset_durability(ability_map, msm);
1231 }
1232 },
1233 Slot::Equip(equip_slot) => {
1234 self.loadout
1235 .repair_item_at_slot(equip_slot, ability_map, msm);
1236 },
1237 Slot::Overflow(_) => {},
1239 }
1240 }
1241
1242 pub fn persistence_push_overflow_items<I: Iterator<Item = Item>>(&mut self, overflow_items: I) {
1246 self.overflow_items.extend(overflow_items);
1247 }
1248
1249 pub fn recipes_iter(&self) -> impl ExactSizeIterator<Item = &String> { self.recipe_book.iter() }
1250
1251 pub fn recipe_groups_iter(&self) -> impl ExactSizeIterator<Item = &Item> {
1252 self.recipe_book.iter_groups()
1253 }
1254
1255 pub fn available_recipes_iter<'a>(
1256 &'a self,
1257 rbm: &'a RecipeBookManifest,
1258 ) -> impl Iterator<Item = (&'a String, &'a Recipe)> + 'a {
1259 self.recipe_book.get_available_iter(rbm)
1260 }
1261
1262 pub fn recipe_book_len(&self) -> usize { self.recipe_book.len() }
1263
1264 pub fn get_recipe<'a>(
1265 &'a self,
1266 recipe_key: &str,
1267 rbm: &'a RecipeBookManifest,
1268 ) -> Option<&'a Recipe> {
1269 self.recipe_book.get(recipe_key, rbm)
1270 }
1271
1272 pub fn push_recipe_group(&mut self, recipe_group: Item) -> Result<(), Item> {
1273 self.recipe_book.push_group(recipe_group)
1274 }
1275
1276 pub fn can_craft_recipe(
1279 &self,
1280 recipe_key: &str,
1281 amount: u32,
1282 rbm: &RecipeBookManifest,
1283 ) -> (bool, Option<SpriteKind>) {
1284 if let Some(recipe) = self.recipe_book.get(recipe_key, rbm) {
1285 (
1286 recipe.inventory_contains_ingredients(self, amount).is_ok(),
1287 recipe.craft_sprite,
1288 )
1289 } else {
1290 (false, None)
1291 }
1292 }
1293
1294 pub fn recipe_is_known(&self, recipe_key: &str) -> bool {
1295 self.recipe_book.is_known(recipe_key)
1296 }
1297
1298 pub fn reset_recipes(&mut self) { self.recipe_book.reset(); }
1299
1300 pub fn persistence_recipes_iter_with_index(&self) -> impl Iterator<Item = (usize, &Item)> {
1301 self.recipe_book.persistence_recipes_iter_with_index()
1302 }
1303}
1304
1305impl Component for Inventory {
1306 type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
1307}
1308
1309#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
1310pub enum CollectFailedReason {
1311 InventoryFull,
1312 LootOwned {
1313 owner: LootOwnerKind,
1314 expiry_secs: u64,
1315 },
1316}
1317
1318#[derive(Clone, Debug, Serialize, Deserialize)]
1319pub enum InventoryUpdateEvent {
1320 Init,
1321 Used,
1322 Consumed(ItemKey),
1323 Gave,
1324 Given,
1325 Swapped,
1326 Dropped,
1327 Collected(FrontendItem),
1328 BlockCollectFailed {
1329 pos: Vec3<i32>,
1330 reason: CollectFailedReason,
1331 },
1332 EntityCollectFailed {
1333 entity: Uid,
1334 reason: CollectFailedReason,
1335 },
1336 Possession,
1337 Debug,
1338 Craft,
1339}
1340
1341impl Default for InventoryUpdateEvent {
1342 fn default() -> Self { Self::Init }
1343}
1344
1345#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1346pub struct InventoryUpdate {
1347 events: Vec<InventoryUpdateEvent>,
1348}
1349
1350impl InventoryUpdate {
1351 pub fn new(event: InventoryUpdateEvent) -> Self {
1352 Self {
1353 events: vec![event],
1354 }
1355 }
1356
1357 pub fn push(&mut self, event: InventoryUpdateEvent) { self.events.push(event); }
1358
1359 pub fn take_events(&mut self) -> Vec<InventoryUpdateEvent> { std::mem::take(&mut self.events) }
1360}
1361
1362impl Component for InventoryUpdate {
1363 type Storage = specs::VecStorage<Self>;
1366}