From 0fa3f4ea196cd2e33fb648fb9872fb167da0dc5c Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 19 Jul 2022 19:39:58 -0600 Subject: [PATCH] move inventory code out of state --- src/ship/character.rs | 3 +- src/ship/items/actions.rs | 5 +- src/ship/items/apply_item.rs | 3 +- src/ship/items/inventory.rs | 529 +++++++++++++++++++++++++++++ src/ship/items/mod.rs | 1 + src/ship/items/state.rs | 520 +--------------------------- src/ship/packet/builder/message.rs | 5 +- src/ship/packet/handler/trade.rs | 3 +- tests/test_trade.rs | 3 +- 9 files changed, 550 insertions(+), 522 deletions(-) create mode 100644 src/ship/items/inventory.rs diff --git a/src/ship/character.rs b/src/ship/character.rs index d2144cd..674e17d 100644 --- a/src/ship/character.rs +++ b/src/ship/character.rs @@ -2,7 +2,8 @@ use libpso::character::character; use crate::common::leveltable::CharacterStats; use crate::entity::character::CharacterEntity; //use crate::ship::items::{CharacterInventory, CharacterBank}; -use crate::ship::items::state::{InventoryState, BankState}; +use crate::ship::items::state::{BankState}; +use crate::ship::items::inventory::InventoryState; use crate::entity::item::Meseta; diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index 02e774d..321db6a 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -8,9 +8,10 @@ use std::pin::Pin; use crate::ship::map::MapArea; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction}; -use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateError, FloorItem, InventoryItem, AddItemResult, FloorItemDetail, - StackedItemDetail, BankItem, BankItemDetail, InventoryItemDetail, IndividualItemDetail}; +use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateError, FloorItem, AddItemResult, FloorItemDetail, + StackedItemDetail, BankItem, BankItemDetail, IndividualItemDetail}; use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction}; +use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; use crate::ship::items::apply_item::apply_item; use crate::entity::item::{ItemDetail, NewItemEntity, TradeId}; use crate::entity::item::tool::Tool; diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs index c88066e..d43f55d 100644 --- a/src/ship/items/apply_item.rs +++ b/src/ship/items/apply_item.rs @@ -5,7 +5,8 @@ use crate::entity::character::CharacterEntity; use crate::entity::item::mag::{MagCell, MagCellError}; use crate::entity::item::tool::ToolType; use crate::entity::item::{ItemDetail, ItemEntityId}; -use crate::ship::items::state::{ItemStateProxy, InventoryItem, InventoryItemDetail, ItemStateError}; +use crate::ship::items::state::{ItemStateProxy, ItemStateError}; +use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; #[derive(Error, Debug)] diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs new file mode 100644 index 0000000..602b515 --- /dev/null +++ b/src/ship/items/inventory.rs @@ -0,0 +1,529 @@ +use std::cmp::Ordering; +use libpso::character::character; +use crate::ship::items::ClientItemId; +use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity}; +use std::future::Future; + +use crate::entity::character::CharacterEntityId; +use crate::entity::item::tool::ToolType; +use crate::entity::item::mag::Mag; +use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; +use crate::ship::items::state::ItemStateError; +use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult}; +use crate::ship::items::state::{FloorItem, FloorItemDetail}; + +#[derive(Clone, Debug)] +pub enum InventoryItemDetail { + Individual(IndividualItemDetail), + Stacked(StackedItemDetail), +} + +impl InventoryItemDetail { + // TODO: rename as_stacked for consistency + pub fn stacked(&self) -> Option<&StackedItemDetail> { + match self { + InventoryItemDetail::Stacked(sitem) => Some(sitem), + _ => None, + } + } + // TODO: rename as_stacked_mut for consistency + pub fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { + match self { + InventoryItemDetail::Stacked(sitem) => Some(sitem), + _ => None, + } + } + + pub fn as_individual(&self) -> Option<&IndividualItemDetail> { + match self { + InventoryItemDetail::Individual(iitem) => Some(iitem), + _ => None, + } + } + + pub fn as_individual_mut(&mut self) -> Option<&mut IndividualItemDetail> { + match self { + InventoryItemDetail::Individual(iitem) => Some(iitem), + _ => None, + } + } + + pub fn as_client_bytes(&self) -> [u8; 16] { + match self { + InventoryItemDetail::Individual(item) => { + item.as_client_bytes() + }, + InventoryItemDetail::Stacked(item) => { + item.tool.as_stacked_bytes(item.entity_ids.len()) + }, + } + } + + // TODO: this should probably go somewhere a bit more fundamental like ItemDetail + pub fn sell_price(&self) -> Result { + match self { + InventoryItemDetail::Individual(individual_item) => { + match &individual_item.item { + // TODO: can wrapped items be sold? + ItemDetail::Weapon(w) => { + if !w.tekked { + return Ok(1u32) + } + if w.is_rare_item() { + return Ok(10u32) + } + Ok((WeaponShopItem::from(w).price() / 8) as u32) + }, + ItemDetail::Armor(a) => { + if a.is_rare_item() { + return Ok(10u32) + } + Ok((ArmorShopItem::from(a).price() / 8) as u32) + }, + ItemDetail::Shield(s) => { + if s.is_rare_item() { + return Ok(10u32) + } + Ok((ArmorShopItem::from(s).price() / 8) as u32) + }, + ItemDetail::Unit(u) => { + if u.is_rare_item() { + return Ok(10u32) + } + Ok((ArmorShopItem::from(u).price() / 8) as u32) + }, + ItemDetail::Tool(t) => { + if !matches!(t.tool, ToolType::PhotonDrop | ToolType::PhotonSphere | ToolType::PhotonCrystal) && t.is_rare_item() { + return Ok(10u32) + } + Ok((ToolShopItem::from(t).price() / 8) as u32) + }, + ItemDetail::TechniqueDisk(d) => { + Ok((ToolShopItem::from(d).price() / 8) as u32) + }, + ItemDetail::Mag(_m) => { + Err(ItemStateError::ItemNotSellable) + }, + ItemDetail::ESWeapon(_e) => { + Ok(10u32) + }, + } + }, + // the number of stacked items sold is handled by the caller. this is just the price of 1 + InventoryItemDetail::Stacked(stacked_item) => { + Ok(((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32) * stacked_item.count() as u32) + }, + } + } + +} + + +#[derive(Clone, Debug)] +pub struct InventoryItem { + pub item_id: ClientItemId, + pub item: InventoryItemDetail, +} + +impl InventoryItem { + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId) -> Fut, + Fut: Future>, + { + match &self.item { + InventoryItemDetail::Individual(individual_item) => { + param = func(param, individual_item.entity_id).await?; + }, + InventoryItemDetail::Stacked(stacked_item) => { + for entity_id in &stacked_item.entity_ids { + param = func(param, *entity_id).await?; + } + } + } + + Ok(param) + } + + pub async fn with_mag(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId, Mag) -> Fut, + Fut: Future>, + { + if let InventoryItemDetail::Individual(individual_item) = &self.item { + if let ItemDetail::Mag(mag) = &individual_item.item { + param = func(param, individual_item.entity_id, mag.clone()).await?; + } + } + Ok(param) + } +} + +#[derive(Clone, Debug)] +pub struct Inventory(Vec); + +impl Inventory { + pub fn new(items: Vec) -> Inventory { + Inventory(items) + } +} + + +#[derive(thiserror::Error, Debug)] +pub enum InventoryError { + #[error("inventory full")] + InventoryFull, + #[error("stack full")] + StackFull, + #[error("meseta full")] + MesetaFull, +} + +#[derive(Clone)] +pub struct InventoryState { + pub character_id: CharacterEntityId, + pub item_id_counter: u32, + pub inventory: Inventory, + pub equipped: EquippedEntity, + pub meseta: Meseta, +} + +impl InventoryState { + pub fn initialize_item_ids(&mut self, base_item_id: u32) { + for (i, item) in self.inventory.0.iter_mut().enumerate() { + item.item_id = ClientItemId(base_item_id + i as u32); + } + self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1; + } + + pub fn new_item_id(&mut self) -> ClientItemId { + self.item_id_counter += 1; + ClientItemId(self.item_id_counter) + } + + pub fn count(&self) -> usize { + self.inventory.0.len() + } + + pub fn add_floor_item(&mut self, item: FloorItem) -> Result { + match item.item { + FloorItemDetail::Individual(iitem) => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(InventoryItem { + item_id: item.item_id, + item: InventoryItemDetail::Individual(iitem) + }); + Ok(AddItemResult::NewItem) + } + }, + FloorItemDetail::Stacked(sitem) => { + let existing_stack = self.inventory.0 + .iter_mut() + .filter_map(|item| item.item.stacked_mut()) + .find(|item| { + item.tool == sitem.tool + }); + match existing_stack { + Some(existing_stack) => { + if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { + Err(InventoryError::StackFull) + } + else { + existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); + Ok(AddItemResult::AddToStack) + } + }, + None => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(InventoryItem { + item_id: item.item_id, + item: InventoryItemDetail::Stacked(sitem) + }); + Ok(AddItemResult::NewItem) + } + } + } + + }, + FloorItemDetail::Meseta(meseta) => { + if self.meseta == Meseta(999999) { + Err(InventoryError::MesetaFull) + } + else { + self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999); + Ok(AddItemResult::Meseta) + } + }, + } + } + + pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> { + match &item.item { + InventoryItemDetail::Individual(_) => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(item); + Ok(( + AddItemResult::NewItem, + self.inventory.0 + .last() + .unwrap() + .clone() + )) + } + }, + InventoryItemDetail::Stacked(sitem) => { + let existing_stack = self.inventory.0 + .iter_mut() + .filter_map(|item| item.item.stacked_mut()) + .find(|item| { + item.tool == sitem.tool + }); + match existing_stack { + Some(existing_stack) => { + if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { + Err(InventoryError::StackFull) + } + else { + existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); + Ok(( + AddItemResult::AddToStack, + self.inventory.0[self.inventory.0 + .iter() + .filter_map(|item| item.item.stacked()) + .position(|item| item.tool == sitem.tool) + .unwrap()] + .clone() + )) + } + }, + None => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(item); + Ok(( + AddItemResult::NewItem, + self.inventory.0 + .last() + .unwrap() + .clone() + )) + } + } + } + } + } + } + + pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + let idx = self.inventory.0 + .iter() + .position(|i| i.item_id == *item_id)?; + match &mut self.inventory.0[idx].item { + InventoryItemDetail::Individual(_individual_item) => { + Some(self.inventory.0.remove(idx)) + }, + InventoryItemDetail::Stacked(stacked_item) => { + let remove_all = (amount == 0) || match stacked_item.entity_ids.len().cmp(&(amount as usize)) { + Ordering::Equal => true, + Ordering::Greater => false, + Ordering::Less => return None, + }; + + if remove_all { + Some(self.inventory.0.remove(idx)) + } + else { + let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); + self.item_id_counter += 1; + Some(InventoryItem { + item_id: ClientItemId(self.item_id_counter), + item: InventoryItemDetail::Stacked(StackedItemDetail { + entity_ids, + tool: stacked_item.tool, + })}) + } + } + } + } + + // TODO: rename get_item_by_client_id + pub fn get_by_client_id(&self, item_id: &ClientItemId) -> Option<&InventoryItem> { + self.inventory.0 + .iter() + .find(|i| i.item_id == *item_id) + } + + pub fn get_by_client_id_mut(&mut self, item_id: &ClientItemId) -> Option<&mut InventoryItem> { + self.inventory.0 + .iter_mut() + .find(|i| i.item_id == *item_id) + } + + pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if self.meseta.0 == 999999 { + return Err(ItemStateError::FullOfMeseta) + } + self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999); + Ok(()) + } + + pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> { + if self.meseta.0 + amount > 999999 { + return Err(ItemStateError::FullOfMeseta) + } + self.meseta.0 += amount; + Ok(()) + } + + pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if amount > self.meseta.0 { + return Err(ItemStateError::InvalidMesetaRemoval(amount)) + } + self.meseta.0 -= amount; + Ok(()) + } + + pub fn equip(&mut self, item_id: &ClientItemId, equip_slot: u8) { + for item in &self.inventory.0 { + if let InventoryItemDetail::Individual(inventory_item) = &item.item { + if item.item_id == *item_id { + match inventory_item.item { + ItemDetail::Weapon(_) => self.equipped.weapon = Some(inventory_item.entity_id), + ItemDetail::Armor(_) => self.equipped.armor = Some(inventory_item.entity_id), + ItemDetail::Shield(_) => self.equipped.shield = Some(inventory_item.entity_id), + ItemDetail::Unit(_) => { + if let Some(unit) = self.equipped.unit.get_mut(equip_slot as usize) { + *unit = Some(inventory_item.entity_id) + } + } + ItemDetail::Mag(_) => self.equipped.mag = Some(inventory_item.entity_id), + _ => {} + } + } + } + } + } + + pub fn unequip(&mut self, item_id: &ClientItemId) { + for item in &self.inventory.0 { + if let InventoryItemDetail::Individual(inventory_item) = &item.item { + if item.item_id == *item_id { + match inventory_item.item { + ItemDetail::Weapon(_) => self.equipped.weapon = None, + ItemDetail::Armor(_) => { + self.equipped.armor = None; + self.equipped.unit = [None; 4]; + } + ItemDetail::Shield(_) => self.equipped.shield = None, + ItemDetail::Unit(_) => { + for unit in self.equipped.unit.iter_mut() { + if *unit == Some(inventory_item.entity_id) { + *unit = None + } + } + } + ItemDetail::Mag(_) => self.equipped.mag = Some(inventory_item.entity_id), + _ => {} + } + } + } + } + } + + pub fn equipped_mag_mut(&mut self) -> Option<(ItemEntityId, &mut Mag)> { + let mag_id = self.equipped.mag?; + self.inventory.0 + .iter_mut() + .filter_map(|i| { + let individual = i.item.as_individual_mut()?; + let entity_id = individual.entity_id; + Some((entity_id, individual.as_mag_mut()?)) + }) + .find(|(entity_id, _)| *entity_id == mag_id) + } + + pub fn sort(&mut self, item_ids: &[ClientItemId]) { + self.inventory.0.sort_by(|a, b| { + let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id); + let b_index = item_ids.iter().position(|item_id| *item_id == b.item_id); + + match (a_index, b_index) { + (Some(a_index), Some(b_index)) => { + a_index.cmp(&b_index) + }, + _ => Ordering::Equal + } + }); + } + + pub fn as_inventory_entity(&self, _character_id: &CharacterEntityId) -> InventoryEntity { + InventoryEntity { + items: self.inventory.0.iter() + .map(|item| { + match &item.item { + InventoryItemDetail::Individual(item) => { + InventoryItemEntity::Individual(ItemEntity { + id: item.entity_id, + item: item.item.clone(), + }) + }, + InventoryItemDetail::Stacked(items) => { + InventoryItemEntity::Stacked(items.entity_ids.iter() + .map(|id| { + ItemEntity { + id: *id, + item: ItemDetail::Tool(items.tool) + } + }) + .collect()) + }, + } + }) + .collect() + } + } + + pub fn as_equipped_entity(&self) -> EquippedEntity { + self.equipped.clone() + } + + + pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] { + self.inventory.0.iter() + .enumerate() + .fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| { + let bytes = item.item.as_client_bytes(); + inventory[slot].data1.copy_from_slice(&bytes[0..12]); + inventory[slot].data2.copy_from_slice(&bytes[12..16]); + inventory[slot].item_id = item.item_id.0; + inventory[slot].equipped = 0; + inventory[slot].flags = 0; + + if let InventoryItemDetail::Individual(individual_item) = &item.item { + if self.equipped.is_equipped(&individual_item.entity_id) { + if let ItemDetail::Unit(_) = individual_item.item { + inventory[slot].data1[4] = self.equipped.unit.iter() + .enumerate() + .find(|(_, u_id)| **u_id == Some(individual_item.entity_id)) + .map(|(a, _)| a) + .unwrap_or(0) as u8 + } + inventory[slot].equipped = 1; + inventory[slot].flags |= 8; + } + } + inventory + }) + } +} diff --git a/src/ship/items/mod.rs b/src/ship/items/mod.rs index 16d1134..3e161f4 100644 --- a/src/ship/items/mod.rs +++ b/src/ship/items/mod.rs @@ -2,6 +2,7 @@ pub mod state; pub mod actions; pub mod apply_item; pub mod itemstateaction; +pub mod inventory; #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)] pub struct ClientItemId(pub u32); diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index 2d2afc4..724282e 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -2,18 +2,19 @@ use std::cmp::Ordering; use std::collections::HashMap; use libpso::character::character; use crate::ship::items::ClientItemId; -use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, BankEntity, BankItemEntity, BankName, EquippedEntity}; +use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankEntity, BankItemEntity, BankName}; use std::future::Future; use crate::ship::map::MapArea; use crate::ship::location::{AreaClient, RoomId}; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::gateway::{EntityGateway, GatewayError}; -use crate::entity::item::tool::{Tool, ToolType}; +use crate::entity::item::tool::Tool; use crate::entity::item::weapon::Weapon; use crate::entity::item::mag::Mag; use crate::ship::drops::ItemDrop; -use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; + +use crate::ship::items::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState}; // TODO: Commit trait that ItemStateProxy and EntityTransaction implement that .commit requires and acts on upon everything succeeding (like 3 less lines of code!) @@ -84,8 +85,6 @@ pub enum FloorType { } - - #[derive(Clone, Debug)] pub struct IndividualItemDetail { pub entity_id: ItemEntityId, @@ -141,154 +140,6 @@ impl StackedItemDetail { } } -#[derive(Clone, Debug)] -pub enum InventoryItemDetail { - Individual(IndividualItemDetail), - Stacked(StackedItemDetail), -} - -impl InventoryItemDetail { - // TODO: rename as_stacked for consistency - pub fn stacked(&self) -> Option<&StackedItemDetail> { - match self { - InventoryItemDetail::Stacked(sitem) => Some(sitem), - _ => None, - } - } - // TODO: rename as_stacked_mut for consistency - pub fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { - match self { - InventoryItemDetail::Stacked(sitem) => Some(sitem), - _ => None, - } - } - - pub fn as_individual(&self) -> Option<&IndividualItemDetail> { - match self { - InventoryItemDetail::Individual(iitem) => Some(iitem), - _ => None, - } - } - - pub fn as_individual_mut(&mut self) -> Option<&mut IndividualItemDetail> { - match self { - InventoryItemDetail::Individual(iitem) => Some(iitem), - _ => None, - } - } - - pub fn as_client_bytes(&self) -> [u8; 16] { - match self { - InventoryItemDetail::Individual(item) => { - item.as_client_bytes() - }, - InventoryItemDetail::Stacked(item) => { - item.tool.as_stacked_bytes(item.entity_ids.len()) - }, - } - } - - // TODO: this should probably go somewhere a bit more fundamental like ItemDetail - pub fn sell_price(&self) -> Result { - match self { - InventoryItemDetail::Individual(individual_item) => { - match &individual_item.item { - // TODO: can wrapped items be sold? - ItemDetail::Weapon(w) => { - if !w.tekked { - return Ok(1u32) - } - if w.is_rare_item() { - return Ok(10u32) - } - Ok((WeaponShopItem::from(w).price() / 8) as u32) - }, - ItemDetail::Armor(a) => { - if a.is_rare_item() { - return Ok(10u32) - } - Ok((ArmorShopItem::from(a).price() / 8) as u32) - }, - ItemDetail::Shield(s) => { - if s.is_rare_item() { - return Ok(10u32) - } - Ok((ArmorShopItem::from(s).price() / 8) as u32) - }, - ItemDetail::Unit(u) => { - if u.is_rare_item() { - return Ok(10u32) - } - Ok((ArmorShopItem::from(u).price() / 8) as u32) - }, - ItemDetail::Tool(t) => { - if !matches!(t.tool, ToolType::PhotonDrop | ToolType::PhotonSphere | ToolType::PhotonCrystal) && t.is_rare_item() { - return Ok(10u32) - } - Ok((ToolShopItem::from(t).price() / 8) as u32) - }, - ItemDetail::TechniqueDisk(d) => { - Ok((ToolShopItem::from(d).price() / 8) as u32) - }, - ItemDetail::Mag(_m) => { - Err(ItemStateError::ItemNotSellable) - }, - ItemDetail::ESWeapon(_e) => { - Ok(10u32) - }, - } - }, - // the number of stacked items sold is handled by the caller. this is just the price of 1 - InventoryItemDetail::Stacked(stacked_item) => { - Ok(((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32) * stacked_item.count() as u32) - }, - } - } - -} - - -#[derive(Clone, Debug)] -pub struct InventoryItem { - pub item_id: ClientItemId, - pub item: InventoryItemDetail, -} - -impl InventoryItem { - pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result - where - F: FnMut(T, ItemEntityId) -> Fut, - Fut: Future>, - { - match &self.item { - InventoryItemDetail::Individual(individual_item) => { - param = func(param, individual_item.entity_id).await?; - }, - InventoryItemDetail::Stacked(stacked_item) => { - for entity_id in &stacked_item.entity_ids { - param = func(param, *entity_id).await?; - } - } - } - - Ok(param) - } - - pub async fn with_mag(&self, mut param: T, mut func: F) -> Result - where - F: FnMut(T, ItemEntityId, Mag) -> Fut, - Fut: Future>, - { - if let InventoryItemDetail::Individual(individual_item) = &self.item { - if let ItemDetail::Mag(mag) = &individual_item.item { - param = func(param, individual_item.entity_id, mag.clone()).await?; - } - } - Ok(param) - } -} - - #[derive(Clone, Debug)] pub enum BankItemDetail { Individual(IndividualItemDetail), @@ -417,20 +268,6 @@ impl FloorItem { } -#[derive(Clone, Debug)] -pub struct Inventory(Vec); - - -#[derive(thiserror::Error, Debug)] -pub enum InventoryError { - #[error("inventory full")] - InventoryFull, - #[error("stack full")] - StackFull, - #[error("meseta full")] - MesetaFull, -} - #[derive(thiserror::Error, Debug)] pub enum BankError { #[error("bank full")] @@ -453,353 +290,6 @@ pub struct LocalFloor(Vec); #[derive(Debug, Clone, Default)] pub struct SharedFloor(Vec); -#[derive(Clone)] -pub struct InventoryState { - character_id: CharacterEntityId, - item_id_counter: u32, - pub inventory: Inventory, - equipped: EquippedEntity, - pub meseta: Meseta, -} - -impl InventoryState { - pub fn initialize_item_ids(&mut self, base_item_id: u32) { - for (i, item) in self.inventory.0.iter_mut().enumerate() { - item.item_id = ClientItemId(base_item_id + i as u32); - } - self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1; - } - - pub fn new_item_id(&mut self) -> ClientItemId { - self.item_id_counter += 1; - ClientItemId(self.item_id_counter) - } - - pub fn count(&self) -> usize { - self.inventory.0.len() - } - - pub fn add_floor_item(&mut self, item: FloorItem) -> Result { - match item.item { - FloorItemDetail::Individual(iitem) => { - if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) - } - else { - self.inventory.0.push(InventoryItem { - item_id: item.item_id, - item: InventoryItemDetail::Individual(iitem) - }); - Ok(AddItemResult::NewItem) - } - }, - FloorItemDetail::Stacked(sitem) => { - let existing_stack = self.inventory.0 - .iter_mut() - .filter_map(|item| item.item.stacked_mut()) - .find(|item| { - item.tool == sitem.tool - }); - match existing_stack { - Some(existing_stack) => { - if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { - Err(InventoryError::StackFull) - } - else { - existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); - Ok(AddItemResult::AddToStack) - } - }, - None => { - if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) - } - else { - self.inventory.0.push(InventoryItem { - item_id: item.item_id, - item: InventoryItemDetail::Stacked(sitem) - }); - Ok(AddItemResult::NewItem) - } - } - } - - }, - FloorItemDetail::Meseta(meseta) => { - if self.meseta == Meseta(999999) { - Err(InventoryError::MesetaFull) - } - else { - self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999); - Ok(AddItemResult::Meseta) - } - }, - } - } - - pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> { - match &item.item { - InventoryItemDetail::Individual(_) => { - if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) - } - else { - self.inventory.0.push(item); - Ok(( - AddItemResult::NewItem, - self.inventory.0 - .last() - .unwrap() - .clone() - )) - } - }, - InventoryItemDetail::Stacked(sitem) => { - let existing_stack = self.inventory.0 - .iter_mut() - .filter_map(|item| item.item.stacked_mut()) - .find(|item| { - item.tool == sitem.tool - }); - match existing_stack { - Some(existing_stack) => { - if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { - Err(InventoryError::StackFull) - } - else { - existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); - Ok(( - AddItemResult::AddToStack, - self.inventory.0[self.inventory.0 - .iter() - .filter_map(|item| item.item.stacked()) - .position(|item| item.tool == sitem.tool) - .unwrap()] - .clone() - )) - } - }, - None => { - if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) - } - else { - self.inventory.0.push(item); - Ok(( - AddItemResult::NewItem, - self.inventory.0 - .last() - .unwrap() - .clone() - )) - } - } - } - } - } - } - - pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { - let idx = self.inventory.0 - .iter() - .position(|i| i.item_id == *item_id)?; - match &mut self.inventory.0[idx].item { - InventoryItemDetail::Individual(_individual_item) => { - Some(self.inventory.0.remove(idx)) - }, - InventoryItemDetail::Stacked(stacked_item) => { - let remove_all = (amount == 0) || match stacked_item.entity_ids.len().cmp(&(amount as usize)) { - Ordering::Equal => true, - Ordering::Greater => false, - Ordering::Less => return None, - }; - - if remove_all { - Some(self.inventory.0.remove(idx)) - } - else { - let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); - self.item_id_counter += 1; - Some(InventoryItem { - item_id: ClientItemId(self.item_id_counter), - item: InventoryItemDetail::Stacked(StackedItemDetail { - entity_ids, - tool: stacked_item.tool, - })}) - } - } - } - } - - pub fn get_by_client_id(&self, item_id: &ClientItemId) -> Option<&InventoryItem> { - self.inventory.0 - .iter() - .find(|i| i.item_id == *item_id) - } - - pub fn get_by_client_id_mut(&mut self, item_id: &ClientItemId) -> Option<&mut InventoryItem> { - self.inventory.0 - .iter_mut() - .find(|i| i.item_id == *item_id) - } - - pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { - if self.meseta.0 == 999999 { - return Err(ItemStateError::FullOfMeseta) - } - self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999); - Ok(()) - } - - pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> { - if self.meseta.0 + amount > 999999 { - return Err(ItemStateError::FullOfMeseta) - } - self.meseta.0 += amount; - Ok(()) - } - - pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { - if amount > self.meseta.0 { - return Err(ItemStateError::InvalidMesetaRemoval(amount)) - } - self.meseta.0 -= amount; - Ok(()) - } - - pub fn equip(&mut self, item_id: &ClientItemId, equip_slot: u8) { - for item in &self.inventory.0 { - if let InventoryItemDetail::Individual(inventory_item) = &item.item { - if item.item_id == *item_id { - match inventory_item.item { - ItemDetail::Weapon(_) => self.equipped.weapon = Some(inventory_item.entity_id), - ItemDetail::Armor(_) => self.equipped.armor = Some(inventory_item.entity_id), - ItemDetail::Shield(_) => self.equipped.shield = Some(inventory_item.entity_id), - ItemDetail::Unit(_) => { - if let Some(unit) = self.equipped.unit.get_mut(equip_slot as usize) { - *unit = Some(inventory_item.entity_id) - } - } - ItemDetail::Mag(_) => self.equipped.mag = Some(inventory_item.entity_id), - _ => {} - } - } - } - } - } - - pub fn unequip(&mut self, item_id: &ClientItemId) { - for item in &self.inventory.0 { - if let InventoryItemDetail::Individual(inventory_item) = &item.item { - if item.item_id == *item_id { - match inventory_item.item { - ItemDetail::Weapon(_) => self.equipped.weapon = None, - ItemDetail::Armor(_) => { - self.equipped.armor = None; - self.equipped.unit = [None; 4]; - } - ItemDetail::Shield(_) => self.equipped.shield = None, - ItemDetail::Unit(_) => { - for unit in self.equipped.unit.iter_mut() { - if *unit == Some(inventory_item.entity_id) { - *unit = None - } - } - } - ItemDetail::Mag(_) => self.equipped.mag = Some(inventory_item.entity_id), - _ => {} - } - } - } - } - } - - pub fn equipped_mag_mut(&mut self) -> Option<(ItemEntityId, &mut Mag)> { - let mag_id = self.equipped.mag?; - self.inventory.0 - .iter_mut() - .filter_map(|i| { - let individual = i.item.as_individual_mut()?; - let entity_id = individual.entity_id; - Some((entity_id, individual.as_mag_mut()?)) - }) - .find(|(entity_id, _)| *entity_id == mag_id) - } - - pub fn sort(&mut self, item_ids: &[ClientItemId]) { - self.inventory.0.sort_by(|a, b| { - let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id); - let b_index = item_ids.iter().position(|item_id| *item_id == b.item_id); - - match (a_index, b_index) { - (Some(a_index), Some(b_index)) => { - a_index.cmp(&b_index) - }, - _ => Ordering::Equal - } - }); - } - - pub fn as_inventory_entity(&self, _character_id: &CharacterEntityId) -> InventoryEntity { - InventoryEntity { - items: self.inventory.0.iter() - .map(|item| { - match &item.item { - InventoryItemDetail::Individual(item) => { - InventoryItemEntity::Individual(ItemEntity { - id: item.entity_id, - item: item.item.clone(), - }) - }, - InventoryItemDetail::Stacked(items) => { - InventoryItemEntity::Stacked(items.entity_ids.iter() - .map(|id| { - ItemEntity { - id: *id, - item: ItemDetail::Tool(items.tool) - } - }) - .collect()) - }, - } - }) - .collect() - } - } - - pub fn as_equipped_entity(&self) -> EquippedEntity { - self.equipped.clone() - } - - - pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] { - self.inventory.0.iter() - .enumerate() - .fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| { - let bytes = item.item.as_client_bytes(); - inventory[slot].data1.copy_from_slice(&bytes[0..12]); - inventory[slot].data2.copy_from_slice(&bytes[12..16]); - inventory[slot].item_id = item.item_id.0; - inventory[slot].equipped = 0; - inventory[slot].flags = 0; - - if let InventoryItemDetail::Individual(individual_item) = &item.item { - if self.equipped.is_equipped(&individual_item.entity_id) { - if let ItemDetail::Unit(_) = individual_item.item { - inventory[slot].data1[4] = self.equipped.unit.iter() - .enumerate() - .find(|(_, u_id)| **u_id == Some(individual_item.entity_id)) - .map(|(a, _)| a) - .unwrap_or(0) as u8 - } - inventory[slot].equipped = 1; - inventory[slot].flags |= 8; - } - } - inventory - }) - } -} #[derive(Clone, Debug)] pub struct Bank(Vec); @@ -1182,7 +672,7 @@ impl ItemState { let inventory_state = InventoryState { character_id: character.id, item_id_counter: 0, - inventory: Inventory(inventory_items), + inventory: Inventory::new(inventory_items), equipped, meseta: character_meseta, }; diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index fb135f7..644d2a1 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -4,7 +4,10 @@ use crate::entity::item; use crate::common::leveltable::CharacterStats; use crate::ship::ship::{ShipError}; use crate::ship::items::ClientItemId; -use crate::ship::items::state::{InventoryItem, FloorItem, BankState, IndividualItemDetail}; +use crate::ship::items::inventory::InventoryItem; +use crate::ship::items::state::IndividualItemDetail; +use crate::ship::items::state::BankState; +use crate::ship::items::state::FloorItem; use crate::ship::location::AreaClient; use std::convert::TryInto; use crate::ship::shops::ShopItem; diff --git a/src/ship/packet/handler/trade.rs b/src/ship/packet/handler/trade.rs index 60b05c3..4627697 100644 --- a/src/ship/packet/handler/trade.rs +++ b/src/ship/packet/handler/trade.rs @@ -5,7 +5,8 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::items::ClientItemId; -use crate::ship::items::state::{ItemState, ItemStateError, InventoryItemDetail}; +use crate::ship::items::state::{ItemState, ItemStateError}; +use crate::ship::items::inventory::InventoryItemDetail; use crate::ship::trade::{TradeItem, TradeState, TradeStatus}; use crate::entity::gateway::EntityGateway; use crate::ship::packet::builder; diff --git a/tests/test_trade.rs b/tests/test_trade.rs index 8e35222..fd2c1b7 100644 --- a/tests/test_trade.rs +++ b/tests/test_trade.rs @@ -5,7 +5,8 @@ use elseware::entity::item; use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket, ShipError}; use elseware::entity::item::{Meseta, ItemEntity}; use elseware::ship::packet::handler::trade::TradeError; -use elseware::ship::items::state::{ItemStateError, InventoryError}; +use elseware::ship::items::state::ItemStateError; +use elseware::ship::items::inventory::InventoryError; use libpso::packet::ship::*; use libpso::packet::messages::*;