use crate::ship::items::ClientItemId; use std::collections::HashMap; use thiserror::Error; use crate::entity::gateway::EntityGateway; 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::tool::{Tool, ToolType}; use crate::entity::item::weapon; use crate::ship::map::MapArea; use crate::ship::ship::ItemDropLocation; use crate::ship::drops::{ItemDrop, ItemDropType}; use crate::ship::location::{AreaClient, RoomId}; use crate::ship::shops::ShopItem; use crate::ship::items::bank::*; use crate::ship::items::floor::*; use crate::ship::items::inventory::*; use crate::ship::items::use_tool; #[derive(PartialEq, Eq)] pub enum FloorType { Local, Shared, } pub enum TriggerCreateItem { Yes, No } #[derive(Error, Debug)] #[error("")] pub enum ItemManagerError { EntityGatewayError, NoSuchItemId(ClientItemId), NoCharacter(CharacterEntityId), CouldNotAddToInventory(ClientItemId), //ItemBelongsToOtherPlayer, Idunnoman, CouldNotSplitItem(ClientItemId), CouldNotDropMeseta, InvalidBankName(BankName), NotEnoughTools(Tool, usize, usize), // have, expected InventoryItemConsumeError(#[from] InventoryItemConsumeError), BankFull, WrongItemType(ClientItemId), UseItemError(#[from] use_tool::UseItemError), CouldNotBuyItem, CouldNotAddBoughtItemToInventory, ItemIdNotInInventory(ClientItemId), CannotGetMutItem, CannotGetIndividualItem, InvalidSlot(u8, u8), // slots available, slot attempted NoArmorEquipped, GatewayError(#[from] crate::entity::gateway::GatewayError), StackedItemError(Vec), } pub struct ItemManager { id_counter: u32, character_inventory: HashMap, //character_bank: HashMap>, character_bank: HashMap, character_floor: HashMap, character_room: HashMap, room_floor: HashMap, room_item_id_counter: HashMap ClientItemId + Send>>, } impl Default for ItemManager { fn default() -> ItemManager { ItemManager { id_counter: 0, character_inventory: HashMap::new(), character_bank: HashMap::new(), character_floor: HashMap::new(), character_room: HashMap::new(), room_floor: HashMap::new(), room_item_id_counter: HashMap::new(), } } } impl ItemManager { pub fn next_global_item_id(&mut self) -> ClientItemId { self.id_counter += 1; ClientItemId(self.id_counter) } // TODO: Result 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); self.character_inventory.insert(character.id, character_inventory); self.character_bank.insert(character.id, character_bank); 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.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_bank(&self, character: &CharacterEntity) -> Result<&CharacterBank, anyhow::Error> { Ok(self.character_bank .get(&character.id) .ok_or(ItemManagerError::NoCharacter(character.id))?) //.get(&BankName("".to_string())) //.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?) } /*pub fn get_character_bank_mut(&mut self, character: &CharacterEntity) -> Result<&CharacterBank, ItemManagerError> { Ok(self.character_bank .get_mut(&character.id) .ok_or(ItemManagerError::NoCharacter(character.id))? .entry(BankName("".to_string())) .or_insert(CharacterBank::new(Vec::new()))) //.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?) }*/ 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 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?; } }, 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?; } if stacked_floor_item.count() != new_inventory_item.count() { TriggerCreateItem::No } else { 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()); } }; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; floor_item.remove_from_floor(); Ok(trigger_create_item) } pub async fn enemy_drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&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.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(), location: ItemLocation::LocalFloor { 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), location: ItemLocation::LocalFloor { 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.change_item_location( &individual_floor_item.entity_id, ItemLocation::SharedFloor { 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.change_item_location( entity_id, ItemLocation::SharedFloor { 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))?; if character.meseta < amount { return Err(ItemManagerError::CouldNotDropMeseta.into()) } character.meseta -= amount; entity_gateway.save_character(character).await?; let item_id = self.room_item_id_counter.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(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, //inventory_item: InventoryItem, item_id: ClientItemId, drop_location: ItemDropLocation, amount: usize) -> Result<&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.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.change_item_location( entity_id, ItemLocation::SharedFloor { 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.change_item_location(&entity_id, ItemLocation::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)?; match bank_item { BankItem::Individual(individual_bank_item) => { entity_gateway.change_item_location(&individual_bank_item.entity_id, ItemLocation::Bank { character_id: character.id, name: BankName("".to_string()) }).await?; }, BankItem::Stacked(stacked_bank_item) => { for entity_id in &stacked_bank_item.entity_ids { entity_gateway.change_item_location(entity_id, ItemLocation::Bank { character_id: character.id, name: BankName("".to_string()) }).await?; } } } 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(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_id: ClientItemId, amount: usize) -> Result<&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)?; match inventory_item { (InventoryItem::Individual(individual_inventory_item), _slot) => { entity_gateway.change_item_location(&individual_inventory_item.entity_id, ItemLocation::Inventory { character_id: character.id, }).await?; }, (InventoryItem::Stacked(stacked_inventory_item), _slot) => { for entity_id in &stacked_inventory_item.entity_ids { entity_gateway.change_item_location(entity_id, ItemLocation::Inventory { character_id: character.id, }).await?; } } } 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.change_item_location(&entity_id, ItemLocation::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(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, shop_item: &(dyn ShopItem + Send + Sync), item_id: ClientItemId, amount: usize) -> Result<&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 { item_entities.push(entity_gateway.create_item(NewItemEntity { location: ItemLocation::Shop, item: ItemDetail::Tool(tool), }).await?); } 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)?; for entity_id in &picked_up_item.entity_ids { entity_gateway.change_item_location(entity_id, ItemLocation::Inventory { character_id: character.id, }).await?; } 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 { location: ItemLocation::Shop, item: ItemDetail::Tool(tool), }).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)?; entity_gateway.change_item_location(&picked_up_item.entity_id, ItemLocation::Inventory { character_id: character.id, }).await?; 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 { location: ItemLocation::Shop, item: item_detail.clone(), }).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)?; entity_gateway.change_item_location(&picked_up_item.entity_id, ItemLocation::Inventory { character_id: character.id, }).await?; 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) } // 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))? .clone(); 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.clone()), }))?; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; Ok(weapon) } }