use crate::ClientItemId; use std::collections::HashMap; use std::cmp::Ordering; use std::cell::RefCell; use thiserror::Error; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; use crate::entity::item::{ItemDetail, ItemNote, BankName}; use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity}; use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::weapon; use maps::area::MapArea; use crate::ship::ship::ItemDropLocation; use crate::ship::trade::TradeItem; use drops::{ItemDrop, ItemDropType}; use location::{AreaClient, RoomId}; use shops::ShopItem; use crate::ship::packet::handler::trade::{TradeError, OTHER_MESETA_ITEM_ID}; use crate::bank::*; use crate::floor::*; use crate::inventory::*; use crate::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError}; #[derive(PartialEq, Eq)] pub enum FloorType { Local, Shared, } pub enum TriggerCreateItem { Yes, No } #[derive(Error, Debug)] #[error("itemmanager")] pub enum ItemManagerError { #[error("gateway")] EntityGatewayError, #[error("no such item id {0}")] NoSuchItemId(ClientItemId), NoCharacter(CharacterEntityId), NoRoom(RoomId), CouldNotAddToInventory(ClientItemId), //ItemBelongsToOtherPlayer, #[error("shrug")] Idunnoman, CouldNotSplitItem(ClientItemId), #[error("could not drop meseta")] CouldNotDropMeseta, InvalidBankName(BankName), #[error("not enough tools")] NotEnoughTools(Tool, usize, usize), // have, expected InventoryItemConsumeError(#[from] InventoryItemConsumeError), #[error("bank full")] BankFull, WrongItemType(ClientItemId), UseItemError(#[from] use_tool::UseItemError), #[error("could not buy item")] CouldNotBuyItem, #[error("could not add bought item to inventory")] CouldNotAddBoughtItemToInventory, ItemIdNotInInventory(ClientItemId), #[error("cannot get mut item")] CannotGetMutItem, #[error("cannot get individual item")] CannotGetIndividualItem, InvalidSlot(u8, u8), // slots available, slot attempted #[error("no armor equipped")] NoArmorEquipped, GatewayError(#[from] GatewayError), #[error("stacked item")] StackedItemError(Vec), #[error("item not sellable")] ItemNotSellable(InventoryItem), #[error("wallet full")] WalletFull, #[error("invalid sale")] InvalidSale, ItemTransactionAction(Box), #[error("invalid trade")] 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, pub(self) character_inventory: HashMap, pub(self) character_meseta: HashMap, pub(self) bank_meseta: HashMap, //character_bank: HashMap>, pub(self) character_bank: HashMap, pub(self) character_floor: HashMap, pub(self) character_room: HashMap, pub(self) room_floor: HashMap, pub(self) room_item_id_counter: RefCell ClientItemId + Send>>>, } impl Default for ItemManager { fn default() -> ItemManager { ItemManager { id_counter: 0, character_inventory: HashMap::new(), character_meseta: HashMap::new(), bank_meseta: HashMap::new(), character_bank: HashMap::new(), character_floor: HashMap::new(), character_room: HashMap::new(), room_floor: HashMap::new(), room_item_id_counter: RefCell::new(HashMap::new()), } } } impl ItemManager { pub fn next_global_item_id(&mut self) -> ClientItemId { self.id_counter += 1; ClientItemId(self.id_counter) } pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> { 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::Individual(IndividualInventoryItem { entity_id: item.id, item_id: self.next_global_item_id(), item: item.item, }) }, InventoryItemEntity::Stacked(items) => { InventoryItem::Stacked(StackedInventoryItem { entity_ids: items.iter().map(|i| i.id).collect(), item_id: self.next_global_item_id(), tool: items.get(0) .ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))? .item .clone() .as_tool() .ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))? }) }, }) }) .collect::, _>>()?; let character_inventory = CharacterInventory::new(inventory_items, &equipped); let bank_items = bank.items.into_iter() .map(|item| -> Result { Ok(match item { BankItemEntity::Individual(item) => { BankItem::Individual(IndividualBankItem { entity_id: item.id, item_id: self.next_global_item_id(), item: item.item, }) }, BankItemEntity::Stacked(items) => { BankItem::Stacked(StackedBankItem { entity_ids: items.iter().map(|i| i.id).collect(), item_id: self.next_global_item_id(), tool: items.get(0) .ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))? .item .clone() .as_tool() .ok_or_else(|| ItemManagerError::StackedItemError(items.clone()))? }) }, }) }) .collect::, _>>()?; let character_bank = CharacterBank::new(bank_items); let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; self.character_inventory.insert(character.id, character_inventory); self.character_bank.insert(character.id, character_bank); self.character_meseta.insert(character.id, character_meseta); self.bank_meseta.insert(character.id, bank_meseta); 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, RoomFloorItems::default()); self.room_floor.entry(room_id).or_insert_with(RoomFloorItems::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 get_character_inventory(&self, character: &CharacterEntity) -> Result<&CharacterInventory, anyhow::Error> { Ok(self.character_inventory.get(&character.id) .ok_or(ItemManagerError::NoCharacter(character.id))?) } pub fn get_character_inventory_mut<'a>(&'a mut self, character: &CharacterEntity) -> Result<&'a mut CharacterInventory, anyhow::Error> { Ok(self.character_inventory.get_mut(&character.id) .ok_or(ItemManagerError::NoCharacter(character.id))?) } pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&CharacterBank, anyhow::Error> { self.character_bank .get(&character.id) .ok_or_else(|| ItemManagerError::NoCharacter(character.id).into()) } pub fn get_character_meseta(&self, character_id: &CharacterEntityId) -> Result<&Meseta, ItemManagerError> { self.character_meseta.get(character_id) .ok_or(ItemManagerError::NoCharacter(*character_id)) } pub fn get_character_meseta_mut<'a>(&'a mut self, character_id: &CharacterEntityId) -> Result<&'a mut Meseta, ItemManagerError> { self.character_meseta.get_mut(character_id) .ok_or(ItemManagerError::NoCharacter(*character_id)) } pub fn get_bank_meseta(&self, character_id: &CharacterEntityId) -> Result<&Meseta, ItemManagerError> { self.bank_meseta.get(character_id) .ok_or(ItemManagerError::NoCharacter(*character_id)) } pub fn get_bank_meseta_mut<'a>(&'a mut self, character_id: &CharacterEntityId) -> Result<&'a mut Meseta, ItemManagerError> { self.bank_meseta.get_mut(character_id) .ok_or(ItemManagerError::NoCharacter(*character_id)) } pub fn get_character_and_bank_meseta_mut<'a>(&'a mut self, character_id: &CharacterEntityId) -> Result<(&'a mut Meseta, &'a mut Meseta), ItemManagerError> { Ok(( self.character_meseta.get_mut(character_id) .ok_or(ItemManagerError::NoCharacter(*character_id))?, self.bank_meseta.get_mut(character_id) .ok_or(ItemManagerError::NoCharacter(*character_id))? )) } 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_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result<(&FloorItem, FloorType), anyhow::Error> { let local_floor = self.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let room = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let shared_floor = self.room_floor.get(room).ok_or(ItemManagerError::NoCharacter(character.id))?; local_floor.get_item_by_id(item_id).map(|item| (item, FloorType::Local)) .or_else(|| { shared_floor.get_item_by_id(item_id).map(|item| (item, FloorType::Shared)) }) .ok_or_else(|| ItemManagerError::NoSuchItemId(item_id).into()) } pub async fn character_picks_up_item(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId) -> Result { 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 })); floor_item }, None => { 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 })); floor_item }, None => { return Err(ItemManagerError::NoSuchItemId(*item_id)) } } } }; 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 { return Err(ItemManagerError::CouldNotAddToInventory(*item_id)); } TriggerCreateItem::Yes }, 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)); }, } }, FloorItem::Meseta(meseta_floor_item) => { let character_meseta = it.manager.character_meseta.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; if character_meseta.0 >= 999999 { return Err(ItemManagerError::CouldNotAddToInventory(*item_id)); } it.action(Box::new(AddMesetaFloorItemToInventory { character_id: character.id, item: meseta_floor_item.clone() })); 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<'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 { Individual(ItemDetail), Stacked(Tool), Meseta(Meseta) } let item = match item_drop.item { ItemDropType::Weapon(w) => ItemOrMeseta::Individual(ItemDetail::Weapon(w)), ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)), ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)), ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)), ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)), ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)), //ItemDropType::IndividualTool(t) => ItemOrMeseta::Individual(ItemDetail::Tool(t)), //ItemDropType::StackedTool(t, _) => ItemOrMeseta::Stacked(t), ItemDropType::Tool(t) if t.tool.is_stackable() => ItemOrMeseta::Stacked(t), ItemDropType::Tool(t) if !t.tool.is_stackable() => ItemOrMeseta::Individual(ItemDetail::Tool(t)), ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)), _ => unreachable!() // rust isnt smart enough to see that the conditional on tool catches everything }; let item_id = self.room_item_id_counter.borrow_mut().get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); let floor_item = match item { ItemOrMeseta::Individual(item_detail) => { let entity = entity_gateway.create_item(NewItemEntity { item: item_detail.clone(), }).await?; entity_gateway.add_item_note(&entity.id, ItemNote::EnemyDrop { character_id: character.id, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, }).await?; FloorItem::Individual(IndividualFloorItem { entity_id: entity.id, item_id, item: item_detail, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, }) }, ItemOrMeseta::Stacked(tool) => { let entity = entity_gateway.create_item(NewItemEntity { item: ItemDetail::Tool(tool), }).await?; entity_gateway.add_item_note(&entity.id, ItemNote::EnemyDrop { character_id: character.id, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, }).await?; FloorItem::Stacked(StackedFloorItem { entity_ids: vec![entity.id], item_id, tool, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, }) }, ItemOrMeseta::Meseta(meseta) => { FloorItem::Meseta(MesetaFloorItem { item_id, meseta, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, }) }, }; self.character_floor.entry(character.id).or_insert_with(RoomFloorItems::default).add_item(floor_item); // TODO: make these real errors self.character_floor.get(&character.id).ok_or(ItemManagerError::Idunnoman)?.get_item_by_id(item_id).ok_or_else(|| ItemManagerError::Idunnoman.into()) } pub async fn player_drop_item_on_shared_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, //inventory_item: InventoryItem, item_id: ClientItemId, item_drop_location: (MapArea, f32, f32, f32)) -> Result<(), anyhow::Error> { 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 dropped_inventory_item = inventory.take_item_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; match dropped_inventory_item { InventoryItem::Individual(individual_inventory_item) => { let individual_floor_item = shared_floor.drop_individual_inventory_item(individual_inventory_item, item_drop_location); entity_gateway.add_item_note( &individual_floor_item.entity_id, ItemNote::PlayerDrop { character_id: character.id, map_area: item_drop_location.0, x: item_drop_location.1, y: item_drop_location.2, z: item_drop_location.3, } ).await?; }, InventoryItem::Stacked(stacked_inventory_item) => { let stacked_floor_item = shared_floor.drop_stacked_inventory_item(stacked_inventory_item, item_drop_location); for entity_id in &stacked_floor_item.entity_ids { entity_gateway.add_item_note( entity_id, ItemNote::PlayerDrop { character_id: character.id, map_area: item_drop_location.0, x: item_drop_location.1, y: item_drop_location.2, z: item_drop_location.3, } ).await?; } }, } entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(()) } pub async fn player_drops_meseta_on_shared_floor(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, drop_location: ItemDropLocation, amount: u32) -> Result { 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 character_meseta = self.character_meseta.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; if character_meseta.0 < amount { return Err(ItemManagerError::CouldNotDropMeseta.into()) } character_meseta.0 -= amount; entity_gateway.set_character_meseta(&character.id, *character_meseta).await?; let item_id = self.room_item_id_counter.borrow_mut().get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); let floor_item = FloorItem::Meseta(MesetaFloorItem { item_id, meseta: Meseta(amount), map_area: drop_location.map_area, x: drop_location.x, y: 0.0, z: drop_location.z, }); shared_floor.add_item(floor_item.clone()); Ok(floor_item) } pub async fn player_drops_partial_stack_on_shared_floor<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &'a CharacterEntity, //inventory_item: InventoryItem, item_id: ClientItemId, drop_location: ItemDropLocation, amount: usize) -> Result<&'a StackedFloorItem, anyhow::Error> { 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 item_to_split = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; let new_item_id = self.room_item_id_counter.borrow_mut().get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); let stacked_floor_item = shared_floor.drop_partial_stacked_inventory_item(item_to_split, amount, new_item_id, (drop_location.map_area, drop_location.x, 0.0, drop_location.z)) .ok_or(ItemManagerError::CouldNotSplitItem(item_id))?; for entity_id in &stacked_floor_item.entity_ids { entity_gateway.add_item_note( entity_id, ItemNote::PlayerDrop { character_id: character.id, map_area: drop_location.map_area, x: drop_location.x, y: 0.0, z: drop_location.z, } ).await?; } entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(stacked_floor_item) } pub async fn player_consumes_tool(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId, amount: usize) -> Result { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let used_item = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; let consumed_item = used_item.consume(amount)?; if let ItemDetail::TechniqueDisk(tech_disk) = consumed_item.item() { // TODO: validate tech level in packet is in bounds [1..30] character.techs.set_tech(tech_disk.tech, TechLevel(tech_disk.level as u8)); entity_gateway.save_character(character).await?; }; for entity_id in consumed_item.entity_ids() { entity_gateway.add_item_note(&entity_id, ItemNote::Consumed).await?; } entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(consumed_item) } pub async fn player_deposits_item(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_id: ClientItemId, amount: usize) -> Result<(), anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let bank = self.character_bank .get_mut(&character.id) .ok_or(ItemManagerError::NoCharacter(character.id))?; let item_to_deposit = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; let _bank_item = bank.deposit_item(item_to_deposit, amount).ok_or(ItemManagerError::Idunnoman)?; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), &BankName("".into())).await?; Ok(()) } pub async fn player_withdraws_item<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &'a CharacterEntity, item_id: ClientItemId, amount: usize) -> Result<&'a InventoryItem, anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let bank = self.character_bank .get_mut(&character.id) .ok_or(ItemManagerError::NoCharacter(character.id))?; let item_to_withdraw = bank.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; let inventory_item_slot = { let inventory_item = inventory.withdraw_item(item_to_withdraw, amount).ok_or(ItemManagerError::Idunnoman)?; inventory_item.1 }; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), &BankName("".into())).await?; inventory.slot(inventory_item_slot).ok_or_else(|| ItemManagerError::Idunnoman.into()) } pub async fn player_feeds_mag_item(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, mag_id: ClientItemId, tool_id: ClientItemId) -> Result<(), anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let consumed_tool = { let item_to_feed = inventory.get_item_handle_by_id(tool_id).ok_or(ItemManagerError::NoSuchItemId(tool_id))?; item_to_feed.consume(1)? }; let mut mag_handle = inventory.get_item_handle_by_id(mag_id).ok_or(ItemManagerError::NoSuchItemId(mag_id))?; let individual_item = mag_handle.item_mut() .ok_or(ItemManagerError::NoSuchItemId(mag_id))? .individual_mut() .ok_or(ItemManagerError::WrongItemType(mag_id))?; let mag = individual_item .mag_mut() .ok_or(ItemManagerError::WrongItemType(mag_id))?; let consumed_tool_type = match &consumed_tool { ConsumedItem::Stacked(stacked_consumed_item) => stacked_consumed_item.tool.tool, _ => return Err(ItemManagerError::WrongItemType(tool_id).into()) }; mag.feed(consumed_tool_type); for entity_id in consumed_tool.entity_ids() { entity_gateway.feed_mag(&individual_item.entity_id, &entity_id).await?; entity_gateway.add_item_note(&entity_id, ItemNote::FedToMag { mag: individual_item.entity_id, }).await?; } entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(()) } pub async fn use_item(&mut self, used_item: ConsumedItem, entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; match &used_item.item() { ItemDetail::Weapon(_w) => { // something like when items are used to combine/transform them? //_ => {} }, ItemDetail::Tool(t) => { match t.tool { ToolType::PowerMaterial => { use_tool::power_material(entity_gateway, character).await; }, ToolType::MindMaterial => { use_tool::mind_material(entity_gateway, character).await; }, ToolType::EvadeMaterial => { use_tool::evade_material(entity_gateway, character).await; }, ToolType::DefMaterial => { use_tool::def_material(entity_gateway, character).await; }, ToolType::LuckMaterial => { use_tool::luck_material(entity_gateway, character).await; }, ToolType::HpMaterial => { use_tool::hp_material(entity_gateway, character).await; }, ToolType::TpMaterial => { use_tool::tp_material(entity_gateway, character).await; }, ToolType::CellOfMag502 => { use_tool::cell_of_mag_502(entity_gateway, &used_item, inventory).await?; }, ToolType::CellOfMag213 => { use_tool::cell_of_mag_213(entity_gateway, &used_item, inventory).await?; }, ToolType::PartsOfRobochao => { use_tool::parts_of_robochao(entity_gateway, &used_item, inventory).await?; }, ToolType::HeartOfOpaOpa => { use_tool::heart_of_opaopa(entity_gateway, &used_item, inventory).await?; }, ToolType::HeartOfPian => { use_tool::heart_of_pian(entity_gateway, &used_item, inventory).await?; }, ToolType::HeartOfChao=> { use_tool::heart_of_chao(entity_gateway, &used_item, inventory).await?; }, ToolType::HeartOfAngel => { use_tool::heart_of_angel(entity_gateway, &used_item, inventory).await?; }, ToolType::KitOfHamburger => { use_tool::kit_of_hamburger(entity_gateway, &used_item, inventory).await?; }, ToolType::PanthersSpirit => { use_tool::panthers_spirit(entity_gateway, &used_item, inventory).await?; }, ToolType::KitOfMark3 => { use_tool::kit_of_mark3(entity_gateway, &used_item, inventory).await?; }, ToolType::KitOfMasterSystem=> { use_tool::kit_of_master_system(entity_gateway, &used_item, inventory).await?; }, ToolType::KitOfGenesis => { use_tool::kit_of_genesis(entity_gateway, &used_item, inventory).await?; }, ToolType::KitOfSegaSaturn => { use_tool::kit_of_sega_saturn(entity_gateway, &used_item, inventory).await?; }, ToolType::KitOfDreamcast => { use_tool::kit_of_dreamcast(entity_gateway, &used_item, inventory).await?; }, ToolType::Tablet => { use_tool::tablet(entity_gateway, &used_item, inventory).await?; }, ToolType::DragonScale => { use_tool::dragon_scale(entity_gateway, &used_item, inventory).await?; }, ToolType::HeavenStrikerCoat => { use_tool::heaven_striker_coat(entity_gateway, &used_item, inventory).await?; }, ToolType::PioneerParts => { use_tool::pioneer_parts(entity_gateway, &used_item, inventory).await?; }, ToolType::AmitiesMemo => { use_tool::amities_memo(entity_gateway, &used_item, inventory).await?; }, ToolType::HeartOfMorolian => { use_tool::heart_of_morolian(entity_gateway, &used_item, inventory).await?; }, ToolType::RappysBeak => { use_tool::rappys_beak(entity_gateway, &used_item, inventory).await?; }, ToolType::YahoosEngine => { use_tool::yahoos_engine(entity_gateway, &used_item, inventory).await?; }, ToolType::DPhotonCore => { use_tool::d_photon_core(entity_gateway, &used_item, inventory).await?; }, ToolType::LibertaKit => { use_tool::liberta_kit(entity_gateway, &used_item, inventory).await?; }, _ => {} } } _ => {} } entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(()) } pub async fn player_buys_item<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &'a CharacterEntity, shop_item: &'a (dyn ShopItem + Send + Sync), item_id: ClientItemId, amount: usize) -> Result<&'a InventoryItem, anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let item_detail = shop_item.as_item(); let inventory_item = match item_detail { ItemDetail::Tool(tool) => { if tool.is_stackable() { let mut item_entities = Vec::new(); for _ in 0..amount { let item_entity = entity_gateway.create_item(NewItemEntity { item: ItemDetail::Tool(tool), }).await?; entity_gateway.add_item_note(&item_entity.id, ItemNote::BoughtAtShop { character_id: character.id, }).await?; item_entities.push(item_entity); } let floor_item = StackedFloorItem { entity_ids: item_entities.into_iter().map(|i| i.id).collect(), item_id, tool, // TODO: this is gonna choke if I ever require the item being near the player for pickup map_area: MapArea::Pioneer2Ep1, x: 0.0, y: 0.0, z: 0.0, }; let item_id = { let (picked_up_item, _slot) = inventory.pick_up_stacked_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?; picked_up_item.item_id }; inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))? } else { let item_entity = entity_gateway.create_item(NewItemEntity { item: ItemDetail::Tool(tool), }).await?; entity_gateway.add_item_note(&item_entity.id, ItemNote::BoughtAtShop { character_id: character.id, }).await?; let floor_item = IndividualFloorItem { entity_id: item_entity.id, item_id, item: ItemDetail::Tool(tool), // TODO: this is gonna choke if I ever require the item being near the player for pickup map_area: MapArea::Pioneer2Ep1, x: 0.0, y: 0.0, z: 0.0, }; let item_id = { let (picked_up_item, _slot) = inventory.pick_up_individual_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?; picked_up_item.item_id }; inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))? } }, item_detail => { let item_entity = entity_gateway.create_item(NewItemEntity { item: item_detail.clone(), }).await?; entity_gateway.add_item_note(&item_entity.id, ItemNote::BoughtAtShop { character_id: character.id, }).await?; let floor_item = IndividualFloorItem { entity_id: item_entity.id, item_id, item: item_detail, // TODO: this is gonna choke if I ever require the item being near the player for pickup map_area: MapArea::Pioneer2Ep1, x: 0.0, y: 0.0, z: 0.0, }; let item_id = { let (picked_up_item, _slot) = inventory.pick_up_individual_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?; picked_up_item.item_id }; inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))? }, }; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(inventory_item) } pub async fn player_sells_item(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, item_id: ClientItemId, amount: usize) -> Result<(), anyhow::Error> { let character_meseta = self.get_character_meseta(&character.id)?.0; let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let sold_item_handle = inventory.get_item_handle_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; if let Some(item_sold) = sold_item_handle.item() { let unit_price = item_sold.get_sell_price()?; { let total_sale = unit_price * amount as u32; if character_meseta + total_sale <= 999999 { match item_sold { InventoryItem::Individual(i) => { entity_gateway.add_item_note(&i.entity_id, ItemNote::SoldToShop).await?; inventory.remove_by_id(item_id).ok_or(ItemManagerError::NoSuchItemId(item_id))?; }, InventoryItem::Stacked(s) => { match amount.cmp(&s.count()) { Ordering::Less | Ordering::Equal => { sold_item_handle.consume(amount)?; }, Ordering::Greater => return Err(ItemManagerError::InvalidSale.into()), }; }, } entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; let character_meseta = self.get_character_meseta_mut(&character.id)?; character_meseta.0 += total_sale; entity_gateway.set_character_meseta(&character.id, *character_meseta).await?; } else { return Err(ItemManagerError::WalletFull.into()) } } } else { return Err(ItemManagerError::ItemIdNotInInventory(item_id).into()) } Ok(()) } // TODO: check if slot exists before putting units into it pub async fn player_equips_item(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_id: ClientItemId, equip_slot: u8) -> Result<(), anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; inventory.equip(&item_id, equip_slot); entity_gateway.set_character_equips(&character.id, &inventory.as_equipped_entity()).await?; Ok(()) } pub async fn player_unequips_item(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_id: ClientItemId) -> Result<(), anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; inventory.unequip(&item_id); entity_gateway.set_character_equips(&character.id, &inventory.as_equipped_entity()).await?; Ok(()) } pub async fn player_sorts_items(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_ids: [u32; 30]) -> Result<(), anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let sorted_inventory_items: Vec = item_ids.iter() .filter(|&client_item_id| *client_item_id < 0xFFFFFFFF) .map(|&client_item_id| inventory.get_item_by_id(ClientItemId(client_item_id))) .filter(|&x| x.is_some()) .map(|x| x.cloned().unwrap()) .collect(); inventory.set_items(sorted_inventory_items); entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(()) } pub async fn replace_item_with_tekked(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_id: ClientItemId, tek: weapon::WeaponModifier) -> Result { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let item = inventory.remove_by_id(item_id) .ok_or(ItemManagerError::NoSuchItemId(item_id))?; let individual = item .individual() .ok_or(ItemManagerError::WrongItemType(item_id))?; let entity_id = individual.entity_id; let mut weapon = *individual .weapon() .ok_or(ItemManagerError::WrongItemType(item_id))?; weapon.apply_modifier(&tek); entity_gateway.add_weapon_modifier(&entity_id, tek).await?; inventory.add_item(InventoryItem::Individual(IndividualInventoryItem { entity_id, item_id, item: ItemDetail::Weapon(weapon), })); entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(weapon) } pub async fn trade_items(&mut self, entity_gateway: &mut EG, room_id: RoomId, p1: (&AreaClient, &CharacterEntity, &Vec, usize), p2: (&AreaClient, &CharacterEntity, &Vec, usize)) -> Result, anyhow::Error> { let it = ItemTransaction::new(self, (p1, p2, room_id)) .act(|it, (p1, p2, room_id)| -> Result<_, anyhow::Error> { let p1_inventory = it.manager.get_character_inventory(p1.1)?; let p2_inventory = it.manager.get_character_inventory(p2.1)?; [(p2_inventory, p1_inventory, p2.2, p1.2), (p1_inventory, p2_inventory, p1.2, p2.2)].iter() .map(|(src_inventory, dest_inventory, trade_recv, trade_send)| { let item_slots_lost_to_trade = trade_send .iter() .fold(0, |acc, item| { match item { TradeItem::Individual(..) => { acc + 1 }, TradeItem::Stacked(item_id, amount) => { let stacked_inventory_item = try { src_inventory .get_item_by_id(*item_id)? .stacked() }; if let Some(Some(item)) = stacked_inventory_item { if item.count() == *amount { acc + 1 } else { acc } } else { acc } } } }); trade_recv .iter() .try_fold(dest_inventory.count(), |acc, item| { match item { TradeItem::Individual(..) => { if acc >= (30 + item_slots_lost_to_trade) { Err(TradeError::NoInventorySpace) } else { Ok(acc + 1) } }, TradeItem::Stacked(item_id, amount) => { let stacked_inventory_item = src_inventory .get_item_by_id(*item_id) .ok_or(TradeError::InvalidItemId(*item_id))? .stacked() .ok_or(TradeError::InvalidItemId(*item_id))?; match dest_inventory.space_for_stacked_item(&stacked_inventory_item.tool, *amount) { SpaceForStack::Yes(YesThereIsSpace::ExistingStack) => { Ok(acc) }, SpaceForStack::Yes(YesThereIsSpace::NewStack) => { Ok(acc + 1) }, SpaceForStack::No(NoThereIsNotSpace::FullStack) => { Err(TradeError::NoStackSpace) }, SpaceForStack::No(NoThereIsNotSpace::FullInventory) => { if acc >= (30 + item_slots_lost_to_trade) { Err(TradeError::NoInventorySpace) } else { Ok(acc + 1) } }, } } } }) }) .collect::, _>>()?; let trade_items = [(p1, p2, p1_inventory), (p2, p1, p2_inventory)] .map(|(src_client, dest_client, src_inventory)| { src_client.2.iter() .map(|item| -> Option<(Option, Box>)> { match item { TradeItem::Individual(item_id) => { let item = src_inventory.get_item_by_id(*item_id)?.individual()?; let new_item_id = it.manager.room_item_id_counter.borrow_mut().get_mut(room_id)?(); Some(( Some(ItemToTrade { add_to: *dest_client.0, remove_from: *src_client.0, current_item_id: *item_id, new_item_id, item_detail: ItemToTradeDetail::Individual(item.item.clone()) }), Box::new(TradeIndividualItem { src_character_id: src_client.1.id, dest_character_id: dest_client.1.id, current_item_id: *item_id, new_item_id, }), )) }, TradeItem::Stacked(item_id, amount) => { let item = src_inventory.get_item_by_id(*item_id)?.stacked()?; if item.count() < *amount { None } else { let new_item_id = it.manager.room_item_id_counter.borrow_mut().get_mut(room_id)?(); Some(( Some(ItemToTrade { add_to: *dest_client.0, remove_from: *src_client.0, current_item_id: *item_id, new_item_id, item_detail: ItemToTradeDetail::Stacked(item.tool, *amount) }), Box::new(TradeStackedItem { src_character_id: src_client.1.id, dest_character_id: dest_client.1.id, //item_ids: item.entity_ids.iter().cloned().take(*amount).collect(), current_item_id: *item_id, new_item_id, amount: *amount, }), )) } } } }) .chain( if src_client.3 > 0 { Box::new(std::iter::once(Some( (Some(ItemToTrade { add_to: *dest_client.0, remove_from: *src_client.0, current_item_id: OTHER_MESETA_ITEM_ID, new_item_id: OTHER_MESETA_ITEM_ID, item_detail: ItemToTradeDetail::Meseta(src_client.3) }), Box::new(TradeMeseta { src_character_id: src_client.1.id, dest_character_id: dest_client.1.id, amount: src_client.3, }) as Box>)))) as Box> } else { Box::new(std::iter::empty()) as Box> }) .collect::>>() }); if let [Some(p1_trades), Some(p2_trades)] = trade_items { let (p1_item_trades, p1_item_actions): (Vec<_>, Vec<_>) = p1_trades.into_iter().unzip(); let (p2_item_trades, p2_item_actions): (Vec<_>, Vec<_>) = p2_trades.into_iter().unzip(); let item_trades = p1_item_trades.into_iter().flatten().chain(p2_item_trades.into_iter().flatten()); let item_actions = p1_item_actions.into_iter().chain(p2_item_actions.into_iter()); for action in item_actions { it.action(action); } Ok(item_trades.collect()) } else { Err(ItemManagerError::InvalidTrade.into()) } }); it.commit(self, entity_gateway) .await .map_err(|err| err.into()) } } #[derive(Debug)] pub enum ItemToTradeDetail { Individual(ItemDetail), Stacked(Tool, usize), Meseta(usize), } #[derive(Debug)] pub struct ItemToTrade { pub add_to: AreaClient, pub remove_from: AreaClient, pub current_item_id: ClientItemId, pub new_item_id: ClientItemId, pub item_detail: ItemToTradeDetail, } #[derive(Debug)] 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(()) } } #[derive(Debug)] 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(()) } } #[derive(Debug)] 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.add_item_note( &self.item.entity_id, ItemNote::Pickup { 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(()) } } #[derive(Debug)] 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(()) } } #[derive(Debug)] struct AddMesetaFloorItemToInventory{ character_id: CharacterEntityId, 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 character_meseta = item_manager.character_meseta.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?; character_meseta.0 = std::cmp::min(character_meseta.0 + self.item.meseta.0, 999999); entity_gateway.set_character_meseta(&self.character_id, *character_meseta).await?; Ok(()) } } #[derive(Debug)] struct TradeIndividualItem { src_character_id: CharacterEntityId, dest_character_id: CharacterEntityId, current_item_id: ClientItemId, new_item_id: ClientItemId, } #[async_trait::async_trait] impl ItemAction for TradeIndividualItem { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { let src_inventory = item_manager.character_inventory.get_mut(&self.src_character_id).ok_or(ItemManagerError::NoCharacter(self.src_character_id))?; let inventory_item = src_inventory.take_item_by_id(self.current_item_id).ok_or(ItemManagerError::NoSuchItemId(self.current_item_id))?; entity_gateway.set_character_inventory(&self.src_character_id, &src_inventory.as_inventory_entity(&self.src_character_id)).await?; let dest_inventory = item_manager.character_inventory.get_mut(&self.dest_character_id).ok_or(ItemManagerError::NoCharacter(self.dest_character_id))?; dest_inventory.add_item_with_new_item_id(inventory_item, self.new_item_id); entity_gateway.set_character_inventory(&self.dest_character_id, &dest_inventory.as_inventory_entity(&self.dest_character_id)).await?; Ok(()) } } #[derive(Debug)] struct TradeStackedItem { src_character_id: CharacterEntityId, dest_character_id: CharacterEntityId, current_item_id: ClientItemId, new_item_id: ClientItemId, amount: usize, } #[async_trait::async_trait] impl ItemAction for TradeStackedItem { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { let src_inventory = item_manager.character_inventory.get_mut(&self.src_character_id).ok_or(ItemManagerError::NoCharacter(self.src_character_id))?; let inventory_item = src_inventory.take_stacked_item_by_id(self.current_item_id, self.amount).ok_or(ItemManagerError::NoSuchItemId(self.current_item_id))?; entity_gateway.set_character_inventory(&self.src_character_id, &src_inventory.as_inventory_entity(&self.src_character_id)).await?; let dest_inventory = item_manager.character_inventory.get_mut(&self.dest_character_id).ok_or(ItemManagerError::NoCharacter(self.dest_character_id))?; dest_inventory.add_item_with_new_item_id(InventoryItem::Stacked(inventory_item), self.new_item_id); entity_gateway.set_character_inventory(&self.dest_character_id, &dest_inventory.as_inventory_entity(&self.dest_character_id)).await?; Ok(()) } } #[derive(Debug)] struct TradeMeseta { src_character_id: CharacterEntityId, dest_character_id: CharacterEntityId, amount: usize, } #[async_trait::async_trait] impl ItemAction for TradeMeseta { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { { let src_meseta = item_manager.get_character_meseta_mut(&self.src_character_id)?; src_meseta.0 -= self.amount as u32; entity_gateway.set_character_meseta(&self.src_character_id, *src_meseta).await?; } { let dest_meseta = item_manager.get_character_meseta_mut(&self.dest_character_id)?; dest_meseta.0 += self.amount as u32; entity_gateway.set_character_meseta(&self.dest_character_id, *dest_meseta).await?; } Ok(()) } }