diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 3fd7cc6..93d10d7 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -9,6 +9,7 @@ use crate::entity::item::*; use std::sync::{Arc, Mutex}; +// TODO: implement multiple banks pub struct InMemoryGatewayTransaction<'a> { working_gateway: InMemoryGateway, diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index 89a219e..b54de2b 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -610,6 +610,14 @@ pub enum PgItemNoteDetail { character_to: u32, character_from: u32, }, + Withdraw { + character_id: u32, + bank: String, + }, + Deposit { + character_id: u32, + bank: String, + } } impl From for PgItemNoteDetail { @@ -643,6 +651,18 @@ impl From for PgItemNoteDetail { id: id.0, character_to: character_to.0, character_from: character_from.0, + }, + ItemNote::Withdraw{character_id, bank} => { + PgItemNoteDetail::Withdraw { + character_id: character_id.0, + bank: bank.0, + } + }, + ItemNote::Deposit{character_id, bank} => { + PgItemNoteDetail::Deposit { + character_id: character_id.0, + bank: bank.0, + } } } } @@ -679,7 +699,15 @@ impl From for ItemNote { id: TradeId(id as u32), character_to: CharacterEntityId(character_to as u32), character_from: CharacterEntityId(character_from as u32), - } + }, + PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw { + character_id: CharacterEntityId(character_id as u32), + bank: BankName(bank), + }, + PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit { + character_id: CharacterEntityId(character_id as u32), + bank: BankName(bank), + }, } } } diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 220b810..203d819 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -59,6 +59,14 @@ pub enum ItemNote { character_to: CharacterEntityId, character_from: CharacterEntityId, }, + Withdraw { + character_id: CharacterEntityId, + bank: BankName, + }, + Deposit { + character_id: CharacterEntityId, + bank: BankName, + }, } #[derive(Debug, Copy, Clone, PartialEq)] diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index 1a80b7a..555403c 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -6,7 +6,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, ItemStateAction, ItemAction, ItemStateError, FloorItem, InventoryItem, AddItemResult, FloorItemDetail, StackedItemDetail}; +use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateAction, ItemAction, ItemStateError, FloorItem, InventoryItem, AddItemResult, FloorItemDetail, + StackedItemDetail, BankItem, BankItemDetail, InventoryItemDetail}; + + pub enum TriggerCreateItem { Yes, @@ -94,14 +97,14 @@ where }).await } -fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId) +fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32) -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> { move |(mut item_state, mut transaction), _| { Box::pin(async move { let mut inventory = item_state.inventory(&character_id)?; - let item = inventory.take_item(&item_id).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?; + let item = inventory.take_item(&item_id, amount).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?; transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; item_state.set_inventory(inventory); @@ -114,7 +117,7 @@ fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItem fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32, f32)) -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) - -> Pin, Box), ()), ItemStateError>> + Send + 'a>> + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> { move |(mut item_state, transaction), inventory_item| { Box::pin(async move { @@ -133,10 +136,10 @@ fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: }}).await?; let mut floor = item_state.floor(&character_id)?; - floor.add_inventory_item(inventory_item, map_area, drop_position); + let floor_item = floor.add_inventory_item(inventory_item, map_area, drop_position).clone(); item_state.set_floor(floor); - Ok(((item_state, transaction), ())) + Ok(((item_state, transaction), floor_item)) }) } } @@ -148,14 +151,14 @@ pub async fn drop_item( item_id: &ClientItemId, map_area: MapArea, drop_position: (f32, f32, f32)) - -> Result<(), ItemStateError> + -> Result where EG: EntityGateway, { entity_gateway.with_transaction(|transaction| async move { let item_state_proxy = ItemStateProxy::new(item_state); let ((item_state_proxy, transaction), result) = ItemStateAction::default() - .act(take_item_from_inventory(character.id, *item_id)) + .act(take_item_from_inventory(character.id, *item_id, 1)) .act(add_inventory_item_to_shared_floor(character.id, map_area, drop_position)) .commit((item_state_proxy, transaction)) .await?; @@ -164,53 +167,62 @@ where }).await } +pub async fn drop_partial_item<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + map_area: MapArea, + drop_position: (f32, f32), + amount: u32) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_item_from_inventory(character.id, *item_id, amount)) + .act(add_inventory_item_to_shared_floor(character.id, map_area, (drop_position.0, 0.0, drop_position.1))) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + -fn take_partial_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32) - -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) - -> Pin, Box), StackedItemDetail), ItemStateError>> + Send + 'a>> +fn take_meseta_from_inventory(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> { move |(mut item_state, mut transaction), _| { Box::pin(async move { let mut inventory = item_state.inventory(&character_id)?; - let item = inventory.take_partial_item(&item_id, amount).ok_or_else (|| ItemStateError::NoFloorItem(item_id))?; - - transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; - item_state.set_inventory(inventory); + inventory.remove_meseta(amount)?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; - Ok(((item_state, transaction), item)) + Ok(((item_state, transaction), ())) }) } } -fn add_partial_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32)) - -> impl for<'a> Fn((ItemStateProxy<'a>, Box), StackedItemDetail) - -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +fn add_meseta_to_shared_floor(character_id: CharacterEntityId, amount: u32, map_area: MapArea, drop_position: (f32, f32)) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> { - move |(mut item_state, transaction), stacked_item| { + + move |(mut item_state, transaction), _| { Box::pin(async move { let floor_item = FloorItem { item_id: item_state.new_item_id()?, - item: FloorItemDetail::Stacked(stacked_item), - map_area, + item: FloorItemDetail::Meseta(Meseta(amount)), + map_area: map_area, x: drop_position.0, y: 0.0, z: drop_position.1, }; - let transaction = floor_item.with_entity_id(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id| { - async move { - if let Ok(transaction) = &mut transaction { - transaction.gateway().add_item_note(&entity_id, ItemNote::PlayerDrop { - character_id, - map_area, - x: drop_position.0, - y: 0.0, - z: drop_position.1, - }).await?; - } - transaction - }}).await?; - let mut floor = item_state.floor(&character_id)?; let floor_item = floor.add_item(floor_item).clone(); item_state.set_floor(floor); @@ -220,11 +232,10 @@ fn add_partial_inventory_item_to_shared_floor(character_id: CharacterEntityId, m } } -pub async fn drop_partial_item<'a, EG>( +pub async fn drop_meseta<'a, EG>( item_state: &'a mut ItemState, entity_gateway: &mut EG, character: &CharacterEntity, - item_id: &ClientItemId, map_area: MapArea, drop_position: (f32, f32), amount: u32) @@ -235,8 +246,8 @@ where entity_gateway.with_transaction(|transaction| async move { let item_state_proxy = ItemStateProxy::new(item_state); let ((item_state_proxy, transaction), result) = ItemStateAction::default() - .act(take_partial_item_from_inventory(character.id, *item_id, amount)) - .act(add_partial_inventory_item_to_shared_floor(character.id, map_area, drop_position)) + .act(take_meseta_from_inventory(character.id, amount)) + .act(add_meseta_to_shared_floor(character.id, amount, map_area, drop_position)) .commit((item_state_proxy, transaction)) .await?; item_state_proxy.commit(); @@ -245,62 +256,228 @@ where } -fn take_meseta_from_inventory(character_id: CharacterEntityId, amount: u32) - -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) - -> Pin, Box), u32), ItemStateError>> + Send + 'a>> +fn take_meseta_from_bank(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + bank.remove_meseta(amount)?; + transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; + + Ok(((item_state, transaction), ())) + }) + } +} + +fn add_meseta_from_bank_to_inventory(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> { move |(mut item_state, mut transaction), _| { Box::pin(async move { let mut inventory = item_state.inventory(&character_id)?; - inventory.remove_meseta(amount)?; + inventory.add_meseta_no_overflow(amount)?; transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; - Ok(((item_state, transaction), amount)) + Ok(((item_state, transaction), ())) }) } } -fn add_meseta_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32)) - -> impl for<'a> Fn((ItemStateProxy<'a>, Box), u32) - -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> + +pub async fn withdraw_meseta<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + amount: u32) + -> Result<(), ItemStateError> +where + EG: EntityGateway, { + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_meseta_from_bank(character.id, amount)) + .act(add_meseta_from_bank_to_inventory(character.id, amount)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} - move |(mut item_state, transaction), amount| { +fn add_meseta_to_bank(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { Box::pin(async move { - let floor_item = FloorItem { - item_id: item_state.new_item_id()?, - item: FloorItemDetail::Meseta(Meseta(amount)), - map_area: map_area, - x: drop_position.0, - y: 0.0, - z: drop_position.1, + let mut bank = item_state.bank(&character_id)?; + bank.add_meseta(amount)?; + transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; + + Ok(((item_state, transaction), ())) + }) + } +} + +pub async fn deposit_meseta<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + amount: u32) + -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_meseta_from_inventory(character.id, amount)) + .act(add_meseta_to_bank(character.id, amount)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, ())) + }).await +} + +fn take_item_from_bank(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), BankItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + let item = bank.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoBankItem(item_id))?; + transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?; + item_state.set_bank(bank); + + Ok(((item_state, transaction), item)) + }) + } +} + +fn add_bank_item_to_inventory(character: &CharacterEntity) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), BankItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + let character = character.clone(); + move |(mut item_state, transaction), bank_item| { + let character = character.clone(); + Box::pin(async move { + let bank_name = item_state.bank(&character.id)?.name; + let mut inventory = item_state.inventory(&character.id)?; + + let character_id = character.id; + let transaction = bank_item.with_entity_id(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id| { + let bank_name = bank_name.clone(); + async move { + if let Ok(transaction) = &mut transaction { + transaction.gateway().add_item_note(&entity_id, ItemNote::Withdraw { + character_id, + bank: bank_name, + }).await?; + } + transaction + }}).await?; + + let inventory_item = InventoryItem { + item_id: bank_item.item_id, + item: match bank_item.item { + BankItemDetail::Individual(individual_item) => InventoryItemDetail::Individual(individual_item), + BankItemDetail::Stacked(stacked_item) => InventoryItemDetail::Stacked(stacked_item), + }, }; - let mut floor = item_state.floor(&character_id)?; - let floor_item = floor.add_item(floor_item).clone(); - item_state.set_floor(floor); + let mut transaction = inventory_item.with_mag(Ok(transaction), |mut transaction: Result<_, ItemStateError>, entity_id, _mag| { + let character = character.clone(); + async move { + if let Ok(transaction) = &mut transaction { + transaction.gateway().change_mag_owner(&entity_id, &character).await?; + } + transaction + }}).await?; - Ok(((item_state, transaction), floor_item)) + inventory.add_item(inventory_item.clone())?; + transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), inventory_item)) }) } } -pub async fn drop_meseta<'a, EG>( +pub async fn withdraw_item<'a, EG>( item_state: &'a mut ItemState, entity_gateway: &mut EG, character: &CharacterEntity, - map_area: MapArea, - drop_position: (f32, f32), + item_id: &ClientItemId, amount: u32) - -> Result + -> Result where EG: EntityGateway, { entity_gateway.with_transaction(|transaction| async move { let item_state_proxy = ItemStateProxy::new(item_state); let ((item_state_proxy, transaction), result) = ItemStateAction::default() - .act(take_meseta_from_inventory(character.id, amount)) - .act(add_meseta_to_shared_floor(character.id, map_area, drop_position)) + .act(take_item_from_bank(character.id, *item_id, amount)) + .act(add_bank_item_to_inventory(&character)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + +fn add_inventory_item_to_bank(character_id: CharacterEntityId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), inventory_item| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + let bank_name = bank.name.clone(); + let mut transaction = inventory_item.with_entity_id(Ok(transaction), move |mut transaction: Result<_, ItemStateError>, entity_id| { + let bank_name = bank_name.clone(); + async move { + if let Ok(transaction) = &mut transaction { + transaction.gateway().add_item_note(&entity_id, ItemNote::Deposit { + character_id, + bank: bank_name, + }).await?; + } + transaction + }}).await?; + + bank.add_inventory_item(inventory_item)?; + + transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?; + item_state.set_bank(bank); + + + Ok(((item_state, transaction), ())) + }) + } +} + +pub async fn deposit_item<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + amount: u32) + -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_item_from_inventory(character.id, *item_id, amount)) + .act(add_inventory_item_to_bank(character.id)) .commit((item_state_proxy, transaction)) .await?; item_state_proxy.commit(); diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index f3b701a..c7b7fbf 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -388,7 +388,7 @@ impl ItemManager { .map_err(|err| err.into()) } - pub async fn enemy_drop_item_on_local_floor<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&'a FloorItem, anyhow::Error> { + pub async fn enemy_drop_item_on_local_floor<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &'a CharacterEntity, item_drop: ItemDrop) -> Result<&'a FloorItem, anyhow::Error> { let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; enum ItemOrMeseta { @@ -805,7 +805,7 @@ impl ItemManager { } pub async fn player_buys_item<'a, EG: EntityGateway>(&'a mut self, - entity_gateway: &mut EG, + entity_gateway: &'a mut EG, character: &'a CharacterEntity, shop_item: &'a (dyn ShopItem + Send + Sync), item_id: ClientItemId, diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index ec5b00c..aef4692 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -2,7 +2,7 @@ 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, BankItemEntity, BankName, EquippedEntity}; +use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, BankEntity, BankItemEntity, BankName, EquippedEntity}; use std::future::Future; use crate::ship::map::MapArea; @@ -13,6 +13,7 @@ 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 { @@ -23,9 +24,15 @@ pub enum ItemStateError { #[error("floor item {0} not found")] NoFloorItem(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), @@ -35,8 +42,11 @@ pub enum ItemStateError { #[error("gateway")] GatewayError(#[from] GatewayError), - #[error("tried to drop more meseta than in inventory: {0}")] - InvalidMesetaDrop(u32), + #[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), @@ -247,8 +257,8 @@ impl InventoryItemDetail { #[derive(Clone, Debug)] pub struct InventoryItem { - item_id: ClientItemId, - item: InventoryItemDetail, + pub item_id: ClientItemId, + pub item: InventoryItemDetail, } impl InventoryItem { @@ -270,6 +280,19 @@ impl InventoryItem { 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 + } } @@ -280,6 +303,13 @@ pub enum BankItemDetail { } 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) => { @@ -303,8 +333,29 @@ impl BankItemDetail { #[derive(Clone, Debug)] pub struct BankItem { - item_id: ClientItemId, - item: BankItemDetail, + 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(Clone)] @@ -397,6 +448,16 @@ pub enum InventoryError { 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, @@ -428,6 +489,11 @@ impl InventoryState { 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() } @@ -490,43 +556,77 @@ impl InventoryState { } } - pub fn take_item(&mut self, item_id: &ClientItemId) -> Option { - self.inventory.0 - .drain_filter(|i| i.item_id == *item_id) - .next() + 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_partial_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { - let amount = amount as usize; - let (idx, _, stacked_item) = self.inventory.0 - .iter_mut() - .enumerate() - .filter_map(|(k, item)| { - match item.item { - InventoryItemDetail::Stacked(ref mut stacked_item) => Some((k, item.item_id, stacked_item)), - _ => None + 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 = 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)) } - }) - .find(|(_, id, _)| *id == *item_id)?; - - - let remove_all = match stacked_item.entity_ids.len().cmp(&amount) { - Ordering::Equal => true, - Ordering::Greater => false, - Ordering::Less => return None, - }; - - if remove_all { - let stacked_item = stacked_item.clone(); - self.inventory.0.remove(idx); - Some(stacked_item) - } - else { - let entity_ids = stacked_item.entity_ids.drain(..amount).collect(); - Some(StackedItemDetail { - entity_ids: entity_ids, - tool: stacked_item.tool, - }) + 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, + })}) + } + } } } @@ -557,9 +657,25 @@ impl InventoryState { } } + 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::InvalidMesetaDrop(amount)) + return Err(ItemStateError::InvalidMesetaRemoval(amount)) } self.meseta.0 -= amount; Ok(()) @@ -601,16 +717,130 @@ pub struct Bank(Vec); 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 + 1; + 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 { @@ -626,6 +856,106 @@ impl BankState { 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) + } } pub struct FloorState { @@ -677,14 +1007,11 @@ impl FloorState { pub struct ItemState { character_inventory: HashMap, character_bank: HashMap, - //character_meseta: HashMap, - //bank_meseta: HashMap, character_room: HashMap, character_floor: HashMap, room_floor: HashMap, - //room_item_id_counter: Arc ClientItemId + Send + Sync>>>>, room_item_id_counter: u32, } @@ -693,8 +1020,6 @@ impl Default for ItemState { ItemState { character_inventory: HashMap::new(), character_bank: HashMap::new(), - //character_meseta: HashMap::new(), - //bank_meseta: HashMap::new(), character_room: HashMap::new(), character_floor: HashMap::new(), room_floor: HashMap::new(), @@ -795,13 +1120,8 @@ impl ItemState { }) .collect::, _>>()?; - let bank_meseta = entity_gateway.get_bank_meseta(&character.id, BankName("".into())).await?; - let bank_state = BankState { - character_id: character.id, - item_id_counter: 0, - bank: Bank(bank_items), - meseta: bank_meseta, - }; + 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); @@ -820,14 +1140,6 @@ impl ItemState { 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); - - /* - let mut inc = 0x00810000; - self.room_item_id_counter.borrow_mut().entry(room_id).or_insert_with(|| Box::new(move || { - inc += 1; - ClientItemId(inc) - })); - */ } pub fn remove_character_from_room(&mut self, character: &CharacterEntity) { @@ -864,34 +1176,12 @@ impl ItemState { struct ProxiedItemState { character_inventory: HashMap, character_bank: HashMap, - //character_meseta: HashMap, - //bank_meseta: HashMap, character_room: HashMap, character_floor: HashMap, room_floor: HashMap, - - //room_item_id_counter: HashMap ClientItemId + Send>>, - } -/* -impl Default for ProxiedItemState { - fn default() -> Self { - ProxiedItemState { - character_inventory: HashMap::new(), - //character_bank: HashMap::new(), - character_meseta: HashMap::new(), - //bank_meseta: HashMap::new(), - character_floor: HashMap::new(), - character_room: HashMap::new(), - room_floor: HashMap::new(), - //room_item_id_counter: HashMap::new(), - } - } -} -*/ - pub struct ItemStateProxy<'a> { item_state: &'a mut ItemState, proxied_state: ProxiedItemState, @@ -901,7 +1191,6 @@ 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_meseta.extend(self.proxied_state.character_meseta.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()); @@ -930,19 +1219,18 @@ impl<'a> ItemStateProxy<'a> { 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) - /* - Ok(InventoryState { - character_id: *character_id, - inventory: get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter)?, - meseta: get_or_clone(&self.item_state.character_meseta, &mut self.proxied_state.character_meseta, *character_id, ItemStateError::NoCharacter)?, - }) - */ } pub fn set_inventory(&mut self, inventory: InventoryState) { self.proxied_state.character_inventory.insert(inventory.character_id, inventory); - //self.proxied_state.character_inventory.insert(inventory.character_id, inventory.inventory); - //self.proxied_state.character_meseta.insert(inventory.character_id, inventory.meseta); + } + + 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 { diff --git a/src/ship/packet/builder/lobby.rs b/src/ship/packet/builder/lobby.rs index 56213dd..aefe96f 100644 --- a/src/ship/packet/builder/lobby.rs +++ b/src/ship/packet/builder/lobby.rs @@ -4,6 +4,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{ShipError, Clients}; use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError}; use crate::ship::packet::builder::{player_info}; +use crate::ship::items::state::ItemState; use crate::ship::items::ItemManager; @@ -11,14 +12,14 @@ pub fn join_lobby(id: ClientId, lobby: LobbyId, client_location: &ClientLocation, clients: &Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result { let lobby_clients = client_location.get_clients_in_lobby(lobby).map_err(|err| -> ClientLocationError { err.into() })?; let playerinfo = lobby_clients.iter() .map(|area_client| { let client = clients.get(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client)).unwrap(); - player_info(0x100, client, area_client, item_manager, level_table) + player_info(0x100, client, area_client, item_state, level_table) }); let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); @@ -40,7 +41,7 @@ pub fn add_to_lobby(id: ClientId, lobby: LobbyId, client_location: &ClientLocation, clients: &Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); @@ -55,7 +56,7 @@ pub fn add_to_lobby(id: ClientId, block: client.block as u16, event: 0, padding: 0, - playerinfo: player_info(0x100, client, &area_client, item_manager, level_table), + playerinfo: player_info(0x100, client, &area_client, item_state, level_table), }) } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index a5303bc..cf91fe9 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -5,6 +5,8 @@ use crate::common::leveltable::CharacterStats; use crate::ship::ship::{ShipError}; use crate::ship::items::{ClientItemId, InventoryItem, StackedFloorItem, FloorItem, CharacterBank}; use crate::ship::items::state::FloorItem as FloorItem2; +use crate::ship::items::state::InventoryItem as InventoryItem2; +use crate::ship::items::state::{BankState}; use crate::ship::location::AreaClient; use std::convert::TryInto; use crate::ship::shops::ShopItem; @@ -78,6 +80,18 @@ pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &Inventory }) } +pub fn create_withdrawn_inventory_item2(area_client: AreaClient, item: &InventoryItem2) -> Result { + let bytes = item.item.as_client_bytes(); + Ok(CreateItem { + client: area_client.local_client.id(), + target: 0, + item_data: bytes[0..12].try_into()?, + item_id: item.item_id.0, + item_data2: bytes[12..16].try_into()?, + unknown: 0, + }) +} + pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem2) -> Result { Ok(RemoveItemFromFloor { client: area_client.local_client.id(), @@ -147,7 +161,7 @@ pub fn character_leveled_up(area_client: AreaClient, level: u32, before_stats: C } // TOOD: checksum? -pub fn bank_item_list(bank: &CharacterBank, char_bank_meseta: u32) -> BankItemList { +pub fn bank_item_list(bank: &BankState) -> BankItemList { BankItemList { aflag: 0, cmd: 0xBC, @@ -155,7 +169,7 @@ pub fn bank_item_list(bank: &CharacterBank, char_bank_meseta: u32) -> BankItemLi size: bank.count() as u32 * 0x18 + 0x14, checksum: 0x123434, item_count: bank.count() as u32, - meseta: char_bank_meseta, + meseta: bank.meseta.0, items: bank.as_client_bank_request() } } diff --git a/src/ship/packet/builder/mod.rs b/src/ship/packet/builder/mod.rs index f5d9f3c..80d9506 100644 --- a/src/ship/packet/builder/mod.rs +++ b/src/ship/packet/builder/mod.rs @@ -10,7 +10,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::character::CharacterBytesBuilder; use crate::ship::ship::ClientState; use crate::ship::location::AreaClient; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -> PlayerHeader { PlayerHeader { @@ -23,15 +23,14 @@ pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) - } } -pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_manager: &ItemManager, level_table: &CharacterLevelTable) -> PlayerInfo { +pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_state: &ItemState, level_table: &CharacterLevelTable) -> PlayerInfo { let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); - let inventory = item_manager.get_character_inventory(&client.character).unwrap(); - let meseta = item_manager.get_character_meseta(&client.character.id).unwrap(); + let inventory = item_state.get_character_inventory(&client.character).unwrap(); let character = CharacterBytesBuilder::default() .character(&client.character) .stats(&stats) .level(level - 1) - .meseta(*meseta) + .meseta(inventory.meseta) .build(); PlayerInfo { header: player_header(tag, client, area_client), diff --git a/src/ship/packet/builder/room.rs b/src/ship/packet/builder/room.rs index c5bd11c..a97d8b8 100644 --- a/src/ship/packet/builder/room.rs +++ b/src/ship/packet/builder/room.rs @@ -4,6 +4,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{ShipError, ClientState, Clients}; use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationError}; use crate::ship::room::RoomState; +use crate::ship::items::state::ItemState; use crate::ship::items::ItemManager; use crate::ship::packet::builder::{player_header, player_info}; use std::convert::TryInto; @@ -53,7 +54,7 @@ pub fn add_to_room(_id: ClientId, client: &ClientState, area_client: &AreaClient, leader: &AreaClient, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable, _room_id: RoomId, ) @@ -68,7 +69,7 @@ pub fn add_to_room(_id: ClientId, block: 0, event: 0, padding: 0, - playerinfo: player_info(0x10000, client, area_client, item_manager, level_table), + playerinfo: player_info(0x10000, client, area_client, item_state, level_table), }) } @@ -76,4 +77,4 @@ pub fn build_rare_monster_list(rare_monster_vec: Vec) -> RareMonsterList { RareMonsterList { ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]), } -} \ No newline at end of file +} diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index c9e6987..50be751 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -4,7 +4,7 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients}; use crate::login::login::get_login_status; use crate::entity::gateway::EntityGateway; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; use crate::common::interserver::ShipMessage; #[allow(clippy::too_many_arguments)] @@ -12,7 +12,7 @@ pub async fn validate_login(id: ClientId, pkt: &Login, entity_gateway: &mut EG, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, shipgate_sender: &Option>, ship_name: &str, num_blocks: usize) @@ -30,7 +30,7 @@ pub async fn validate_login(id: ClientId, .clone(); let settings = entity_gateway.get_user_settings_by_user(&user).await?; - item_manager.load_character(entity_gateway, &character).await?; + item_state.load_character(entity_gateway, &character).await?; if let Some(shipgate_sender) = shipgate_sender.as_ref() { shipgate_sender(ShipMessage::AddUser(user.id)); diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index f5f4a09..38968a3 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -15,7 +15,7 @@ use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem}; use crate::ship::items::state::{ItemState, FloorType, FloorItemDetail}; -use crate::ship::items::actions::{pick_up_item, TriggerCreateItem}; +use crate::ship::items::actions::{pick_up_item, withdraw_meseta, deposit_meseta, withdraw_item, deposit_item, TriggerCreateItem}; const BANK_ACTION_DEPOSIT: u8 = 0; const BANK_ACTION_WITHDRAW: u8 = 1; @@ -242,13 +242,12 @@ EG: EntityGateway // item_manager is not mutable in this, but for reasons I don't quite understand it requires the unique access of it to compile here pub async fn send_bank_list(id: ClientId, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - let bank_items = item_manager.get_character_bank(&client.character)?; - let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?; - let bank_items_pkt = builder::message::bank_item_list(bank_items, bank_meseta.0); + let bank = item_state.get_character_bank(&client.character)?; + let bank_items_pkt = builder::message::bank_item_list(bank); Ok(Box::new(vec![(id, SendShipPacket::BankItemList(bank_items_pkt))].into_iter())) } @@ -257,7 +256,7 @@ pub async fn bank_interaction(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -267,42 +266,24 @@ where let other_clients_in_area = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?; let bank_action_pkts = match bank_interaction.action { BANK_ACTION_DEPOSIT => { - let character_meseta = item_manager.get_character_meseta(&client.character.id)?; - let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?; if bank_interaction.item_id == 0xFFFFFFFF { - if character_meseta.0 >= bank_interaction.meseta_amount && (bank_interaction.meseta_amount + bank_meseta.0) <= BANK_MESETA_CAPACITY { - let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?; - character_meseta.0 -= bank_interaction.meseta_amount; - bank_meseta.0 += bank_interaction.meseta_amount; - entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?; - // TODO: BankName - entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?; - } + deposit_meseta(item_state, entity_gateway, &client.character, bank_interaction.meseta_amount).await?; Vec::new() } else { - item_manager.player_deposits_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?; + deposit_item(item_state, entity_gateway, &client.character, &ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32).await?; let player_no_longer_has_item = builder::message::player_no_longer_has_item(area_client, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32); vec![SendShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(player_no_longer_has_item)))] } }, BANK_ACTION_WITHDRAW => { - let character_meseta = item_manager.get_character_meseta(&client.character.id)?; - let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?; if bank_interaction.item_id == 0xFFFFFFFF { - if (bank_meseta.0 >= bank_interaction.meseta_amount) && (character_meseta.0 + bank_interaction.meseta_amount <= INVENTORY_MESETA_CAPACITY) { - let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?; - character_meseta.0 += bank_interaction.meseta_amount; - bank_meseta.0 -= bank_interaction.meseta_amount; - entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?; - // TODO: BankName - entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?; - } + withdraw_meseta(item_state, entity_gateway, &client.character, bank_interaction.meseta_amount).await?; Vec::new() } else { - let item_added_to_inventory = item_manager.player_withdraws_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?; - let item_created = builder::message::create_withdrawn_inventory_item(area_client, item_added_to_inventory)?; + let item_added_to_inventory = withdraw_item(item_state, entity_gateway, &client.character, &ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32).await?; + let item_created = builder::message::create_withdrawn_inventory_item2(area_client, &item_added_to_inventory)?; vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(item_created)))] } }, diff --git a/src/ship/packet/handler/lobby.rs b/src/ship/packet/handler/lobby.rs index 4113929..dd0e6d9 100644 --- a/src/ship/packet/handler/lobby.rs +++ b/src/ship/packet/handler/lobby.rs @@ -6,7 +6,6 @@ use crate::ship::character::{FullCharacterBytesBuilder}; use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationError, RoomId}; use crate::ship::packet; use crate::ship::items::state::ItemState; -use crate::ship::items::ItemManager; use crate::entity::gateway::EntityGateway; use crate::ship::map::MapArea; @@ -52,12 +51,12 @@ pub fn send_player_to_lobby(id: ClientId, _pkt: &CharData, client_location: &mut ClientLocation, clients: &Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result, anyhow::Error> { let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).map_err(|_| ShipError::TooManyClients)?; - let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?; - let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?; + let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, level_table)?; + let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, level_table)?; let neighbors = client_location.get_client_neighbors(id).unwrap(); Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] .into_iter() @@ -70,7 +69,7 @@ pub async fn change_lobby(id: ClientId, requested_lobby: u32, client_location: &mut ClientLocation, clients: &Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, level_table: &CharacterLevelTable, ship_rooms: &mut Rooms, entity_gateway: &mut EG) @@ -87,7 +86,7 @@ pub async fn change_lobby(id: ClientId, if client_location.get_client_neighbors(id)?.is_empty() { ship_rooms[old_room.0] = None; } - item_manager.remove_character_from_room(&client.character); + item_state.remove_character_from_room(&client.character); }, } let leave_lobby = packet::builder::lobby::remove_from_lobby(id, client_location)?; @@ -104,9 +103,9 @@ pub async fn change_lobby(id: ClientId, } } } - item_manager.load_character(entity_gateway, &client.character).await?; - let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?; - let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?; + item_state.load_character(entity_gateway, &client.character).await?; + let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, level_table)?; + let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, level_table)?; let neighbors = client_location.get_client_neighbors(id).unwrap(); Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] .into_iter() diff --git a/src/ship/packet/handler/room.rs b/src/ship/packet/handler/room.rs index 9e89469..96688f5 100644 --- a/src/ship/packet/handler/room.rs +++ b/src/ship/packet/handler/room.rs @@ -6,14 +6,14 @@ use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients}; use crate::ship::location::{ClientLocation, RoomId, RoomLobby, ClientLocationError}; use crate::ship::packet::builder; use crate::ship::room; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; use std::convert::{TryFrom}; pub fn create_room(id: ClientId, create_room: &CreateRoom, client_location: &mut ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, level_table: &CharacterLevelTable, rooms: &mut Rooms) -> Result + Send>, anyhow::Error> { @@ -45,7 +45,7 @@ pub fn create_room(id: ClientId, let mut room = room::RoomState::from_create_room(create_room, client.character.section_id).unwrap(); room.bursting = true; - item_manager.add_character_to_room(room_id, &client.character, area_client); + item_state.add_character_to_room(room_id, &client.character, area_client); let join_room = builder::room::join_room(id, clients, client_location, room_id, &room)?; rooms[room_id.0] = Some(room); @@ -80,7 +80,7 @@ pub fn join_room(id: ClientId, pkt: &MenuSelect, client_location: &mut ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, level_table: &CharacterLevelTable, rooms: &mut Rooms) -> Result + Send>, ShipError> { diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 8cd2fdc..1584074 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -553,10 +553,10 @@ impl ShipServerState { handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await? }, GameMessage::BankRequest(_bank_request) => { - handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_manager).await? + handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_state).await? }, GameMessage::BankInteraction(bank_interaction) => { - handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::ShopRequest(shop_request) => { handler::direct_message::shop_request(id, shop_request, &block.client_location, &mut self.clients, &block.rooms, &self.level_table, &mut self.shops).await? @@ -609,7 +609,7 @@ impl ServerState for ShipServerState { -> Result + Send>, anyhow::Error> { Ok(match pkt { RecvShipPacket::Login(login) => { - Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.shipgate_sender, &self.name, self.blocks.0.len()) + Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_state, &self.shipgate_sender, &self.name, self.blocks.0.len()) .await?.into_iter().map(move |pkt| (id, pkt))) }, RecvShipPacket::QuestDetailRequest(questdetailrequest) => { @@ -632,7 +632,7 @@ impl ServerState for ShipServerState { let select_block = handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_state, &self.level_table)?.into_iter(); Box::new(leave_lobby.chain(select_block)) } - ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?, + ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)?, QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &mut block.rooms)?, _ => unreachable!(), } @@ -654,7 +654,7 @@ impl ServerState for ShipServerState { menu: room_password_req.menu, item: room_password_req.item, }; - handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)? + handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)? } else { Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))].into_iter()) @@ -662,7 +662,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::CharData(chardata) => { let block = self.blocks.with_client(id, &self.clients)?; - Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_manager, &self.level_table)?.into_iter()) + Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state, &self.level_table)?.into_iter()) }, RecvShipPacket::Message(msg) => { self.message(id, msg).await? @@ -676,7 +676,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::CreateRoom(create_room) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)? + handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)? }, RecvShipPacket::RoomNameRequest(_req) => { let block = self.blocks.with_client(id, &self.clients)?; @@ -714,7 +714,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::LobbySelect(pkt) => { let block = self.blocks.with_client(id, &self.clients)?; - Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter()) + Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter()) }, RecvShipPacket::RequestQuestList(rql) => { let block = self.blocks.with_client(id, &self.clients)?; @@ -786,7 +786,7 @@ impl ServerState for ShipServerState { } block.client_location.remove_client_from_area(id); - self.item_manager.remove_character_from_room(&client.character); + self.item_state.remove_character_from_room(&client.character); if let Some(mut client) = self.clients.remove(&id) { client.user.at_ship = false; diff --git a/tests/test_bank.rs b/tests/test_bank.rs index aad05b9..43a4364 100644 --- a/tests/test_bank.rs +++ b/tests/test_bank.rs @@ -844,7 +844,7 @@ async fn test_deposit_too_much_meseta() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -852,7 +852,9 @@ async fn test_deposit_too_much_meseta() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packets.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); @@ -881,7 +883,7 @@ async fn test_deposit_meseta_when_bank_is_maxed() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -889,7 +891,9 @@ async fn test_deposit_meseta_when_bank_is_maxed() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packets.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); @@ -1009,7 +1013,7 @@ async fn test_withdraw_stacked_item() { assert!(packets.len() == 2); assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)})) - if create_item.item_id == 0x10002 + if create_item.item_id == 0x20000 )); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -1146,7 +1150,7 @@ async fn test_withdraw_stacked_item_with_stack_already_in_inventory() { assert!(packets.len() == 2); assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)})) - if create_item.item_id == 0x10000 + if create_item.item_id == 0x20000 )); let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); @@ -1519,7 +1523,7 @@ async fn test_withdraw_too_much_meseta() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packet = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -1527,7 +1531,9 @@ async fn test_withdraw_too_much_meseta() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packet.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); @@ -1556,7 +1562,7 @@ async fn test_withdraw_meseta_inventory_is_maxed() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packet = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -1564,7 +1570,9 @@ async fn test_withdraw_meseta_inventory_is_maxed() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packet.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap();