diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index d47eb7b..e25f1bc 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -1,10 +1,10 @@ use crate::ship::items::ClientItemId; use std::collections::HashMap; use thiserror::Error; -use crate::entity::gateway::EntityGateway; +use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; use crate::entity::item::{ItemDetail, ItemLocation, BankName}; -use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity}; +use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, ItemEntityId, InventoryItemEntity, BankItemEntity}; use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::weapon; use crate::ship::map::MapArea; @@ -17,6 +17,7 @@ use crate::ship::items::bank::*; use crate::ship::items::floor::*; use crate::ship::items::inventory::*; use crate::ship::items::use_tool; +use crate::ship::items::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError}; #[derive(PartialEq, Eq)] pub enum FloorType { @@ -35,6 +36,7 @@ pub enum ItemManagerError { EntityGatewayError, NoSuchItemId(ClientItemId), NoCharacter(CharacterEntityId), + NoRoom(RoomId), CouldNotAddToInventory(ClientItemId), //ItemBelongsToOtherPlayer, Idunnoman, @@ -53,10 +55,36 @@ pub enum ItemManagerError { CannotGetIndividualItem, InvalidSlot(u8, u8), // slots available, slot attempted NoArmorEquipped, - GatewayError(#[from] crate::entity::gateway::GatewayError), + GatewayError(#[from] GatewayError), StackedItemError(Vec), + ItemTransactionAction(Box), + InvalidTrade, } +impl std::convert::From> for ItemManagerError +where + E: std::fmt::Debug + std::marker::Send + std::marker::Sync + std::error::Error + 'static, +{ + fn from(other: TransactionError) -> ItemManagerError { + match other { + TransactionError::Action(err) => { + ItemManagerError::ItemTransactionAction(Box::new(err)) + }, + TransactionError::Commit(err) => { + match err { + TransactionCommitError::Gateway(gw) => { + ItemManagerError::GatewayError(gw) + }, + TransactionCommitError::ItemManager(im) => { + im + } + } + } + } + } +} + + pub struct ItemManager { pub(super) id_counter: u32, @@ -225,80 +253,88 @@ impl ItemManager { pub async fn character_picks_up_item(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId) -> Result { - let local_floor = self.character_floor.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; - let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; - let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; - let shared_floor = self.room_floor.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?; - - let floor_item = local_floor.get_item_handle_by_id(item_id) - .or_else(|| { - shared_floor.get_item_handle_by_id(item_id) - }) - .ok_or(ItemManagerError::NoSuchItemId(item_id))?; - - let trigger_create_item = match floor_item.item() { - Some(FloorItem::Individual(individual_floor_item)) => { - let new_inventory_item = inventory.pick_up_individual_floor_item(individual_floor_item); - match new_inventory_item { - Some((new_inventory_item, _slot)) => { - entity_gateway.change_item_location( - &new_inventory_item.entity_id, - ItemLocation::Inventory { - character_id: character.id, - } - ).await?; - if new_inventory_item.mag().is_some() { - entity_gateway.change_mag_owner(&new_inventory_item.entity_id, character).await?; - } + let it = ItemTransaction::new(&self, (character, item_id)) + .act(|it, (character, item_id)| -> Result<_, ItemManagerError> { + let local_floor = it.manager.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let inventory = it.manager.character_inventory.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let room_id = it.manager.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let shared_floor = it.manager.room_floor.get(room_id).ok_or(ItemManagerError::NoRoom(*room_id))?; + + let floor_item = match local_floor.get_item_by_id(*item_id) { + Some(floor_item) => { + it.action(Box::new(RemoveFromLocalFloor { + character_id: character.id, + item_id: item_id.clone() + })); + floor_item }, None => { - return Err(ItemManagerError::CouldNotAddToInventory(item_id).into()); - }, - } - TriggerCreateItem::Yes - }, - Some(FloorItem::Stacked(stacked_floor_item)) => { - let new_inventory_item = inventory.pick_up_stacked_floor_item(stacked_floor_item); - - match new_inventory_item { - Some((new_inventory_item, _slot)) => { - for entity_id in &new_inventory_item.entity_ids { - entity_gateway.change_item_location( - entity_id, - ItemLocation::Inventory { - character_id: character.id, - } - ).await?; + match shared_floor.get_item_by_id(*item_id) { + Some(floor_item) => { + it.action(Box::new(RemoveFromSharedFloor { + room_id: *room_id, + item_id: item_id.clone() + })); + floor_item + }, + None => { + return Err(ItemManagerError::NoSuchItemId(item_id.clone())).into() + } } + } + }; - if stacked_floor_item.count() != new_inventory_item.count() { - TriggerCreateItem::No + let create_trigger = match floor_item { + FloorItem::Individual(individual_floor_item) => { + if inventory.space_for_individual_item() { + it.action(Box::new(AddIndividualFloorItemToInventory { + character: (**character).clone(), + item: individual_floor_item.clone() + })) } else { - TriggerCreateItem::Yes + return Err(ItemManagerError::CouldNotAddToInventory(*item_id).into()); } + TriggerCreateItem::Yes }, - None => { - return Err(ItemManagerError::CouldNotAddToInventory(item_id).into()); - } - } - }, - Some(FloorItem::Meseta(meseta_floor_item)) => { - if character.meseta >= 999999 { - return Err(ItemManagerError::CouldNotAddToInventory(item_id).into()); - } - character.meseta = std::cmp::min(character.meseta + meseta_floor_item.meseta.0, 999999); - entity_gateway.save_character(character).await?; - TriggerCreateItem::No - }, - None => { - return Err(ItemManagerError::CouldNotAddToInventory(item_id).into()); - } - }; + FloorItem::Stacked(stacked_floor_item) => { + match inventory.space_for_stacked_item(&stacked_floor_item.tool, stacked_floor_item.entity_ids.len()) { + SpaceForStack::Yes(YesThereIsSpace::NewStack) => { + it.action(Box::new(AddStackedFloorItemToInventory { + character_id: character.id, + item: stacked_floor_item.clone() + })); + TriggerCreateItem::Yes + }, + SpaceForStack::Yes(YesThereIsSpace::ExistingStack) => { + it.action(Box::new(AddStackedFloorItemToInventory { + character_id: character.id, + item: stacked_floor_item.clone() + })); + TriggerCreateItem::No + }, + SpaceForStack::No => { + return Err(ItemManagerError::CouldNotAddToInventory(*item_id).into()); + }, + } + }, + FloorItem::Meseta(meseta_floor_item) => { + if character.meseta >= 999999 { + return Err(ItemManagerError::CouldNotAddToInventory(*item_id).into()); + } + it.action(Box::new(AddMesetaFloorItemToInventory { + character: (**character).clone(), + item: meseta_floor_item.clone() + })); - entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; - floor_item.remove_from_floor(); - Ok(trigger_create_item) + TriggerCreateItem::No + }, + }; + Ok(create_trigger) + }); + it.commit(self, entity_gateway) + .await + .map_err(|err| err.into()) } pub async fn enemy_drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&FloorItem, anyhow::Error> { @@ -924,14 +960,93 @@ impl ItemManager { Ok(weapon) } - pub async fn send_items_to_other_player(&mut self, - entity_gateway: &mut EG, - source_character: &CharacterEntity, - dest_character: &CharacterEntity, - items: &Vec) - -> Result<(), anyhow::Error> { - - +struct RemoveFromLocalFloor { + character_id: CharacterEntityId, + item_id: ClientItemId, +} + +#[async_trait::async_trait] +impl ItemAction for RemoveFromLocalFloor { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let local_floor = item_manager.character_floor.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?; + local_floor.remove_item(&self.item_id); + Ok(()) + } +} + + +struct RemoveFromSharedFloor { + room_id: RoomId, + item_id: ClientItemId, +} + +#[async_trait::async_trait] +impl ItemAction for RemoveFromSharedFloor { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let shared_floor = item_manager.room_floor.get_mut(&self.room_id).ok_or(ItemManagerError::NoRoom(self.room_id))?; + shared_floor.remove_item(&self.item_id); + Ok(()) + } +} + + +struct AddIndividualFloorItemToInventory{ + character: CharacterEntity, + item: IndividualFloorItem, +} + +#[async_trait::async_trait] +impl ItemAction for AddIndividualFloorItemToInventory { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let inventory = item_manager.character_inventory.get_mut(&self.character.id).ok_or(ItemManagerError::NoCharacter(self.character.id))?; + let inv_item = inventory.add_individual_floor_item(&self.item); + + entity_gateway.change_item_location( + &self.item.entity_id, + ItemLocation::Inventory { + character_id: self.character.id, + } + ).await?; + + if inv_item.mag().is_some() { + entity_gateway.change_mag_owner(&self.item.entity_id, &self.character).await?; + } + + entity_gateway.set_character_inventory(&self.character.id, &inventory.as_inventory_entity(&self.character.id)).await?; + Ok(()) + } +} + + +struct AddStackedFloorItemToInventory{ + character_id: CharacterEntityId, + item: StackedFloorItem, +} + +#[async_trait::async_trait] +impl ItemAction for AddStackedFloorItemToInventory { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let inventory = item_manager.character_inventory.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?; + inventory.add_stacked_floor_item(&self.item); + + entity_gateway.set_character_inventory(&self.character_id, &inventory.as_inventory_entity(&self.character_id)).await?; + Ok(()) + } +} + + +struct AddMesetaFloorItemToInventory{ + character: CharacterEntity, + item: MesetaFloorItem, +} + +#[async_trait::async_trait] +impl ItemAction for AddMesetaFloorItemToInventory { + async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let mut nchar = self.character.clone(); + nchar.meseta = std::cmp::min(self.character.meseta + self.item.meseta.0, 999999); + entity_gateway.save_character(&nchar).await?; + Ok(()) } }