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 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; use crate::entity::item::mag::Mag; use crate::ship::drops::ItemDrop; // TODO: Commit trait that ItemStateProxy and EntityTransaction implement that .commit requires and acts on upon everything succeeding (like 3 less lines of code!) #[derive(thiserror::Error, Debug)] pub enum ItemStateError { #[error("character {0} not found")] NoCharacter(CharacterEntityId), #[error("room {0} not found")] NoRoom(RoomId), #[error("floor item {0} not found")] NoFloorItem(ClientItemId), #[error("expected {0} to be a tool")] NotATool(ClientItemId), #[error("bank item {0} not found")] NoBankItem(ClientItemId), #[error("inventory error {0}")] InventoryError(#[from] InventoryError), #[error("bank error {0}")] BankError(#[from] BankError), #[error("invalid drop? {0:?} (this shouldn't occur)")] BadItemDrop(ItemDrop), #[error("idk")] Dummy, #[error("gateway")] GatewayError(#[from] GatewayError), #[error("tried to remove more meseta than exists: {0}")] InvalidMesetaRemoval(u32), #[error("tried to add meseta when there is no more room")] FullOfMeseta, #[error("stacked item")] StackedItemError(Vec), #[error("apply item {0}")] ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError), } pub enum FloorType { Local, Shared, } #[async_trait::async_trait] pub trait ItemAction { type Input; type Output; type Start; type Error; async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error>; async fn commit(&self, v: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error>; } pub struct ItemStateAction { _t: std::marker::PhantomData, _s: std::marker::PhantomData, _e: std::marker::PhantomData, } impl Default for ItemStateAction { fn default() -> ItemStateAction { ItemStateAction { _t: std::marker::PhantomData, _s: std::marker::PhantomData, _e: std::marker::PhantomData, } } } impl ItemStateAction where T: Send + Sync, S: Send + Sync, E: Send + Sync, { pub fn act(self, f: F) -> ItemActionStage, F, Fut, S, E> where F: Fn(S, ()) -> Fut + Send + Sync, Fut: Future> + Send { ItemActionStage { _s: Default::default(), _e: std::marker::PhantomData, prev: self, actionf: f, } } } pub struct ItemActionStage where P: ItemAction, F: Fn(S, P::Output) -> Fut + Send + Sync, Fut: Future> + Send, { _s: std::marker::PhantomData, _e: std::marker::PhantomData, prev: P, actionf: F, } #[async_trait::async_trait] impl ItemAction for ItemActionStage where P: ItemAction + ItemAction + Send + Sync, F: Fn(S, P::Output) -> Fut + Send + Sync, Fut: Future> + Send, S: Send + Sync, P::Output: Send + Sync, E: Send + Sync, O: Send + Sync, P::Error: Send + Sync, { type Input = P::Output; type Output = O; type Start = S; type Error = P::Error; async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> { (self.actionf)(s, i).await } async fn commit(&self, i: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error> { let (i, prev) = self.prev.commit(i).await?; self.action(i, prev).await } } impl ItemActionStage where P: ItemAction + Send + Sync, F: Fn(S, P::Output) -> Fut + Send + Sync, Fut: Future> + Send, S: Send + Sync, P::Output: Send + Sync, E: Send + Sync, O: Send + Sync, P::Error: Send + Sync, { #[allow(clippy::type_complexity)] pub fn act(self, g: G) -> ItemActionStage, G, GFut, S, E> where S: Send + Sync, G: Fn(S, as ItemAction>::Output) -> GFut + Send + Sync, GFut: Future> + Send, O2: Send + Sync, { ItemActionStage { _s: Default::default(), _e: Default::default(), prev: self, actionf: g, } } } #[async_trait::async_trait] impl ItemAction for ItemStateAction where T: Send + Sync, S: Send + Sync, E: Send + Sync, { type Input = T; type Output = (); type Start = T; type Error = E; async fn action(&self, s: Self::Start, _i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> { Ok((s, ())) } async fn commit(&self, i: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error> { Ok((i, ())) } } #[derive(Clone, Debug)] pub struct IndividualItemDetail { pub entity_id: ItemEntityId, pub item: ItemDetail, } #[derive(Clone, Debug)] pub struct StackedItemDetail { pub entity_ids: Vec, pub tool: Tool, } impl StackedItemDetail { pub fn count(&self) -> usize { self.entity_ids.len() } } #[derive(Clone, Debug)] pub enum InventoryItemDetail { Individual(IndividualItemDetail), Stacked(StackedItemDetail), } impl InventoryItemDetail { fn stacked(&self) -> Option<&StackedItemDetail> { match self { InventoryItemDetail::Stacked(sitem) => Some(sitem), _ => None, } } fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { match self { InventoryItemDetail::Stacked(sitem) => Some(sitem), _ => None, } } pub fn as_client_bytes(&self) -> [u8; 16] { match self { InventoryItemDetail::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(), } }, InventoryItemDetail::Stacked(item) => { item.tool.as_stacked_bytes(item.entity_ids.len()) }, } } } #[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) -> T 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; } } } param } pub async fn with_mag(&self, mut param: T, mut func: F) -> T 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; } } param } } #[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) -> T 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; } } } param } } #[derive(Debug, Clone)] pub enum FloorItemDetail { Individual(IndividualItemDetail), Stacked(StackedItemDetail), Meseta(Meseta), } impl FloorItemDetail { fn stacked(&self) -> Option<&StackedItemDetail> { match self { FloorItemDetail::Stacked(sitem) => Some(sitem), _ => None, } } } #[derive(Debug, Clone)] pub struct FloorItem { pub item_id: ClientItemId, pub item: FloorItemDetail, pub map_area: MapArea, pub x: f32, pub y: f32, pub z: f32, } impl FloorItem { pub async fn with_entity_id(&self, mut param: T, mut func: F) -> T where F: FnMut(T, ItemEntityId) -> Fut, Fut: Future, { match &self.item { FloorItemDetail::Individual(individual_item) => { param = func(param, individual_item.entity_id).await; }, FloorItemDetail::Stacked(stacked_item) => { for entity_id in &stacked_item.entity_ids { param = func(param, *entity_id).await; } }, FloorItemDetail::Meseta(_meseta) => {}, } param } pub async fn with_mag(&self, mut param: T, mut func: F) -> T where F: FnMut(T, ItemEntityId, Mag) -> Fut, Fut: Future, { if let FloorItemDetail::Individual(individual_item) = &self.item { if let ItemDetail::Mag(mag) = &individual_item.item { param = func(param, individual_item.entity_id, mag.clone()).await; } } param } pub fn as_client_bytes(&self) -> [u8; 16] { match &self.item { FloorItemDetail::Individual(individual_floor_item) => { individual_floor_item.item.as_client_bytes() }, FloorItemDetail::Stacked(stacked_floor_item) => { stacked_floor_item.tool.as_stacked_bytes(stacked_floor_item.entity_ids.len()) }, FloorItemDetail::Meseta(meseta_floor_item) => { meseta_floor_item.as_bytes() } } } } #[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")] BankFull, #[error("stack full")] StackFull, #[error("meseta full")] MesetaFull, } #[derive(Clone)] pub enum AddItemResult { NewItem, AddToStack, Meseta, } #[derive(Debug, Clone, Default)] 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 { match &item.item { InventoryItemDetail::Individual(_) => { if self.inventory.0.len() >= 30 { Err(InventoryError::InventoryFull) } else { self.inventory.0.push(item); Ok(AddItemResult::NewItem) } }, 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) } }, None => { if self.inventory.0.len() >= 30 { Err(InventoryError::InventoryFull) } else { self.inventory.0.push(item); Ok(AddItemResult::NewItem) } } } } } } 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: entity_ids, tool: stacked_item.tool, })}) } } } } 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 sort(&mut self, item_ids: &Vec) { 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); #[derive(Clone, Debug)] pub struct BankState { character_id: CharacterEntityId, item_id_counter: u32, pub name: BankName, 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 += 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: 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) } } #[derive(Debug)] pub struct FloorState { character_id: CharacterEntityId, local: LocalFloor, shared: SharedFloor, } impl FloorState { pub fn take_item(&mut self, item_id: &ClientItemId) -> Option { let item = self.local.0 .drain_filter(|item| { item.item_id == *item_id }) .next(); item.or_else(|| { self.shared.0 .drain_filter(|item| { item.item_id == *item_id }) .next() }) } pub fn add_inventory_item(&mut self, inventory_item: InventoryItem, map_area: MapArea, position: (f32, f32, f32)) -> &FloorItem { let floor_item = FloorItem { item_id: inventory_item.item_id, item: match inventory_item.item { InventoryItemDetail::Individual(individual_item) => FloorItemDetail::Individual(individual_item), InventoryItemDetail::Stacked(stacked_item) => FloorItemDetail::Stacked(stacked_item), }, map_area: map_area, x: position.0, y: position.1, z: position.2, }; self.shared.0.push(floor_item); &self.shared.0[self.shared.0.len()-1] } pub fn add_item(&mut self, floor_item: FloorItem) -> &FloorItem { self.shared.0.push(floor_item); &self.shared.0[self.shared.0.len()-1] } } pub struct ItemState { character_inventory: HashMap, character_bank: HashMap, character_room: HashMap, character_floor: HashMap, room_floor: HashMap, room_item_id_counter: u32, } impl Default for ItemState { fn default() -> ItemState { ItemState { character_inventory: HashMap::new(), character_bank: HashMap::new(), character_room: HashMap::new(), character_floor: HashMap::new(), room_floor: HashMap::new(), room_item_id_counter: 0x00810000, } } } impl ItemState { pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<&InventoryState, ItemStateError> { Ok(self.character_inventory.get(&character.id) .ok_or(ItemStateError::NoCharacter(character.id))?) } pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&BankState, ItemStateError> { Ok(self.character_bank.get(&character.id) .ok_or(ItemStateError::NoCharacter(character.id))?) } } impl ItemState { fn new_item_id(&mut self) -> Result { self.room_item_id_counter += 1; Ok(ClientItemId(self.room_item_id_counter)) } pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> { let inventory = entity_gateway.get_character_inventory(&character.id).await?; let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?; let equipped = entity_gateway.get_character_equips(&character.id).await?; let inventory_items = inventory.items.into_iter() .map(|item| -> Result { Ok(match item { InventoryItemEntity::Individual(item) => { InventoryItem { item_id: ClientItemId(0), item: InventoryItemDetail::Individual(IndividualItemDetail { entity_id: item.id, item: item.item, }), } }, InventoryItemEntity::Stacked(items) => { InventoryItem { item_id: ClientItemId(0), item: InventoryItemDetail::Stacked(StackedItemDetail { entity_ids: items.iter().map(|i| i.id).collect(), tool: items.get(0) .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? .item .clone() .as_tool() .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? }) } }, }) }) .collect::, _>>()?; let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let inventory_state = InventoryState { character_id: character.id, item_id_counter: 0, inventory: Inventory(inventory_items), equipped: equipped, meseta: character_meseta, }; let bank_items = bank.items.into_iter() .map(|item| -> Result { Ok(match item { BankItemEntity::Individual(item) => { BankItem { item_id: self.new_item_id()?, item: BankItemDetail::Individual(IndividualItemDetail { entity_id: item.id, item: item.item, }) } }, BankItemEntity::Stacked(items) => { BankItem { item_id: self.new_item_id()?, item: BankItemDetail::Stacked(StackedItemDetail { entity_ids: items.iter().map(|i| i.id).collect(), tool: items.get(0) .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? .item .clone() .as_tool() .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? }) } }, }) }) .collect::, _>>()?; let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; let bank_state = BankState::new(character.id, BankName("".into()), Bank(bank_items), bank_meseta); self.character_inventory.insert(character.id, inventory_state); self.character_bank.insert(character.id, bank_state); Ok(()) } pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) { let base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000; let inventory = self.character_inventory.get_mut(&character.id).unwrap(); inventory.initialize_item_ids(base_inventory_id); let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000; let default_bank = self.character_bank.get_mut(&character.id); if let Some(default_bank ) = default_bank { default_bank.initialize_item_ids(base_bank_id); } self.character_room.insert(character.id, room_id); self.character_floor.insert(character.id, LocalFloor::default()); self.room_floor.entry(room_id).or_insert_with(SharedFloor::default); } pub fn remove_character_from_room(&mut self, character: &CharacterEntity) { self.character_inventory.remove(&character.id); self.character_floor.remove(&character.id); if let Some(room) = self.character_room.remove(&character.id).as_ref() { if self.character_room.iter().any(|(_, r)| r == room) { self.room_floor.remove(room); } } } pub fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(&FloorItem, FloorType), ItemStateError> { let local_floor = self.character_floor.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?; let room = self.character_room.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?; let shared_floor = self.room_floor.get(room).ok_or(ItemStateError::NoCharacter(*character_id))?; local_floor.0 .iter() .find(|item| item.item_id == *item_id) .map(|item| (item, FloorType::Local)) .or_else(|| { shared_floor.0 .iter() .find(|item| item.item_id == *item_id) .map(|item| (item, FloorType::Shared)) }) .ok_or_else(|| ItemStateError::NoFloorItem(*item_id)) } } #[derive(Default)] struct ProxiedItemState { character_inventory: HashMap, character_bank: HashMap, character_room: HashMap, character_floor: HashMap, room_floor: HashMap, } pub struct ItemStateProxy<'a> { item_state: &'a mut ItemState, proxied_state: ProxiedItemState, } impl<'a> ItemStateProxy<'a> { pub fn commit(self) { self.item_state.character_inventory.extend(self.proxied_state.character_inventory.clone()); self.item_state.character_bank.extend(self.proxied_state.character_bank.clone()); self.item_state.character_room.extend(self.proxied_state.character_room.clone()); self.item_state.character_floor.extend(self.proxied_state.character_floor.clone()); self.item_state.room_floor.extend(self.proxied_state.room_floor.clone()); } } fn get_or_clone(master: &HashMap, proxy: &mut HashMap, key: K, err: fn(K) -> ItemStateError) -> Result where K: Eq + std::hash::Hash + Copy, V: Clone { let existing_element = master.get(&key).ok_or_else(|| err(key))?; Ok(proxy.entry(key) .or_insert_with(|| existing_element.clone()).clone()) } impl<'a> ItemStateProxy<'a> { pub fn new(item_state: &'a mut ItemState) -> Self { ItemStateProxy { item_state, proxied_state: Default::default(), } } pub fn inventory(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter) } pub fn set_inventory(&mut self, inventory: InventoryState) { self.proxied_state.character_inventory.insert(inventory.character_id, inventory); } pub fn bank(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_bank, &mut self.proxied_state.character_bank, *character_id, ItemStateError::NoCharacter) } pub fn set_bank(&mut self, bank: BankState) { self.proxied_state.character_bank.insert(bank.character_id, bank); } pub fn floor(&mut self, character_id: &CharacterEntityId) -> Result { let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, *character_id, ItemStateError::NoCharacter)?; Ok(FloorState { character_id: *character_id, local: get_or_clone(&self.item_state.character_floor, &mut self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter)?, shared: get_or_clone(&self.item_state.room_floor, &mut self.proxied_state.room_floor, room_id, ItemStateError::NoRoom)?, }) } pub fn set_floor(&mut self, floor: FloorState) { let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, floor.character_id, ItemStateError::NoCharacter).unwrap(); self.proxied_state.character_floor.insert(floor.character_id, floor.local); self.proxied_state.room_floor.insert(room_id, floor.shared); } pub fn new_item_id(&mut self) -> Result { self.item_state.new_item_id() } }