use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
#[derive(new, Clone, Serialize, Deserialize, Debug, Builder)]
pub struct ItemDefinition<K, S, D: Default> {
pub key: K,
pub slot_type: S,
pub name: String,
pub friendly_name: String,
pub description: String,
pub maximum_stack: Option<usize>,
pub maximum_durability: Option<usize>,
#[new(default)]
#[builder(default)]
pub user_data: D,
}
#[derive(new, Clone, Serialize, Deserialize, Debug, Builder)]
pub struct ItemInstance<K, U: Default> {
pub key: K,
pub quantity: usize,
#[new(default)]
#[builder(default)]
pub durability: Option<usize>,
#[new(default)]
#[builder(default)]
pub user_data: U,
}
#[derive(Serialize, Deserialize, Clone, new)]
pub struct ItemDefinitions<K: Hash + Eq, S, D: Default> {
pub defs: HashMap<K, ItemDefinition<K, S, D>>,
}
impl<K: Hash + Eq, S, D: Default> Default for ItemDefinitions<K, S, D> {
fn default() -> Self {
Self {
defs: HashMap::default(),
}
}
}
impl<K: Hash + Eq + Clone, S, D: Default> From<Vec<ItemDefinition<K, S, D>>>
for ItemDefinitions<K, S, D>
{
fn from(t: Vec<ItemDefinition<K, S, D>>) -> Self {
let defs = t
.into_iter()
.map(|s| (s.key.clone(), s))
.collect::<HashMap<_, _>>();
Self::new(defs)
}
}
pub trait SlotType {
fn can_insert_into(&self, item_type: &Self) -> bool;
}
impl SlotType for () {
fn can_insert_into(&self, _: &Self) -> bool {
true
}
}
#[derive(new, Clone, Serialize, Deserialize, Debug)]
pub enum InventorySizingMode {
Fixed {
size: usize,
},
Dynamic {
min_size: usize,
max_size: usize,
},
}
#[derive(new, Clone, Serialize, Deserialize, Debug)]
pub enum MoveToFrontMode {
None,
TakeLast,
Offset,
}
#[derive(new, Clone, Serialize, Deserialize, Debug, Builder)]
pub struct Inventory<K, S: SlotType, U: Default> {
pub content: Vec<Option<ItemInstance<K, U>>>,
#[builder(default)]
pub slot_restriction: Vec<Option<S>>,
pub move_to_front: MoveToFrontMode,
pub sizing_mode: InventorySizingMode,
}
impl<K: PartialEq + Clone + Debug, S: SlotType, U: Default + Clone + Debug> Inventory<K, S, U> {
pub fn new_fixed(count: usize) -> Inventory<K, S, U> {
let mut content = Vec::with_capacity(count);
(0..count).for_each(|_| content.push(None));
let mut slot_restriction = Vec::with_capacity(count);
(0..count).for_each(|_| slot_restriction.push(None));
Inventory {
content,
slot_restriction,
move_to_front: MoveToFrontMode::None,
sizing_mode: InventorySizingMode::new_fixed(count),
}
}
pub fn new_dynamic(minimum: usize, maximum: usize) -> Inventory<K, S, U> {
let mut content = Vec::with_capacity(minimum);
(0..minimum).for_each(|_| content.push(None));
Inventory {
content,
slot_restriction: vec![],
move_to_front: MoveToFrontMode::None,
sizing_mode: InventorySizingMode::new_dynamic(minimum, maximum),
}
}
pub fn use_item(&mut self, idx: usize) -> Result<Option<usize>, ItemError<K, U>> {
if let Some(Some(ii)) = self.content.get_mut(idx) {
if ii.durability.is_some() {
if ii.durability.unwrap() == 0 {
Err(ItemError::ItemDestroyed(self.delete_stack(idx)?))
} else {
*ii.durability.as_mut().unwrap() -= 1;
Ok(Some(ii.durability.unwrap()))
}
} else {
Ok(None)
}
} else {
Err(ItemError::SlotEmpty)
}
}
pub fn consume(&mut self, idx: usize) -> Result<usize, ItemError<K, U>> {
if let Some(Some(ii)) = self.content.get_mut(idx) {
ii.quantity -= 1;
if ii.quantity == 0 {
Err(ItemError::StackConsumed(self.delete_stack(idx)?))
} else {
Ok(ii.quantity)
}
} else {
Err(ItemError::SlotEmpty)
}
}
pub fn has_space(&self) -> bool {
match self.sizing_mode {
InventorySizingMode::Fixed { size: _ } => self.content.iter().any(|o| o.is_none()),
InventorySizingMode::Dynamic {
min_size: _,
max_size,
} => self.content.len() != max_size,
}
}
pub fn transfer(
&mut self,
from_idx: usize,
target: &mut Inventory<K, S, U>,
to_idx: usize,
quantity: usize,
_with_overflow: bool,
) -> Result<(), ItemError<K, U>> {
let mv = self.delete(from_idx, quantity)?;
target.insert_into(to_idx, mv)?;
Ok(())
}
pub fn transfer_stack(
&mut self,
from_idx: usize,
target: &mut Inventory<K, S, U>,
to_idx: usize,
with_overflow: bool,
) -> Result<(), ItemError<K, U>> {
if let Some(Some(qty)) = self
.content
.get(from_idx)
.map(|i| i.as_ref().map(|i2| i2.quantity))
{
self.transfer(from_idx, target, to_idx, qty, with_overflow)
} else {
Err(ItemError::SlotEmpty)
}
}
pub fn move_item(
&mut self,
from_idx: usize,
to_idx: usize,
quantity: usize,
_with_overflow: bool,
) -> Result<(), ItemError<K, U>> {
let mv = self.delete(from_idx, quantity)?;
self.insert_into(to_idx, mv)?;
Ok(())
}
pub fn move_stack(
&mut self,
from_idx: usize,
to_idx: usize,
with_overflow: bool,
) -> Result<(), ItemError<K, U>> {
if let Some(Some(qty)) = self
.content
.get(from_idx)
.map(|i| i.as_ref().map(|i2| i2.quantity))
{
self.move_item(from_idx, to_idx, qty, with_overflow)
} else {
Err(ItemError::SlotEmpty)
}
}
pub fn delete(
&mut self,
idx: usize,
quantity: usize,
) -> Result<ItemInstance<K, U>, ItemError<K, U>> {
if let Some(Some(ii)) = self.content.get_mut(idx) {
if ii.quantity >= quantity {
ii.quantity -= quantity;
let mut ret = ItemInstance::new(ii.key.clone(), quantity);
ret.durability = ii.durability.clone();
if ii.quantity == 0 {
self.remove_slot(idx);
}
Ok(ret)
} else {
Err(ItemError::NotEnoughQuantity)
}
} else {
Err(ItemError::SlotEmpty)
}
}
fn remove_slot(&mut self, idx: usize) -> Option<ItemInstance<K, U>> {
match self.move_to_front {
MoveToFrontMode::None => {
if let Some(s) = self.content.get_mut(idx) {
let ret = s.clone();
*s = None;
ret
} else {
None
}
}
MoveToFrontMode::TakeLast => {
let ret = self.content.swap_remove(idx);
self.content.push(None);
ret
}
MoveToFrontMode::Offset => {
let ret = self.content.remove(idx);
self.content.push(None);
ret
}
}
}
pub fn delete_stack(&mut self, idx: usize) -> Result<ItemInstance<K, U>, ItemError<K, U>> {
if let Some(Some(qty)) = self
.content
.get(idx)
.map(|i| i.as_ref().map(|i2| i2.quantity))
{
self.delete(idx, qty)
} else {
Err(ItemError::SlotEmpty)
}
}
pub fn delete_key(
&mut self,
key: &K,
quantity: usize,
) -> Result<ItemInstance<K, U>, ItemError<K, U>> {
if !self.has_quantity(key, quantity) {
return Err(ItemError::NotEnoughQuantity);
}
let mut remaining = quantity;
for idx in self
.content
.iter()
.enumerate()
.filter(|(_, ii)| ii.is_some() && ii.as_ref().unwrap().key == *key)
.map(|(idx, _)| idx)
.collect::<Vec<_>>()
{
let avail = self
.content
.get(idx)
.as_ref()
.unwrap()
.as_ref()
.unwrap()
.quantity;
let rm = if avail >= remaining { remaining } else { avail };
remaining -= rm;
self.delete(idx, rm)
.expect("Failed to delete from item stack during delete_key call. This is a bug.");
if remaining == 0 {
return Ok(ItemInstance::new(key.clone(), quantity));
}
}
unreachable!();
}
pub fn has_quantity(&self, key: &K, quantity: usize) -> bool {
let sum: usize = self
.content
.iter()
.flatten()
.filter(|ii| ii.key == *key)
.map(|ii| ii.quantity)
.sum();
sum >= quantity
}
pub fn has(&self, key: &K) -> bool {
self.content
.iter()
.any(|ii| ii.is_some() && ii.as_ref().unwrap().key == *key)
}
pub fn get(&self, idx: usize) -> &Option<ItemInstance<K, U>> {
self.content.get(idx).unwrap_or(&None)
}
pub fn get_mut(&mut self, idx: usize) -> Option<&mut ItemInstance<K, U>> {
self.content
.get_mut(idx)
.map(|opt| opt.as_mut())
.unwrap_or(None)
}
pub fn get_key(&self, key: &K) -> impl Iterator<Item = &ItemInstance<K, U>> {
let key = key.clone();
self.content
.iter()
.flatten()
.filter(move |ii| ii.key == key)
}
pub fn get_key_mut(&mut self, key: &K) -> impl Iterator<Item = &mut ItemInstance<K, U>> {
let key = key.clone();
self.content
.iter_mut()
.flatten()
.filter(move |ii| ii.key == key)
}
pub fn insert_into(
&mut self,
idx: usize,
item: ItemInstance<K, U>,
) -> Result<(), ItemError<K, U>> {
let opt = self.content.get_mut(idx);
match opt {
Some(Some(_)) => Err(ItemError::SlotOccupied),
Some(None) => {
*opt.unwrap() = Some(item);
Ok(())
}
None => panic!("Out of bound inventory insertion at index {}", idx),
}
}
pub fn insert(&mut self, item: ItemInstance<K, U>) -> Result<(), ItemError<K, U>> {
if let Some(slot) = self.first_empty_slot() {
self.insert_into(slot, item).unwrap();
Ok(())
} else {
match self.sizing_mode {
InventorySizingMode::Fixed { size: _ } => Err(ItemError::InventoryFull),
InventorySizingMode::Dynamic {
min_size: _,
max_size: _,
} => {
if self.has_space() {
self.content.push(None);
self.insert_into(self.content.len() - 1, item).unwrap();
Ok(())
} else {
Err(ItemError::InventoryFull)
}
}
}
}
}
pub fn first_empty_slot(&self) -> Option<usize> {
match self.move_to_front {
MoveToFrontMode::None => {
let ret = self
.content
.iter()
.enumerate()
.find(|t| t.1.is_none())
.map(|t| t.0);
ret
}
MoveToFrontMode::TakeLast | MoveToFrontMode::Offset => {
let max = match self.sizing_mode {
InventorySizingMode::Fixed { size } => size,
InventorySizingMode::Dynamic {
min_size: _,
max_size,
} => max_size,
};
if self.content.len() != max {
Some(self.content.len())
} else {
None
}
}
}
}
}
#[derive(Debug)]
pub enum ItemError<K: PartialEq + Debug, U: Default> {
StackOverflow(ItemInstance<K, U>),
InventoryFull,
InventoryOverflow(Vec<ItemInstance<K, U>>),
ItemDestroyed(ItemInstance<K, U>),
StackConsumed(ItemInstance<K, U>),
SlotOccupied,
SlotRestricted,
LockedOriginSlot,
LockedRemoteSlot,
SlotEmpty,
NotEnoughQuantity,
}