2019-05-18 16:46:14 +00:00
|
|
|
pub mod item;
|
2020-04-10 02:36:35 +00:00
|
|
|
pub mod slot;
|
2019-05-18 16:46:14 +00:00
|
|
|
|
2020-09-26 15:20:46 +00:00
|
|
|
use crate::{comp::inventory::item::ItemDef, recipe::Recipe};
|
2020-09-17 23:02:14 +00:00
|
|
|
use core::ops::Not;
|
|
|
|
use item::Item;
|
2020-07-06 14:23:08 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2020-03-04 10:09:48 +00:00
|
|
|
use specs::{Component, FlaggedStorage, HashMapStorage};
|
2020-07-06 05:56:02 +00:00
|
|
|
use specs_idvs::IdvStorage;
|
2019-05-18 16:46:14 +00:00
|
|
|
|
2020-07-18 00:05:28 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2019-05-18 16:46:14 +00:00
|
|
|
pub struct Inventory {
|
2020-09-17 23:02:14 +00:00
|
|
|
slots: Vec<Option<Item>>,
|
|
|
|
amount: u32,
|
2019-05-18 16:46:14 +00:00
|
|
|
}
|
|
|
|
|
2019-10-31 04:47:17 +00:00
|
|
|
/// Errors which the methods on `Inventory` produce
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
2020-02-01 20:39:39 +00:00
|
|
|
/// The inventory is full and items could not be added. The extra items have
|
|
|
|
/// been returned.
|
2019-10-31 04:47:17 +00:00
|
|
|
Full(Vec<Item>),
|
|
|
|
}
|
|
|
|
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::len_without_is_empty)] // TODO: Pending review in #587
|
2019-05-18 16:46:14 +00:00
|
|
|
impl Inventory {
|
2020-09-17 23:02:14 +00:00
|
|
|
pub fn new_empty() -> Inventory {
|
|
|
|
Inventory {
|
|
|
|
slots: vec![None; 36],
|
|
|
|
amount: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn slots(&self) -> &[Option<Item>] { &self.slots }
|
2019-07-25 17:41:06 +00:00
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn len(&self) -> usize { self.slots.len() }
|
2019-07-25 22:52:28 +00:00
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
/// Total number of occupied slots in the inventory.
|
|
|
|
pub fn amount(&self) -> u32 { self.amount }
|
|
|
|
|
2020-03-23 23:23:21 +00:00
|
|
|
pub fn recount_items(&mut self) {
|
2020-03-24 12:59:53 +00:00
|
|
|
self.amount = self.slots.iter().filter(|i| i.is_some()).count() as u32;
|
2020-03-23 23:23:21 +00:00
|
|
|
}
|
|
|
|
|
2020-03-19 13:30:50 +00:00
|
|
|
/// Adds a new item to the first fitting group of the inventory or starts a
|
|
|
|
/// new group. Returns the item again if no space was found.
|
|
|
|
pub fn push(&mut self, item: Item) -> Option<Item> {
|
2020-09-17 23:02:14 +00:00
|
|
|
if item.is_stackable() {
|
|
|
|
if let Some(slot_item) = self
|
|
|
|
.slots
|
|
|
|
.iter_mut()
|
|
|
|
.filter_map(Option::as_mut)
|
|
|
|
.find(|s| *s == &item)
|
|
|
|
{
|
|
|
|
return slot_item
|
|
|
|
.increase_amount(item.amount())
|
|
|
|
.err()
|
|
|
|
.and(Some(item));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No existing item to stack with or item not stackable, put the item in a new
|
|
|
|
// slot
|
|
|
|
self.add_to_first_empty(item)
|
2020-03-19 13:30:50 +00:00
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Adds a new item to the first empty slot of the inventory. Returns the
|
|
|
|
/// item again if no free slot was found.
|
2020-03-19 13:30:50 +00:00
|
|
|
fn add_to_first_empty(&mut self, item: Item) -> Option<Item> {
|
2020-03-23 23:23:21 +00:00
|
|
|
let item = match self.slots.iter_mut().find(|slot| slot.is_none()) {
|
2019-07-25 22:52:28 +00:00
|
|
|
Some(slot) => {
|
|
|
|
*slot = Some(item);
|
2019-08-30 19:02:18 +00:00
|
|
|
None
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-08-30 19:02:18 +00:00
|
|
|
None => Some(item),
|
2020-03-23 23:23:21 +00:00
|
|
|
};
|
|
|
|
self.recount_items();
|
|
|
|
item
|
2019-08-28 20:47:52 +00:00
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Add a series of items to inventory, returning any which do not fit as an
|
|
|
|
/// error.
|
2020-07-14 20:11:39 +00:00
|
|
|
pub fn push_all<I: Iterator<Item = Item>>(&mut self, items: I) -> Result<(), Error> {
|
2019-10-31 04:47:17 +00:00
|
|
|
// Vec doesn't allocate for zero elements so this should be cheap
|
|
|
|
let mut leftovers = Vec::new();
|
2020-07-14 20:11:39 +00:00
|
|
|
for item in items {
|
|
|
|
if let Some(item) = self.push(item) {
|
2019-10-31 04:47:17 +00:00
|
|
|
leftovers.push(item);
|
|
|
|
}
|
|
|
|
}
|
2020-06-08 18:37:41 +00:00
|
|
|
if !leftovers.is_empty() {
|
2019-10-31 04:47:17 +00:00
|
|
|
Err(Error::Full(leftovers))
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-27 16:48:42 +00:00
|
|
|
/// Add a series of items to an inventory without giving duplicates.
|
|
|
|
/// (n * m complexity)
|
2019-11-07 01:57:05 +00:00
|
|
|
///
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Error if inventory cannot contain the items (is full), returning the
|
|
|
|
/// un-added items. This is a lazy inefficient implementation, as it
|
|
|
|
/// iterates over the inventory more times than necessary (n^2) and with
|
|
|
|
/// the proper structure wouldn't need to iterate at all, but because
|
|
|
|
/// this should be fairly cold code, clarity has been favored over
|
|
|
|
/// efficiency.
|
2019-10-31 14:26:25 +00:00
|
|
|
pub fn push_all_unique<I: Iterator<Item = Item>>(&mut self, mut items: I) -> Result<(), Error> {
|
2019-10-31 04:47:17 +00:00
|
|
|
let mut leftovers = Vec::new();
|
|
|
|
for item in &mut items {
|
|
|
|
if self.contains(&item).not() {
|
2019-10-31 14:26:25 +00:00
|
|
|
self.push(item).map(|overflow| leftovers.push(overflow));
|
2019-10-31 04:47:17 +00:00
|
|
|
} // else drop item if it was already in
|
|
|
|
}
|
2020-06-08 18:37:41 +00:00
|
|
|
if !leftovers.is_empty() {
|
2019-10-31 04:47:17 +00:00
|
|
|
Err(Error::Full(leftovers))
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Replaces an item in a specific slot of the inventory. Returns the old
|
|
|
|
/// item or the same item again if that slot was not found.
|
2019-08-30 22:09:25 +00:00
|
|
|
pub fn insert(&mut self, cell: usize, item: Item) -> Result<Option<Item>, Item> {
|
2019-08-28 20:47:52 +00:00
|
|
|
match self.slots.get_mut(cell) {
|
|
|
|
Some(slot) => {
|
2020-09-17 23:02:14 +00:00
|
|
|
let old = core::mem::replace(slot, Some(item));
|
2020-09-18 23:17:47 +00:00
|
|
|
if old.is_none() {
|
2020-09-17 23:02:14 +00:00
|
|
|
self.recount_items();
|
|
|
|
}
|
2019-08-30 22:09:25 +00:00
|
|
|
Ok(old)
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-08-30 22:09:25 +00:00
|
|
|
None => Err(item),
|
2019-07-25 22:52:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-18 02:41:45 +00:00
|
|
|
/// Checks if inserting item exists in given cell. Inserts an item if it
|
|
|
|
/// exists.
|
|
|
|
pub fn insert_or_stack(&mut self, cell: usize, item: Item) -> Result<Option<Item>, Item> {
|
2020-09-17 23:02:14 +00:00
|
|
|
if item.is_stackable() {
|
|
|
|
match self.slots.get_mut(cell) {
|
2020-07-18 02:41:45 +00:00
|
|
|
Some(Some(slot_item)) => {
|
2020-09-17 23:02:14 +00:00
|
|
|
Ok(if slot_item == &item {
|
|
|
|
slot_item
|
|
|
|
.increase_amount(item.amount())
|
|
|
|
.err()
|
|
|
|
.and(Some(item))
|
2020-07-18 02:41:45 +00:00
|
|
|
} else {
|
2020-09-17 23:02:14 +00:00
|
|
|
let old_item = core::mem::replace(slot_item, item);
|
|
|
|
// No need to recount--we know the count is the same.
|
|
|
|
Some(old_item)
|
|
|
|
})
|
2020-07-18 02:41:45 +00:00
|
|
|
},
|
|
|
|
Some(None) => self.insert(cell, item),
|
|
|
|
None => Err(item),
|
2020-09-17 23:02:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.insert(cell, item)
|
2020-07-18 02:41:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn is_full(&self) -> bool { self.slots.iter().all(|slot| slot.is_some()) }
|
2019-09-25 22:53:43 +00:00
|
|
|
|
2019-10-31 04:47:17 +00:00
|
|
|
/// O(n) count the number of items in this inventory.
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn count(&self) -> usize { self.slots.iter().filter_map(|slot| slot.as_ref()).count() }
|
2019-10-31 04:47:17 +00:00
|
|
|
|
|
|
|
/// O(n) check if an item is in this inventory.
|
|
|
|
pub fn contains(&self, item: &Item) -> bool {
|
2019-11-06 22:57:54 +00:00
|
|
|
self.slots.iter().any(|slot| slot.as_ref() == Some(item))
|
2019-10-31 04:47:17 +00:00
|
|
|
}
|
|
|
|
|
2019-08-30 20:42:43 +00:00
|
|
|
/// Get content of a slot
|
|
|
|
pub fn get(&self, cell: usize) -> Option<&Item> {
|
|
|
|
self.slots.get(cell).and_then(Option::as_ref)
|
2019-05-18 16:46:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-30 20:42:43 +00:00
|
|
|
/// Swap the items inside of two slots
|
2019-07-25 22:52:28 +00:00
|
|
|
pub fn swap_slots(&mut self, a: usize, b: usize) {
|
|
|
|
if a.max(b) < self.slots.len() {
|
|
|
|
self.slots.swap(a, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-30 20:42:43 +00:00
|
|
|
/// Remove an item from the slot
|
2019-07-25 12:46:54 +00:00
|
|
|
pub fn remove(&mut self, cell: usize) -> Option<Item> {
|
2020-03-23 23:23:21 +00:00
|
|
|
let item = self.slots.get_mut(cell).and_then(|item| item.take());
|
|
|
|
self.recount_items();
|
|
|
|
item
|
2019-05-18 16:46:14 +00:00
|
|
|
}
|
2020-03-19 13:30:50 +00:00
|
|
|
|
|
|
|
/// Remove just one item from the slot
|
|
|
|
pub fn take(&mut self, cell: usize) -> Option<Item> {
|
|
|
|
if let Some(Some(item)) = self.slots.get_mut(cell) {
|
2020-09-17 23:02:14 +00:00
|
|
|
let mut return_item = item.duplicate();
|
|
|
|
|
|
|
|
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.");
|
|
|
|
self.recount_items();
|
|
|
|
Some(return_item)
|
|
|
|
} else {
|
|
|
|
self.remove(cell)
|
2020-03-19 13:30:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2020-07-14 20:11:39 +00:00
|
|
|
|
|
|
|
/// Determine how many of a particular item there is in the inventory.
|
2020-09-26 15:20:46 +00:00
|
|
|
pub fn item_count(&self, item_def: &ItemDef) -> u64 {
|
2020-07-14 20:11:39 +00:00
|
|
|
self.slots()
|
|
|
|
.iter()
|
|
|
|
.flatten()
|
2020-09-26 15:20:46 +00:00
|
|
|
.filter(|it| it.is_same_item_def(item_def))
|
2020-09-17 23:02:14 +00:00
|
|
|
.map(|it| u64::from(it.amount()))
|
2020-07-14 20:11:39 +00:00
|
|
|
.sum()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determine whether the inventory contains the ingredients for a recipe.
|
|
|
|
/// If it does, return a vector of numbers, where is number corresponds
|
|
|
|
/// to an inventory slot, along with the number of items that need
|
|
|
|
/// removing from it. It items are missing, return the missing items, and
|
|
|
|
/// how many are missing.
|
|
|
|
pub fn contains_ingredients<'a>(
|
|
|
|
&self,
|
|
|
|
recipe: &'a Recipe,
|
2020-09-26 15:20:46 +00:00
|
|
|
) -> Result<Vec<u32>, Vec<(&'a ItemDef, u32)>> {
|
2020-07-14 20:11:39 +00:00
|
|
|
let mut slot_claims = vec![0; self.slots.len()];
|
2020-09-26 15:20:46 +00:00
|
|
|
let mut missing = Vec::<(&ItemDef, u32)>::new();
|
2020-07-14 20:11:39 +00:00
|
|
|
|
|
|
|
for (input, mut needed) in recipe.inputs() {
|
|
|
|
let mut contains_any = false;
|
|
|
|
|
|
|
|
for (i, slot) in self.slots().iter().enumerate() {
|
2020-09-26 15:20:46 +00:00
|
|
|
if let Some(item) = slot.as_ref().filter(|item| item.is_same_item_def(&*input)) {
|
2020-09-17 23:02:14 +00:00
|
|
|
let can_claim = (item.amount() - slot_claims[i]).min(needed);
|
2020-07-14 20:11:39 +00:00
|
|
|
slot_claims[i] += can_claim;
|
|
|
|
needed -= can_claim;
|
|
|
|
contains_any = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if needed > 0 || !contains_any {
|
|
|
|
missing.push((input, needed));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 14:10:12 +00:00
|
|
|
if missing.is_empty() {
|
2020-07-14 20:11:39 +00:00
|
|
|
Ok(slot_claims)
|
|
|
|
} else {
|
|
|
|
Err(missing)
|
|
|
|
}
|
|
|
|
}
|
2019-05-18 16:46:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-25 14:02:05 +00:00
|
|
|
impl Default for Inventory {
|
|
|
|
fn default() -> Inventory {
|
2019-08-26 18:05:13 +00:00
|
|
|
let mut inventory = Inventory {
|
2020-06-03 17:59:09 +00:00
|
|
|
slots: vec![None; 36],
|
2020-03-23 23:23:21 +00:00
|
|
|
amount: 0,
|
2019-07-25 22:52:28 +00:00
|
|
|
};
|
2020-11-05 09:18:06 +00:00
|
|
|
inventory.push(Item::new_from_asset_expect(
|
|
|
|
"common.items.consumable.potion_minor",
|
|
|
|
));
|
2020-09-17 23:02:14 +00:00
|
|
|
inventory.push(Item::new_from_asset_expect("common.items.food.cheese"));
|
2019-08-26 18:05:13 +00:00
|
|
|
inventory
|
2019-07-25 14:02:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-18 16:46:14 +00:00
|
|
|
impl Component for Inventory {
|
2019-07-31 09:30:46 +00:00
|
|
|
type Storage = HashMapStorage<Self>;
|
2019-05-18 16:46:14 +00:00
|
|
|
}
|
2019-07-25 14:48:27 +00:00
|
|
|
|
2020-07-18 00:05:28 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2020-03-04 10:09:48 +00:00
|
|
|
pub enum InventoryUpdateEvent {
|
|
|
|
Init,
|
|
|
|
Used,
|
2020-08-04 23:21:42 +00:00
|
|
|
Consumed(String),
|
2020-03-04 10:09:48 +00:00
|
|
|
Gave,
|
|
|
|
Given,
|
|
|
|
Swapped,
|
|
|
|
Dropped,
|
2020-06-26 18:40:28 +00:00
|
|
|
Collected(Item),
|
2020-03-11 10:30:59 +00:00
|
|
|
CollectFailed,
|
2020-03-04 10:09:48 +00:00
|
|
|
Possession,
|
|
|
|
Debug,
|
2020-07-14 20:11:39 +00:00
|
|
|
Craft,
|
2020-03-04 10:09:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for InventoryUpdateEvent {
|
|
|
|
fn default() -> Self { Self::Init }
|
|
|
|
}
|
|
|
|
|
2020-06-26 18:40:28 +00:00
|
|
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
2020-03-04 10:09:48 +00:00
|
|
|
pub struct InventoryUpdate {
|
|
|
|
event: InventoryUpdateEvent,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InventoryUpdate {
|
|
|
|
pub fn new(event: InventoryUpdateEvent) -> Self { Self { event } }
|
|
|
|
|
2020-06-26 18:40:28 +00:00
|
|
|
pub fn event(&self) -> InventoryUpdateEvent { self.event.clone() }
|
2020-03-04 10:09:48 +00:00
|
|
|
}
|
2019-07-25 14:48:27 +00:00
|
|
|
|
|
|
|
impl Component for InventoryUpdate {
|
2020-07-06 05:56:02 +00:00
|
|
|
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
2019-07-25 14:48:27 +00:00
|
|
|
}
|
2019-10-31 04:47:17 +00:00
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
#[cfg(test)] mod test;
|