use core::ops::Not;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use std::{cmp::Ordering, convert::TryFrom, mem, num::NonZeroU32, ops::Range};
use tracing::{debug, trace, warn};
use vek::Vec3;
use crate::{
comp::{
body::Body,
inventory::{
item::{
item_key::ItemKey, tool::AbilityMap, ItemDef, ItemDefinitionIdOwned, ItemKind,
MaterialStatManifest, TagExampleInfo,
},
loadout::Loadout,
recipe_book::RecipeBook,
slot::{EquipSlot, Slot, SlotError},
},
loot_owner::LootOwnerKind,
slot::{InvSlotId, SlotId},
Item,
},
recipe::{Recipe, RecipeBookManifest},
resources::Time,
terrain::SpriteKind,
uid::Uid,
LoadoutBuilder,
};
use super::FrontendItem;
pub mod item;
pub mod loadout;
pub mod loadout_builder;
pub mod recipe_book;
pub mod slot;
#[cfg(test)] mod test;
#[cfg(test)] mod test_helpers;
pub mod trade_pricing;
pub type InvSlot = Option<Item>;
const DEFAULT_INVENTORY_SLOTS: usize = 18;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Inventory {
next_sort_order: InventorySortOrder,
loadout: Loadout,
slots: Vec<InvSlot>,
overflow_items: Vec<Item>,
recipe_book: RecipeBook,
}
#[derive(Debug)]
pub enum Error {
Full(Vec<Item>),
}
impl Error {
pub fn returned_items(self) -> impl Iterator<Item = Item> {
match self {
Error::Full(items) => items.into_iter(),
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum InventorySortOrder {
Name,
Quality,
Category,
Tag,
Amount,
}
impl InventorySortOrder {
fn next(&self) -> InventorySortOrder {
match self {
InventorySortOrder::Name => InventorySortOrder::Quality,
InventorySortOrder::Quality => InventorySortOrder::Tag,
InventorySortOrder::Tag => InventorySortOrder::Category,
InventorySortOrder::Category => InventorySortOrder::Amount,
InventorySortOrder::Amount => InventorySortOrder::Name,
}
}
}
pub enum CustomOrder {
Name,
Quality,
KindPartial,
KindFull,
Tag,
}
impl Inventory {
pub fn with_empty() -> Inventory {
Self::with_loadout_humanoid(LoadoutBuilder::empty().build())
}
pub fn with_loadout(loadout: Loadout, body: Body) -> Inventory {
if let Body::Humanoid(_) = body {
Self::with_loadout_humanoid(loadout)
} else {
Self::with_loadout_animal(loadout)
}
}
pub fn with_loadout_humanoid(loadout: Loadout) -> Inventory {
Inventory {
next_sort_order: InventorySortOrder::Name,
loadout,
slots: vec![None; DEFAULT_INVENTORY_SLOTS],
overflow_items: Vec::new(),
recipe_book: RecipeBook::default(),
}
}
pub fn with_loadout_animal(loadout: Loadout) -> Inventory {
Inventory {
next_sort_order: InventorySortOrder::Name,
loadout,
slots: vec![None; 1],
overflow_items: Vec::new(),
recipe_book: RecipeBook::default(),
}
}
pub fn with_recipe_book(mut self, recipe_book: RecipeBook) -> Inventory {
self.recipe_book = recipe_book;
self
}
pub fn capacity(&self) -> usize { self.slots().count() }
pub fn slots(&self) -> impl Iterator<Item = &InvSlot> {
self.slots
.iter()
.chain(self.loadout.inv_slots_with_id().map(|(_, slot)| slot))
}
pub fn overflow_items(&self) -> impl Iterator<Item = &Item> { self.overflow_items.iter() }
fn slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
self.slots.iter_mut().chain(self.loadout.inv_slots_mut())
}
fn slots_mut_with_mutable_recently_unequipped_items(
&mut self,
) -> (
impl Iterator<Item = &mut InvSlot>,
&mut HashMap<ItemDefinitionIdOwned, (Time, u8)>,
) {
let (slots_mut, recently_unequipped) = self
.loadout
.inv_slots_mut_with_mutable_recently_unequipped_items();
(self.slots.iter_mut().chain(slots_mut), recently_unequipped)
}
pub fn slots_with_id(&self) -> impl Iterator<Item = (InvSlotId, &InvSlot)> {
self.slots
.iter()
.enumerate()
.map(|(i, slot)| ((InvSlotId::new(0, u16::try_from(i).unwrap())), slot))
.chain(
self.loadout
.inv_slots_with_id()
.map(|(loadout_slot_id, inv_slot)| (loadout_slot_id.into(), inv_slot)),
)
}
pub fn order_by_custom(custom_order: &[CustomOrder], a: &Item, b: &Item) -> Ordering {
let mut order = custom_order.iter();
let a_quality = a.quality();
let b_quality = b.quality();
let a_kind = a.kind().get_itemkind_string();
let b_kind = b.kind().get_itemkind_string();
let mut cmp = Ordering::Equal;
while cmp == Ordering::Equal {
match order.next() {
Some(CustomOrder::KindFull) => cmp = Ord::cmp(&a_kind, &b_kind),
Some(CustomOrder::KindPartial) => {
cmp = Ord::cmp(
&a_kind.split_once(':').unwrap().0,
&b_kind.split_once(':').unwrap().0,
)
},
Some(CustomOrder::Quality) => cmp = Ord::cmp(&b_quality, &a_quality),
#[allow(deprecated)]
Some(CustomOrder::Name) => cmp = Ord::cmp(&a.name(), &b.name()),
Some(CustomOrder::Tag) => {
cmp = Ord::cmp(
&a.tags().first().map_or("", |tag| tag.name()),
&b.tags().first().map_or("", |tag| tag.name()),
)
},
_ => break,
}
}
cmp
}
pub fn sort(&mut self) {
let sort_order = self.next_sort_order;
let mut items: Vec<Item> = self.slots_mut().filter_map(mem::take).collect();
items.sort_by(|a, b| match sort_order {
#[allow(deprecated)]
InventorySortOrder::Name => Ord::cmp(&a.name(), &b.name()),
InventorySortOrder::Quality => Ord::cmp(&b.quality(), &a.quality()),
InventorySortOrder::Category => {
let order = [
CustomOrder::KindPartial,
CustomOrder::Quality,
CustomOrder::KindFull,
CustomOrder::Name,
];
Self::order_by_custom(&order, a, b)
},
InventorySortOrder::Tag => Ord::cmp(
&a.tags().first().map_or("", |tag| tag.name()),
&b.tags().first().map_or("", |tag| tag.name()),
),
InventorySortOrder::Amount => Ord::cmp(&b.amount(), &a.amount()),
});
self.push_all(items.into_iter()).expect(
"It is impossible for there to be insufficient inventory space when sorting the \
inventory",
);
self.next_sort_order = self.next_sort_order.next();
}
pub fn next_sort_order(&self) -> InventorySortOrder { self.next_sort_order }
pub fn push(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
if item.is_stackable() {
let total_amount = item.amount();
let remaining = self
.slots_mut()
.filter_map(Option::as_mut)
.filter(|s| *s == &item)
.try_fold(total_amount, |remaining, current| {
debug_assert_eq!(
item.max_amount(),
current.max_amount(),
"max_amount of two equal items must match"
);
let new_remaining = remaining
.checked_sub(current.max_amount() - current.amount())
.filter(|&remaining| remaining > 0);
if new_remaining.is_some() {
current
.set_amount(current.max_amount())
.expect("max_amount() is always a valid amount");
} else {
current.increase_amount(remaining).expect(
"This item must be able to contain the remaining amount, because \
remaining < current.max_amount() - current.amount()",
);
}
new_remaining
});
if let Some(remaining) = remaining {
item.set_amount(remaining)
.expect("Remaining is known to be > 0");
self.insert(item)
.map_err(|item| (item, NonZeroU32::new(total_amount - remaining)))
} else {
Ok(())
}
} else {
self.insert(item).map_err(|item| (item, None))
}
}
pub fn push_all<I: Iterator<Item = Item>>(&mut self, items: I) -> Result<(), Error> {
let mut leftovers = Vec::new();
for item in items {
if let Err((item, _)) = self.push(item) {
leftovers.push(item);
}
}
if !leftovers.is_empty() {
Err(Error::Full(leftovers))
} else {
Ok(())
}
}
pub fn push_all_unique<I: Iterator<Item = Item>>(&mut self, mut items: I) -> Result<(), Error> {
let mut leftovers = Vec::new();
for item in &mut items {
if self.contains(&item).not() {
if let Err((overflow, _)) = self.push(item) {
leftovers.push(overflow);
}
} }
if !leftovers.is_empty() {
Err(Error::Full(leftovers))
} else {
Ok(())
}
}
pub fn insert_at(&mut self, inv_slot_id: InvSlotId, item: Item) -> Result<Option<Item>, Item> {
match self.slot_mut(inv_slot_id) {
Some(slot) => Ok(mem::replace(slot, Some(item))),
None => Err(item),
}
}
pub fn merge_stack_into(&mut self, src: InvSlotId, dst: InvSlotId) -> bool {
let mut amount = None;
if let (Some(srcitem), Some(dstitem)) = (self.get(src), self.get(dst)) {
if srcitem == dstitem && srcitem.is_stackable() {
amount = Some(srcitem.amount());
}
}
if let Some(amount) = amount {
let dstitem = self
.get_mut(dst)
.expect("self.get(dst) was Some right above this");
dstitem
.increase_amount(amount)
.map(|_| {
self.remove(src).expect("Already verified that src was populated.");
})
.is_ok()
} else {
false
}
}
pub fn insert_or_stack_at(
&mut self,
inv_slot_id: InvSlotId,
item: Item,
) -> Result<Option<Item>, Item> {
if item.is_stackable() {
match self.slot_mut(inv_slot_id) {
Some(Some(slot_item)) => {
Ok(if slot_item == &item {
slot_item
.increase_amount(item.amount())
.err()
.and(Some(item))
} else {
let old_item = mem::replace(slot_item, item);
Some(old_item)
})
},
Some(None) => self.insert_at(inv_slot_id, item),
None => Err(item),
}
} else {
self.insert_at(inv_slot_id, item)
}
}
#[must_use = "Returned item will be lost if not used"]
pub fn try_equip(&mut self, item: Item) -> Result<(), Item> { self.loadout.try_equip(item) }
pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() }
pub fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() }
pub fn contains(&self, item: &Item) -> bool {
self.slots().any(|slot| slot.as_ref() == Some(item))
}
pub fn get_slot_of_item(&self, item: &Item) -> Option<InvSlotId> {
self.slots_with_id()
.find(|&(_, it)| {
if let Some(it) = it {
it.item_definition_id() == item.item_definition_id()
} else {
false
}
})
.map(|(slot, _)| slot)
}
pub fn get_slot_of_item_by_def_id(
&self,
item_def_id: &item::ItemDefinitionIdOwned,
) -> Option<InvSlotId> {
self.slots_with_id()
.find(|&(_, it)| {
if let Some(it) = it {
it.item_definition_id() == *item_def_id
} else {
false
}
})
.map(|(slot, _)| slot)
}
pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
self.slot(inv_slot_id).and_then(Option::as_ref)
}
pub fn get_overflow(&self, overflow: usize) -> Option<&Item> {
self.overflow_items.get(overflow)
}
pub fn get_slot(&self, slot: Slot) -> Option<&Item> {
match slot {
Slot::Inventory(inv_slot) => self.get(inv_slot),
Slot::Equip(equip) => self.equipped(equip),
Slot::Overflow(overflow) => self.get_overflow(overflow),
}
}
pub fn get_by_hash(&self, item_hash: u64) -> Option<&Item> {
self.slots().flatten().find(|i| i.item_hash() == item_hash)
}
pub fn get_slot_from_hash(&self, item_hash: u64) -> Option<InvSlotId> {
let slot_with_id = self.slots_with_id().find(|slot| match slot.1 {
None => false,
Some(item) => item.item_hash() == item_hash,
});
slot_with_id.map(|s| s.0)
}
fn get_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut Item> {
self.slot_mut(inv_slot_id).and_then(Option::as_mut)
}
pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
self.loadout.equipped(equip_slot)
}
pub fn loadout_items_with_persistence_key(
&self,
) -> impl Iterator<Item = (&str, Option<&Item>)> {
self.loadout.items_with_persistence_key()
}
pub fn get_slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
let offset = self.slots.len();
self.loadout
.slot_range_for_equip_slot(equip_slot)
.map(|loadout_range| (loadout_range.start + offset)..(loadout_range.end + offset))
}
pub fn swap_slots(&mut self, a: InvSlotId, b: InvSlotId) {
if self.slot(a).is_none() || self.slot(b).is_none() {
warn!("swap_slots called with non-existent inventory slot(s)");
return;
}
let slot_a = mem::take(self.slot_mut(a).unwrap());
let slot_b = mem::take(self.slot_mut(b).unwrap());
*self.slot_mut(a).unwrap() = slot_b;
*self.slot_mut(b).unwrap() = slot_a;
}
pub fn move_overflow_item(&mut self, overflow: usize, inv_slot: InvSlotId) {
match self.slot(inv_slot) {
Some(Some(_)) => {
warn!("Attempted to move from overflow slot to a filled inventory slot");
return;
},
None => {
warn!("Attempted to move from overflow slot to a non-existent inventory slot");
return;
},
Some(None) => {},
};
let item = self.overflow_items.remove(overflow);
*self.slot_mut(inv_slot).unwrap() = Some(item);
}
pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
self.slot_mut(inv_slot_id).and_then(|item| item.take())
}
#[must_use = "Returned items will be lost if not used"]
pub fn overflow_remove(&mut self, overflow_slot: usize) -> Option<Item> {
if overflow_slot < self.overflow_items.len() {
Some(self.overflow_items.remove(overflow_slot))
} else {
None
}
}
pub fn take(
&mut self,
inv_slot_id: InvSlotId,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Option<Item> {
if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
let mut return_item = item.duplicate(ability_map, msm);
if item.is_stackable() && item.amount() > 1 {
item.decrease_amount(1).ok()?;
return_item
.set_amount(1)
.expect("Items duplicated from a stackable item must be stackable.");
Some(return_item)
} else {
self.remove(inv_slot_id)
}
} else {
None
}
}
pub fn take_amount(
&mut self,
inv_slot_id: InvSlotId,
amount: NonZeroU32,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Option<Item> {
if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
if item.is_stackable() && item.amount() > amount.get() {
let mut return_item = item.duplicate(ability_map, msm);
let return_amount = amount.get();
let new_amount = item.amount() - return_amount;
return_item
.set_amount(return_amount)
.expect("We know that 0 < return_amount < item.amount()");
item.set_amount(new_amount)
.expect("new_amount must be > 0 since return item is < item.amount");
Some(return_item)
} else {
self.remove(inv_slot_id)
}
} else {
None
}
}
#[must_use = "Returned items will be lost if not used"]
pub fn take_half(
&mut self,
inv_slot_id: InvSlotId,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Option<Item> {
if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
item.take_half(ability_map, msm)
.or_else(|| self.remove(inv_slot_id))
} else {
None
}
}
#[must_use = "Returned items will be lost if not used"]
pub fn overflow_take_half(
&mut self,
overflow_slot: usize,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Option<Item> {
if let Some(item) = self.overflow_items.get_mut(overflow_slot) {
item.take_half(ability_map, msm)
.or_else(|| self.overflow_remove(overflow_slot))
} else {
None
}
}
pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
self.slots_mut()
.filter(|x| x.is_some())
.filter_map(mem::take)
}
pub fn item_count(&self, item_def: &ItemDef) -> u64 {
self.slots()
.flatten()
.filter(|it| it.is_same_item_def(item_def))
.map(|it| u64::from(it.amount()))
.sum()
}
fn insert(&mut self, item: Item) -> Result<(), Item> {
match self.slots_mut().find(|slot| slot.is_none()) {
Some(slot) => {
*slot = Some(item);
Ok(())
},
None => Err(item),
}
}
pub fn slot(&self, inv_slot_id: InvSlotId) -> Option<&InvSlot> {
match SlotId::from(inv_slot_id) {
SlotId::Inventory(slot_idx) => self.slots.get(slot_idx),
SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot(loadout_slot_id),
}
}
pub fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> {
match SlotId::from(inv_slot_id) {
SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx),
SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot_mut(loadout_slot_id),
}
}
pub fn free_slots_minus_equipped_item(&self, equip_slot: EquipSlot) -> usize {
if let Some(mut equip_slot_idx) = self.loadout.loadout_idx_for_equip_slot(equip_slot) {
equip_slot_idx += 1;
self.slots_with_id()
.filter(|(inv_slot_id, slot)| {
inv_slot_id.loadout_idx() != equip_slot_idx && slot.is_none()
})
.count()
} else {
warn!(
"Attempted to fetch loadout index for non-existent EquipSlot: {:?}",
equip_slot
);
0
}
}
pub fn equipped_items(&self) -> impl Iterator<Item = &Item> { self.loadout.items() }
pub fn equipped_items_with_slot(&self) -> impl Iterator<Item = (EquipSlot, &Item)> {
self.loadout.items_with_slot()
}
pub fn replace_loadout_item(
&mut self,
equip_slot: EquipSlot,
replacement_item: Option<Item>,
time: Time,
) -> Option<Item> {
self.loadout.swap(equip_slot, replacement_item, time)
}
#[must_use = "Returned items will be lost if not used"]
pub fn equip(&mut self, inv_slot: InvSlotId, time: Time) -> Vec<Item> {
self.get(inv_slot)
.and_then(|item| self.loadout.get_slot_to_equip_into(&item.kind()))
.map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot, time))
.unwrap_or_default()
}
pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
let (inv_slot_for_equipped, slots_from_equipped) = self
.get(inv_slot)
.and_then(|item| self.loadout.get_slot_to_equip_into(&item.kind()))
.and_then(|equip_slot| self.equipped(equip_slot))
.map_or((1, 0), |item| (0, item.slots().len()));
let slots_from_inv = self
.get(inv_slot)
.map(|item| item.slots().len())
.unwrap_or(0);
i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
- i32::try_from(slots_from_equipped)
.expect("Equipped item with more than i32::MAX slots")
+ i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
- i32::try_from(self.populated_slots())
.expect("Inventory item with more than i32::MAX used slots")
+ inv_slot_for_equipped }
pub fn pickup_item(&mut self, mut item: Item) -> Result<(), (Item, Option<NonZeroU32>)> {
if item.is_stackable() {
return self.push(item);
}
if self.free_slots() < item.populated_slots() + 1 {
return Err((item, None));
}
item.drain().for_each(|item| {
self.push(item).unwrap();
});
self.push(item)
}
#[must_use = "Returned items will be lost if not used"]
#[allow(clippy::needless_collect)] pub fn unequip(
&mut self,
equip_slot: EquipSlot,
time: Time,
) -> Result<Option<Vec<Item>>, SlotError> {
if self.free_slots_minus_equipped_item(equip_slot) == 0 {
return Err(SlotError::InventoryFull);
}
Ok(self
.loadout
.swap(equip_slot, None, time)
.and_then(|mut unequipped_item| {
let unloaded_items: Vec<Item> = unequipped_item.drain().collect();
self.push(unequipped_item)
.expect("Failed to push item to inventory, precondition failed?");
match self.push_all(unloaded_items.into_iter()) {
Err(Error::Full(leftovers)) => Some(leftovers),
Ok(()) => None,
}
}))
}
pub fn free_after_unequip(&self, equip_slot: EquipSlot) -> i32 {
let (inv_slot_for_unequipped, slots_from_equipped) = self
.equipped(equip_slot)
.map_or((0, 0), |item| (1, item.slots().len()));
i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
- i32::try_from(slots_from_equipped)
.expect("Equipped item with more than i32::MAX slots")
- i32::try_from(self.populated_slots())
.expect("Inventory item with more than i32::MAX used slots")
- inv_slot_for_unequipped }
#[must_use = "Returned items will be lost if not used"]
pub fn swap(&mut self, slot_a: Slot, slot_b: Slot, time: Time) -> Vec<Item> {
match (slot_a, slot_b) {
(Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
self.swap_slots(slot_a, slot_b);
Vec::new()
},
(Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
| (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
self.swap_inventory_loadout(inv_slot, equip_slot, time)
},
(Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
self.loadout.swap_slots(slot_a, slot_b, time);
Vec::new()
},
(Slot::Overflow(overflow_slot), Slot::Inventory(inv_slot))
| (Slot::Inventory(inv_slot), Slot::Overflow(overflow_slot)) => {
self.move_overflow_item(overflow_slot, inv_slot);
Vec::new()
},
(Slot::Overflow(_), Slot::Equip(_)) | (Slot::Equip(_), Slot::Overflow(_)) => Vec::new(),
(Slot::Overflow(_), Slot::Overflow(_)) => Vec::new(),
}
}
pub fn free_after_swap(&self, equip_slot: EquipSlot, inv_slot: InvSlotId) -> i32 {
let (inv_slot_for_equipped, slots_from_equipped) = self
.equipped(equip_slot)
.map_or((0, 0), |item| (1, item.slots().len()));
let (inv_slot_for_inv_item, slots_from_inv_item) = self
.get(inv_slot)
.map_or((0, 0), |item| (1, item.slots().len()));
i32::try_from(self.capacity())
.expect("inventory with more than i32::MAX slots")
- i32::try_from(slots_from_equipped)
.expect("equipped item with more than i32::MAX slots")
+ i32::try_from(slots_from_inv_item)
.expect("inventory item with more than i32::MAX slots")
- i32::try_from(self.populated_slots())
.expect("inventory with more than i32::MAX used slots")
- inv_slot_for_equipped + inv_slot_for_inv_item }
#[must_use = "Returned items will be lost if not used"]
pub fn swap_inventory_loadout(
&mut self,
inv_slot_id: InvSlotId,
equip_slot: EquipSlot,
time: Time,
) -> Vec<Item> {
if !self.can_swap(inv_slot_id, equip_slot) {
return Vec::new();
}
let from_inv = self.remove(inv_slot_id);
let from_equip = self.loadout.swap(equip_slot, from_inv, time);
let unloaded_items = from_equip
.map(|mut from_equip| {
let mut items: Vec<Item> = from_equip.drain().collect();
if let Err(returned) = self.insert_at(inv_slot_id, from_equip) {
items.insert(0, returned);
}
items
})
.unwrap_or_default();
match equip_slot {
EquipSlot::ActiveMainhand => {
if self.loadout.equipped(EquipSlot::ActiveMainhand).is_none()
&& self.loadout.equipped(EquipSlot::ActiveOffhand).is_some()
{
let offhand = self.loadout.swap(EquipSlot::ActiveOffhand, None, time);
assert!(
self.loadout
.swap(EquipSlot::ActiveMainhand, offhand, time)
.is_none()
);
}
},
EquipSlot::InactiveMainhand => {
if self.loadout.equipped(EquipSlot::InactiveMainhand).is_none()
&& self.loadout.equipped(EquipSlot::InactiveOffhand).is_some()
{
let offhand = self.loadout.swap(EquipSlot::InactiveOffhand, None, time);
assert!(
self.loadout
.swap(EquipSlot::InactiveMainhand, offhand, time)
.is_none()
);
}
},
_ => {},
}
match self.push_all(unloaded_items.into_iter()) {
Err(Error::Full(leftovers)) => leftovers,
Ok(()) => Vec::new(),
}
}
pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
if !self.get(inv_slot_id).map_or(true, |item| {
self.loadout.slot_can_hold(equip_slot, Some(&*item.kind()))
}) {
trace!("can_swap = false, equip slot can't hold item");
return false;
}
if self.slot(inv_slot_id).is_none() {
debug!(
"can_swap = false, tried to swap into non-existent inventory slot: {:?}",
inv_slot_id
);
return false;
}
true
}
pub fn equipped_items_replaceable_by<'a>(
&'a self,
item_kind: &'a ItemKind,
) -> impl Iterator<Item = &'a Item> {
self.loadout.equipped_items_replaceable_by(item_kind)
}
pub fn swap_equipped_weapons(&mut self, time: Time) { self.loadout.swap_equipped_weapons(time) }
pub fn persistence_update_all_item_states(
&mut self,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) {
self.slots_mut().for_each(|slot| {
if let Some(item) = slot {
item.update_item_state(ability_map, msm);
}
});
self.overflow_items
.iter_mut()
.for_each(|item| item.update_item_state(ability_map, msm));
}
pub fn damage_items(
&mut self,
ability_map: &item::tool::AbilityMap,
msm: &item::MaterialStatManifest,
time: Time,
) {
self.loadout.damage_items(ability_map, msm);
self.loadout.cull_recently_unequipped_items(time);
let (slots_mut, recently_unequipped_items) =
self.slots_mut_with_mutable_recently_unequipped_items();
slots_mut.filter_map(|slot| slot.as_mut()).for_each(|item| {
if item
.durability_lost()
.map_or(false, |dur| dur < Item::MAX_DURABILITY)
&& let Some((_unequip_time, count)) =
recently_unequipped_items.get_mut(&item.item_definition_id())
&& *count > 0
{
*count -= 1;
item.increment_damage(ability_map, msm);
}
});
}
pub fn repair_item_at_slot(
&mut self,
slot: Slot,
ability_map: &item::tool::AbilityMap,
msm: &item::MaterialStatManifest,
) {
match slot {
Slot::Inventory(invslot) => {
if let Some(Some(item)) = self.slot_mut(invslot) {
item.reset_durability(ability_map, msm);
}
},
Slot::Equip(equip_slot) => {
self.loadout
.repair_item_at_slot(equip_slot, ability_map, msm);
},
Slot::Overflow(_) => {},
}
}
pub fn persistence_push_overflow_items<I: Iterator<Item = Item>>(&mut self, overflow_items: I) {
self.overflow_items.extend(overflow_items);
}
pub fn recipes_iter(&self) -> impl ExactSizeIterator<Item = &String> { self.recipe_book.iter() }
pub fn recipe_groups_iter(&self) -> impl ExactSizeIterator<Item = &Item> {
self.recipe_book.iter_groups()
}
pub fn available_recipes_iter<'a>(
&'a self,
rbm: &'a RecipeBookManifest,
) -> impl Iterator<Item = (&String, &Recipe)> + '_ {
self.recipe_book.get_available_iter(rbm)
}
pub fn recipe_book_len(&self) -> usize { self.recipe_book.len() }
pub fn get_recipe<'a>(
&'a self,
recipe_key: &str,
rbm: &'a RecipeBookManifest,
) -> Option<&Recipe> {
self.recipe_book.get(recipe_key, rbm)
}
pub fn push_recipe_group(&mut self, recipe_group: Item) -> Result<(), Item> {
self.recipe_book.push_group(recipe_group)
}
pub fn can_craft_recipe(
&self,
recipe_key: &str,
amount: u32,
rbm: &RecipeBookManifest,
) -> (bool, Option<SpriteKind>) {
if let Some(recipe) = self.recipe_book.get(recipe_key, rbm) {
(
recipe.inventory_contains_ingredients(self, amount).is_ok(),
recipe.craft_sprite,
)
} else {
(false, None)
}
}
pub fn recipe_is_known(&self, recipe_key: &str) -> bool {
self.recipe_book.is_known(recipe_key)
}
pub fn reset_recipes(&mut self) { self.recipe_book.reset(); }
pub fn persistence_recipes_iter_with_index(&self) -> impl Iterator<Item = (usize, &Item)> {
self.recipe_book.persistence_recipes_iter_with_index()
}
}
impl Component for Inventory {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum CollectFailedReason {
InventoryFull,
LootOwned {
owner: LootOwnerKind,
expiry_secs: u64,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InventoryUpdateEvent {
Init,
Used,
Consumed(ItemKey),
Gave,
Given,
Swapped,
Dropped,
Collected(FrontendItem),
BlockCollectFailed {
pos: Vec3<i32>,
reason: CollectFailedReason,
},
EntityCollectFailed {
entity: Uid,
reason: CollectFailedReason,
},
Possession,
Debug,
Craft,
}
impl Default for InventoryUpdateEvent {
fn default() -> Self { Self::Init }
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct InventoryUpdate {
events: Vec<InventoryUpdateEvent>,
}
impl InventoryUpdate {
pub fn new(event: InventoryUpdateEvent) -> Self {
Self {
events: vec![event],
}
}
pub fn push(&mut self, event: InventoryUpdateEvent) { self.events.push(event); }
pub fn take_events(&mut self) -> Vec<InventoryUpdateEvent> { std::mem::take(&mut self.events) }
}
impl Component for InventoryUpdate {
type Storage = specs::VecStorage<Self>;
}