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