veloren/common/src/comp/inventory/mod.rs

566 lines
20 KiB
Rust
Raw Normal View History

pub mod item;
pub mod slot;
use crate::{assets::Asset, recipe::Recipe};
use item::{Item, ItemAsset, ItemKind};
use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage, HashMapStorage};
use specs_idvs::IdvStorage;
use std::ops::Not;
// The limit on distance between the entity and a collectible (squared)
pub const MAX_PICKUP_RANGE_SQR: f32 = 64.0;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Inventory {
pub slots: Vec<Option<Item>>,
2020-03-23 23:23:21 +00:00
pub amount: u32,
}
/// Errors which the methods on `Inventory` produce
#[derive(Debug)]
pub enum Error {
/// The inventory is full and items could not be added. The extra items have
/// been returned.
Full(Vec<Item>),
}
#[allow(clippy::len_without_is_empty)] // TODO: Pending review in #587
impl Inventory {
pub fn slots(&self) -> &[Option<Item>] { &self.slots }
pub fn len(&self) -> usize { self.slots.len() }
2019-07-25 22:52:28 +00:00
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> {
let item = match &item.kind {
ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => {
self.add_to_first_empty(item)
},
2020-03-19 13:30:50 +00:00
ItemKind::Utility {
kind: item_kind,
amount: new_amount,
} => {
for slot in &mut self.slots {
if slot
.as_ref()
.map(|s| s.name() == item.name())
.unwrap_or(false)
&& slot
.as_ref()
.map(|s| s.description() == item.description())
.unwrap_or(false)
{
if let Some(Item {
kind: ItemKind::Utility { kind, amount },
..
}) = slot
{
if *item_kind == *kind {
2020-03-19 13:30:50 +00:00
*amount += new_amount;
2020-03-23 23:23:21 +00:00
self.recount_items();
2020-03-19 13:30:50 +00:00
return None;
}
}
}
}
// It didn't work
self.add_to_first_empty(item)
},
ItemKind::Consumable {
kind: item_kind,
amount: new_amount,
..
} => {
for slot in &mut self.slots {
if slot
.as_ref()
.map(|s| s.name() == item.name())
.unwrap_or(false)
&& slot
.as_ref()
.map(|s| s.description() == item.description())
.unwrap_or(false)
{
if let Some(Item {
kind: ItemKind::Consumable { kind, amount, .. },
..
}) = slot
{
if *item_kind == *kind {
2020-03-19 13:30:50 +00:00
*amount += new_amount;
2020-03-23 23:23:21 +00:00
self.recount_items();
2020-03-19 13:30:50 +00:00
return None;
}
}
}
}
// It didn't work
self.add_to_first_empty(item)
},
ItemKind::Throwable {
kind: item_kind,
amount: new_amount,
..
} => {
for slot in &mut self.slots {
if slot
.as_ref()
.map(|s| s.name() == item.name())
.unwrap_or(false)
&& slot
.as_ref()
.map(|s| s.description() == item.description())
.unwrap_or(false)
{
if let Some(Item {
kind: ItemKind::Throwable { kind, amount, .. },
..
}) = slot
{
if *item_kind == *kind {
*amount += new_amount;
self.recount_items();
return None;
}
}
}
}
// It didn't work
self.add_to_first_empty(item)
},
2020-03-19 13:30:50 +00:00
ItemKind::Ingredient {
kind: item_kind,
amount: new_amount,
} => {
for slot in &mut self.slots {
if slot
.as_ref()
.map(|s| s.name() == item.name())
.unwrap_or(false)
&& slot
.as_ref()
.map(|s| s.description() == item.description())
.unwrap_or(false)
{
if let Some(Item {
kind: ItemKind::Ingredient { kind, amount },
..
}) = slot
{
if *item_kind == *kind {
2020-03-19 13:30:50 +00:00
*amount += new_amount;
2020-03-23 23:23:21 +00:00
self.recount_items();
2020-03-19 13:30:50 +00:00
return None;
}
}
}
}
// It didn't work
self.add_to_first_empty(item)
},
2020-03-23 23:23:21 +00:00
};
self.recount_items();
item
2020-03-19 13:30:50 +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);
None
},
None => Some(item),
2020-03-23 23:23:21 +00:00
};
self.recount_items();
item
}
/// 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> {
// 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) {
leftovers.push(item);
}
}
2020-03-23 23:23:21 +00:00
self.recount_items();
if !leftovers.is_empty() {
Err(Error::Full(leftovers))
} else {
Ok(())
}
}
/// Add a series of items to an inventory without giving duplicates.
/// (n * m complexity)
2019-11-07 01:57:05 +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> {
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));
} // else drop item if it was already in
}
if !leftovers.is_empty() {
Err(Error::Full(leftovers))
} else {
Ok(())
}
}
/// 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.
pub fn insert(&mut self, cell: usize, item: Item) -> Result<Option<Item>, Item> {
match self.slots.get_mut(cell) {
Some(slot) => {
let old = slot.take();
*slot = Some(item);
2020-03-23 23:23:21 +00:00
self.recount_items();
Ok(old)
},
None => Err(item),
2019-07-25 22:52:28 +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> {
match &item.kind {
ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => {
self.insert(cell, item)
},
ItemKind::Utility {
amount: new_amount, ..
} => match self.slots.get_mut(cell) {
Some(Some(slot_item)) => {
if slot_item.name() == item.name()
&& slot_item.description() == item.description()
{
if let Item {
kind: ItemKind::Utility { amount, .. },
..
} = slot_item
{
*amount += *new_amount;
self.recount_items();
Ok(None)
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
},
Some(None) => self.insert(cell, item),
None => Err(item),
},
ItemKind::Ingredient {
amount: new_amount, ..
} => match self.slots.get_mut(cell) {
Some(Some(slot_item)) => {
if slot_item.name() == item.name()
&& slot_item.description() == item.description()
{
if let Item {
kind: ItemKind::Ingredient { amount, .. },
..
} = slot_item
{
*amount += *new_amount;
self.recount_items();
Ok(None)
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
},
Some(None) => self.insert(cell, item),
None => Err(item),
},
ItemKind::Consumable {
amount: new_amount, ..
} => match self.slots.get_mut(cell) {
Some(Some(slot_item)) => {
if slot_item.name() == item.name()
&& slot_item.description() == item.description()
{
if let Item {
kind: ItemKind::Consumable { amount, .. },
..
} = slot_item
{
*amount += *new_amount;
self.recount_items();
Ok(None)
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
},
Some(None) => self.insert(cell, item),
None => Err(item),
},
ItemKind::Throwable {
amount: new_amount, ..
} => match self.slots.get_mut(cell) {
Some(Some(slot_item)) => {
if slot_item.name() == item.name()
&& slot_item.description() == item.description()
{
if let Item {
kind: ItemKind::Throwable { amount, .. },
..
} = slot_item
{
*amount += *new_amount;
self.recount_items();
Ok(None)
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
} else {
let old_item = std::mem::replace(slot_item, item);
self.recount_items();
Ok(Some(old_item))
}
},
Some(None) => self.insert(cell, item),
None => Err(item),
},
}
}
pub fn is_full(&self) -> bool { self.slots.iter().all(|slot| slot.is_some()) }
2019-09-25 22:53:43 +00:00
/// O(n) count the number of items in this inventory.
pub fn count(&self) -> usize { self.slots.iter().filter_map(|slot| slot.as_ref()).count() }
/// O(n) check if an item is in this inventory.
pub fn contains(&self, item: &Item) -> bool {
self.slots.iter().any(|slot| slot.as_ref() == Some(item))
}
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-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
}
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) {
let mut return_item = item.clone();
match &mut item.kind {
ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => {
self.remove(cell)
},
2020-03-19 13:30:50 +00:00
ItemKind::Utility { kind, amount } => {
if *amount <= 1 {
self.remove(cell)
} else {
*amount -= 1;
return_item.kind = ItemKind::Utility {
kind: *kind,
amount: 1,
};
2020-03-23 23:23:21 +00:00
self.recount_items();
2020-03-19 13:30:50 +00:00
Some(return_item)
}
},
ItemKind::Consumable {
kind,
amount,
effect,
} => {
if *amount <= 1 {
self.remove(cell)
} else {
*amount -= 1;
return_item.kind = ItemKind::Consumable {
kind: kind.clone(),
2020-03-19 13:30:50 +00:00
effect: *effect,
amount: 1,
};
2020-03-23 23:23:21 +00:00
self.recount_items();
2020-03-19 13:30:50 +00:00
Some(return_item)
}
},
ItemKind::Throwable { kind, amount } => {
if *amount <= 1 {
self.remove(cell)
} else {
*amount -= 1;
return_item.kind = ItemKind::Throwable {
kind: *kind,
amount: 1,
};
self.recount_items();
Some(return_item)
}
},
2020-03-19 13:30:50 +00:00
ItemKind::Ingredient { kind, amount } => {
if *amount <= 1 {
self.remove(cell)
} else {
*amount -= 1;
return_item.kind = ItemKind::Ingredient {
kind: kind.clone(),
2020-03-19 13:30:50 +00:00
amount: 1,
};
2020-03-23 23:23:21 +00:00
self.recount_items();
2020-03-19 13:30:50 +00:00
Some(return_item)
}
},
}
} else {
None
}
}
2020-07-14 20:11:39 +00:00
/// Determine how many of a particular item there is in the inventory.
pub fn item_count(&self, item: &Item) -> usize {
self.slots()
.iter()
.flatten()
.filter(|it| it.superficially_eq(item))
.map(|it| it.amount() as usize)
.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,
) -> Result<Vec<usize>, Vec<(&'a Item, usize)>> {
let mut slot_claims = vec![0; self.slots.len()];
let mut missing = Vec::new();
for (input, mut needed) in recipe.inputs() {
let mut contains_any = false;
for (i, slot) in self.slots().iter().enumerate() {
if let Some(item) = slot.as_ref().filter(|item| item.superficially_eq(input)) {
let can_claim = (item.amount() as usize - slot_claims[i]).min(needed);
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-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
};
inventory.push(ItemAsset::load_expect_cloned("common.items.food.cheese"));
inventory.push(ItemAsset::load_expect_cloned("common.items.food.apple"));
2019-08-26 18:05:13 +00:00
inventory
2019-07-25 14:02:05 +00:00
}
}
impl Component for Inventory {
2019-07-31 09:30:46 +00:00
type Storage = HashMapStorage<Self>;
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InventoryUpdateEvent {
Init,
Used,
Consumed(String),
Gave,
Given,
Swapped,
Dropped,
Collected(Item),
CollectFailed,
Possession,
Debug,
2020-07-14 20:11:39 +00:00
Craft,
}
impl Default for InventoryUpdateEvent {
fn default() -> Self { Self::Init }
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct InventoryUpdate {
event: InventoryUpdateEvent,
}
impl InventoryUpdate {
pub fn new(event: InventoryUpdateEvent) -> Self { Self { event } }
pub fn event(&self) -> InventoryUpdateEvent { self.event.clone() }
}
impl Component for InventoryUpdate {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}
#[cfg(test)] mod test;