use std::cmp::Ordering; use libpso::character::character; use crate::ship::items::ClientItemId; use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity, BankName}; use std::future::Future; use crate::entity::character::CharacterEntityId; use crate::ship::items::state::ItemStateError; use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult}; use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; #[derive(thiserror::Error, Debug)] pub enum BankError { #[error("bank full")] BankFull, #[error("stack full")] StackFull, #[error("meseta full")] MesetaFull, } #[derive(Clone, Debug)] pub enum BankItemDetail { Individual(IndividualItemDetail), Stacked(StackedItemDetail), } impl BankItemDetail { fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { match self { BankItemDetail::Stacked(sitem) => Some(sitem), _ => None, } } pub fn as_client_bytes(&self) -> [u8; 16] { match self { BankItemDetail::Individual(item) => { match &item.item { ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Armor(a) => a.as_bytes(), ItemDetail::Shield(s) => s.as_bytes(), ItemDetail::Unit(u) => u.as_bytes(), ItemDetail::Tool(t) => t.as_individual_bytes(), ItemDetail::TechniqueDisk(d) => d.as_bytes(), ItemDetail::Mag(m) => m.as_bytes(), ItemDetail::ESWeapon(e) => e.as_bytes(), } }, BankItemDetail::Stacked(item) => { item.tool.as_stacked_bytes(item.entity_ids.len()) }, } } } #[derive(Clone, Debug)] pub struct BankItem { pub item_id: ClientItemId, pub item: BankItemDetail, } impl BankItem { 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 { BankItemDetail::Individual(individual_item) => { param = func(param, individual_item.entity_id).await?; }, BankItemDetail::Stacked(stacked_item) => { for entity_id in &stacked_item.entity_ids { param = func(param, *entity_id).await?; } } } Ok(param) } } #[derive(Clone, Debug)] pub struct Bank(Vec); impl Bank { pub fn new(items: Vec) -> Bank { Bank(items) } } #[derive(Clone, Debug)] pub struct BankState { pub character_id: CharacterEntityId, pub item_id_counter: u32, pub name: BankName, pub bank: Bank, pub meseta: Meseta, } impl BankState { pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState { bank.0.sort(); BankState { character_id, item_id_counter: 0, name, bank, meseta, } } pub fn count(&self) -> usize { self.bank.0.len() } pub fn initialize_item_ids(&mut self, base_item_id: u32) { for (i, item) in self.bank.0.iter_mut().enumerate() { item.item_id = ClientItemId(base_item_id + i as u32); } self.item_id_counter = base_item_id + self.bank.0.len() as u32; } pub fn add_meseta(&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 add_inventory_item(&mut self, item: InventoryItem) -> Result { match item.item { InventoryItemDetail::Individual(iitem) => { if self.bank.0.len() >= 30 { Err(BankError::BankFull) } else { self.bank.0.push(BankItem { item_id: item.item_id, item: BankItemDetail::Individual(iitem) }); self.bank.0.sort(); Ok(AddItemResult::NewItem) } }, InventoryItemDetail::Stacked(sitem) => { let existing_stack = self.bank.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(BankError::StackFull) } else { existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); Ok(AddItemResult::AddToStack) } }, None => { if self.bank.0.len() >= 30 { Err(BankError::BankFull) } else { self.bank.0.push(BankItem { item_id: item.item_id, item: BankItemDetail::Stacked(sitem) }); self.bank.0.sort(); Ok(AddItemResult::NewItem) } } } } } } pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { let idx = self.bank.0 .iter() .position(|i| i.item_id == *item_id)?; match &mut self.bank.0[idx].item { BankItemDetail::Individual(_individual_item) => { Some(self.bank.0.remove(idx)) }, BankItemDetail::Stacked(stacked_item) => { let remove_all = 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.bank.0.remove(idx)) } else { let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); self.item_id_counter += 1; Some(BankItem { item_id: ClientItemId(self.item_id_counter), item: BankItemDetail::Stacked(StackedItemDetail { entity_ids, tool: stacked_item.tool, })}) } } } } pub fn as_client_bank_items(&self) -> character::Bank { self.bank.0.iter() .enumerate() .fold(character::Bank::default(), |mut bank, (slot, item)| { bank.item_count = (slot + 1) as u32; let bytes = item.item.as_client_bytes(); bank.items[slot].data1.copy_from_slice(&bytes[0..12]); bank.items[slot].data2.copy_from_slice(&bytes[12..16]); bank.items[slot].item_id = item.item_id.0; bank }) } pub fn as_client_bank_request(&self) -> Vec { self.bank.0.iter() .map(|item| { let bytes = item.item.as_client_bytes(); let mut data1 = [0; 12]; let mut data2 = [0; 4]; data1.copy_from_slice(&bytes[0..12]); data2.copy_from_slice(&bytes[12..16]); let amount = match &item.item { BankItemDetail::Individual(_individual_bank_item) => { 1 }, BankItemDetail::Stacked(stacked_bank_item) => { stacked_bank_item.count() }, }; character::BankItem { data1, data2, item_id: item.item_id.0, amount: amount as u16, flags: 1, } }) .collect() } pub fn as_bank_entity(&self) -> BankEntity { BankEntity { items: self.bank.0.iter() .map(|item| { match &item.item { BankItemDetail::Individual(item) => { BankItemEntity::Individual(ItemEntity { id: item.entity_id, item: item.item.clone(), }) }, BankItemDetail::Stacked(items) => { BankItemEntity::Stacked(items.entity_ids.iter() .map(|id| { ItemEntity { id: *id, item: ItemDetail::Tool(items.tool) } }) .collect()) }, } }) .collect() } } } impl std::cmp::PartialEq for BankItem { fn eq(&self, other: &BankItem) -> bool { let mut self_bytes = [0u8; 4]; let mut other_bytes = [0u8; 4]; self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]); other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]); let self_value = u32::from_be_bytes(self_bytes); let other_value = u32::from_be_bytes(other_bytes); self_value.eq(&other_value) } } impl std::cmp::Eq for BankItem {} impl std::cmp::PartialOrd for BankItem { fn partial_cmp(&self, other: &BankItem) -> Option { let mut self_bytes = [0u8; 4]; let mut other_bytes = [0u8; 4]; self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]); other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]); let self_value = u32::from_be_bytes(self_bytes); let other_value = u32::from_be_bytes(other_bytes); self_value.partial_cmp(&other_value) } } impl std::cmp::Ord for BankItem { fn cmp(&self, other: &BankItem) -> std::cmp::Ordering { let mut self_bytes = [0u8; 4]; let mut other_bytes = [0u8; 4]; self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]); other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]); let self_value = u32::from_le_bytes(self_bytes); let other_value = u32::from_le_bytes(other_bytes); self_value.cmp(&other_value) } }