diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 811c72ba8d..ce5eef18e1 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -5,12 +5,20 @@ pub use item::{Debug, Item, ItemKind, Tool}; use crate::assets; use specs::{Component, HashMapStorage, NullStorage}; +use std::ops::Not; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Inventory { pub slots: Vec>, } +/// 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), +} + impl Inventory { pub fn slots(&self) -> &[Option] { &self.slots @@ -32,6 +40,45 @@ impl Inventory { } } + /// Add a series of items to inventory, returning any which do not fit as an error. + pub fn push_all>(&mut self, mut items: I) -> Result<(), Error> { + // Vec doesn't allocate for zero elements so this should be cheap + let mut leftovers = Vec::new(); + let mut slots = self.slots.iter_mut(); + for item in &mut items { + if let Some(slot) = slots.find(|slot| slot.is_none()) { + slot.replace(item); + } else { + leftovers.push(item); + } + } + if leftovers.len() > 0 { + Err(Error::Full(leftovers)) + } else { + Ok(()) + } + } + + /// Add a series of items to an inventory without giving duplicates. (n * m complexity) + /// + /// 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. + pub fn push_all_unique>(&mut self, mut items: I) -> Result<(), Error> { + let mut leftovers = Vec::new(); + for item in &mut items { + if self.contains(&item).not() { + self.push(item).map(|overflow| leftovers.push(overflow)); + } // else drop item if it was already in + } + if leftovers.len() > 0 { + 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, Item> { @@ -49,6 +96,16 @@ impl Inventory { self.slots.iter().all(|slot| slot.is_some()) } + /// 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)) + } + /// Get content of a slot pub fn get(&self, cell: usize) -> Option<&Item> { self.slots.get(cell).and_then(Option::as_ref) @@ -89,3 +146,6 @@ pub struct InventoryUpdate; impl Component for InventoryUpdate { type Storage = NullStorage; } + +#[cfg(test)] +mod test; diff --git a/common/src/comp/inventory/test.rs b/common/src/comp/inventory/test.rs new file mode 100644 index 0000000000..51abc2db73 --- /dev/null +++ b/common/src/comp/inventory/test.rs @@ -0,0 +1,69 @@ +use super::*; +use lazy_static::lazy_static; +lazy_static! { + static ref TEST_ITEMS: Vec = vec![ + assets::load_expect_cloned("common.items.debug.boost"), + assets::load_expect_cloned("common.items.debug.possess") + ]; +} +/// The `Default` inventory should contain two items +#[test] +fn create_default_count() { + assert_eq!(Inventory::default().count(), 2) +} + +/// Attempting to push into a full inventory should return the same item. +#[test] +fn push_full() { + let mut inv = Inventory { + slots: TEST_ITEMS.iter().map(|a| Some(a.clone())).collect(), + }; + assert_eq!( + inv.push(TEST_ITEMS[0].clone()).unwrap(), + TEST_ITEMS[0].clone() + ) +} + +/// Attempting to push a series into a full inventory should return them all. +#[test] +fn push_all_full() { + let mut inv = Inventory { + slots: TEST_ITEMS.iter().map(|a| Some(a.clone())).collect(), + }; + let Error::Full(leftovers) = inv + .push_all(TEST_ITEMS.iter().map(|a| a.clone())) + .expect_err("Pushing into a full inventory somehow worked!"); + assert_eq!(leftovers, TEST_ITEMS.clone()) +} + +/// Attempting to push uniquely into an inventory containing all the items should work fine. +#[test] +fn push_unique_all_full() { + let mut inv = Inventory { + slots: TEST_ITEMS.iter().map(|a| Some(a.clone())).collect(), + }; + inv.push_all_unique(TEST_ITEMS.iter().map(|a| a.clone())) + .expect("Pushing unique items into an inventory that already contains them didn't work!"); +} + +/// Attempting to push uniquely into an inventory containing all the items should work fine. +#[test] +fn push_all_empty() { + let mut inv = Inventory { + slots: vec![None, None], + }; + inv.push_all(TEST_ITEMS.iter().map(|a| a.clone())) + .expect("Pushing items into an empty inventory didn't work!"); +} + +/// Attempting to push uniquely into an inventory containing all the items should work fine. +#[test] +fn push_all_unique_empty() { + let mut inv = Inventory { + slots: vec![None, None], + }; + inv.push_all_unique(TEST_ITEMS.iter().map(|a| a.clone())) + .expect( + "Pushing unique items into an empty inventory that didn't contain them didn't work!", + ); +} diff --git a/server/src/cmd.rs b/server/src/cmd.rs index d93b3c23c5..b978d658dd 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -238,6 +238,14 @@ lazy_static! { true, handle_remove_lights, ), + ChatCommand::new( + "debug", + "", + "/debug : Place all debug items into your pack.", + true, + handle_debug, + ), + ]; } fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &ChatCommand) { @@ -257,6 +265,7 @@ fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &C server.notify_client(entity, ServerMsg::private(String::from("Invalid item!"))); } } + fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { if let Ok((x, y, z)) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32) { match server.state.read_component_cloned::(entity) { @@ -988,6 +997,31 @@ fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &Cha } } +use common::comp::Item; +fn handle_debug(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { + if let Ok(items) = assets::load_glob::("common.items.debug.*") { + server + .state() + .ecs() + .write_storage::() + .get_mut(entity) + // TODO: Consider writing a `load_glob_cloned` in `assets` and using that here + .map(|inv| inv.push_all_unique(items.iter().map(|item| item.as_ref().clone()))); + let _ = server + .state + .ecs() + .write_storage::() + .insert(entity, comp::InventoryUpdate); + } else { + server.notify_client( + entity, + ServerMsg::private(String::from( + "Debug items not found? Something is very broken.", + )), + ); + } +} + fn handle_remove_lights( server: &mut Server, entity: EcsEntity,