use std::collections::HashMap; use crate::ship::items::ClientItemId; use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankName}; use crate::ship::location::{AreaClient, RoomId}; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::item::tool::Tool; use crate::entity::item::weapon::Weapon; use crate::entity::item::mag::Mag; use crate::ship::drops::ItemDrop; use crate::ship::items::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState}; use crate::ship::items::floor::{FloorState, FloorItem, LocalFloor, SharedFloor, FloorType}; use crate::ship::items::bank::{Bank, BankState, BankItem, BankItemDetail, BankError}; #[derive(thiserror::Error, Debug)] pub enum ItemStateError { #[error("character {0} not found")] NoCharacter(CharacterEntityId), #[error("room {0} not found")] NoRoom(RoomId), #[error("floor item {0} not found")] NoFloorItem(ClientItemId), #[error("expected {0} to be a tool")] NotATool(ClientItemId), #[error("bank item {0} not found")] NoBankItem(ClientItemId), #[error("inventory error {0}")] InventoryError(#[from] InventoryError), #[error("bank error {0}")] BankError(#[from] BankError), #[error("invalid item id {0}")] InvalidItemId(ClientItemId), #[error("invalid drop? {0:?} (this shouldn't occur)")] BadItemDrop(ItemDrop), #[error("idk")] Dummy, #[error("gateway")] GatewayError(#[from] GatewayError), #[error("tried to remove more meseta than exists: {0}")] InvalidMesetaRemoval(u32), #[error("tried to add meseta when there is no more room")] FullOfMeseta, #[error("stacked item")] StackedItemError(Vec), #[error("apply item {0}")] ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError), #[error("item is not a mag {0}")] NotAMag(ClientItemId), #[error("item is not mag food {0}")] NotMagFood(ClientItemId), #[error("item is not sellable")] ItemNotSellable, #[error("could not modify item")] InvalidModifier, #[error("wrong item type ")] WrongItemType(ClientItemId), } #[derive(Clone, Debug)] pub struct IndividualItemDetail { pub entity_id: ItemEntityId, pub item: ItemDetail, } impl IndividualItemDetail { pub fn as_weapon(&self) -> Option<&Weapon> { match &self.item { ItemDetail::Weapon(weapon) => Some(weapon), _ => None } } pub fn as_mag(&self) -> Option<&Mag> { match &self.item { ItemDetail::Mag(mag) => Some(mag), _ => None } } pub fn as_mag_mut(&mut self) -> Option<&mut Mag> { match &mut self.item { ItemDetail::Mag(mag) => Some(mag), _ => None } } pub fn as_client_bytes(&self) -> [u8; 16] { match &self.item { ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Armor(a) => a.as_bytes(), ItemDetail::Shield(s) => s.as_bytes(), ItemDetail::Unit(u) => u.as_bytes(), ItemDetail::Tool(t) => t.as_individual_bytes(), ItemDetail::TechniqueDisk(d) => d.as_bytes(), ItemDetail::Mag(m) => m.as_bytes(), ItemDetail::ESWeapon(e) => e.as_bytes(), } } } #[derive(Clone, Debug)] pub struct StackedItemDetail { pub entity_ids: Vec, pub tool: Tool, } impl StackedItemDetail { pub fn count(&self) -> usize { self.entity_ids.len() } } #[derive(Clone)] pub enum AddItemResult { NewItem, AddToStack, Meseta, } pub struct ItemState { character_inventory: HashMap, character_bank: HashMap, character_room: HashMap, character_floor: HashMap, room_floor: HashMap, room_item_id_counter: u32, } impl Default for ItemState { fn default() -> ItemState { ItemState { character_inventory: HashMap::new(), character_bank: HashMap::new(), character_room: HashMap::new(), character_floor: HashMap::new(), room_floor: HashMap::new(), room_item_id_counter: 0x00810000, } } } impl ItemState { pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<&InventoryState, ItemStateError> { self.character_inventory.get(&character.id) .ok_or(ItemStateError::NoCharacter(character.id)) } pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&BankState, ItemStateError> { self.character_bank.get(&character.id) .ok_or(ItemStateError::NoCharacter(character.id)) } } impl ItemState { fn new_item_id(&mut self) -> Result { self.room_item_id_counter += 1; Ok(ClientItemId(self.room_item_id_counter)) } pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> { let inventory = entity_gateway.get_character_inventory(&character.id).await?; let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?; let equipped = entity_gateway.get_character_equips(&character.id).await?; let inventory_items = inventory.items.into_iter() .map(|item| -> Result { Ok(match item { InventoryItemEntity::Individual(item) => { InventoryItem { item_id: ClientItemId(0), item: InventoryItemDetail::Individual(IndividualItemDetail { entity_id: item.id, item: item.item, }), } }, InventoryItemEntity::Stacked(items) => { InventoryItem { item_id: ClientItemId(0), item: InventoryItemDetail::Stacked(StackedItemDetail { entity_ids: items.iter().map(|i| i.id).collect(), tool: items.get(0) .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? .item .clone() .as_tool() .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? }) } }, }) }) .collect::, _>>()?; let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let inventory_state = InventoryState { character_id: character.id, item_id_counter: 0, inventory: Inventory::new(inventory_items), equipped, meseta: character_meseta, }; let bank_items = bank.items.into_iter() .map(|item| -> Result { Ok(match item { BankItemEntity::Individual(item) => { BankItem { item_id: self.new_item_id()?, item: BankItemDetail::Individual(IndividualItemDetail { entity_id: item.id, item: item.item, }) } }, BankItemEntity::Stacked(items) => { BankItem { item_id: self.new_item_id()?, item: BankItemDetail::Stacked(StackedItemDetail { entity_ids: items.iter().map(|i| i.id).collect(), tool: items.get(0) .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? .item .clone() .as_tool() .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? }) } }, }) }) .collect::, _>>()?; let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; let bank_state = BankState::new(character.id, BankName("".into()), Bank::new(bank_items), bank_meseta); self.character_inventory.insert(character.id, inventory_state); self.character_bank.insert(character.id, bank_state); Ok(()) } pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) { let base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000; let inventory = self.character_inventory.get_mut(&character.id).unwrap(); inventory.initialize_item_ids(base_inventory_id); let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000; let default_bank = self.character_bank.get_mut(&character.id); if let Some(default_bank ) = default_bank { default_bank.initialize_item_ids(base_bank_id); } self.character_room.insert(character.id, room_id); self.character_floor.insert(character.id, LocalFloor::default()); self.room_floor.entry(room_id).or_insert_with(SharedFloor::default); } pub fn remove_character_from_room(&mut self, character: &CharacterEntity) { self.character_inventory.remove(&character.id); self.character_floor.remove(&character.id); if let Some(room) = self.character_room.remove(&character.id).as_ref() { if self.character_room.iter().any(|(_, r)| r == room) { self.room_floor.remove(room); } } } pub fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(&FloorItem, FloorType), ItemStateError> { let local_floor = self.character_floor.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?; let room = self.character_room.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?; let shared_floor = self.room_floor.get(room).ok_or(ItemStateError::NoCharacter(*character_id))?; local_floor.0 .iter() .find(|item| item.item_id == *item_id) .map(|item| (item, FloorType::Local)) .or_else(|| { shared_floor.0 .iter() .find(|item| item.item_id == *item_id) .map(|item| (item, FloorType::Shared)) }) .ok_or(ItemStateError::NoFloorItem(*item_id)) } } #[derive(Default)] struct ProxiedItemState { character_inventory: HashMap, character_bank: HashMap, character_room: HashMap, character_floor: HashMap, room_floor: HashMap, } pub struct ItemStateProxy<'a> { item_state: &'a mut ItemState, proxied_state: ProxiedItemState, } impl<'a> ItemStateProxy<'a> { pub fn commit(self) { self.item_state.character_inventory.extend(self.proxied_state.character_inventory.clone()); self.item_state.character_bank.extend(self.proxied_state.character_bank.clone()); self.item_state.character_room.extend(self.proxied_state.character_room.clone()); self.item_state.character_floor.extend(self.proxied_state.character_floor.clone()); self.item_state.room_floor.extend(self.proxied_state.room_floor); } } fn get_or_clone(master: &HashMap, proxy: &mut HashMap, key: K, err: fn(K) -> ItemStateError) -> Result where K: Eq + std::hash::Hash + Copy, V: Clone { let existing_element = master.get(&key).ok_or_else(|| err(key))?; Ok(proxy.entry(key) .or_insert_with(|| existing_element.clone()).clone()) } impl<'a> ItemStateProxy<'a> { pub fn new(item_state: &'a mut ItemState) -> Self { ItemStateProxy { item_state, proxied_state: Default::default(), } } pub fn inventory(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter) } pub fn set_inventory(&mut self, inventory: InventoryState) { self.proxied_state.character_inventory.insert(inventory.character_id, inventory); } pub fn bank(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_bank, &mut self.proxied_state.character_bank, *character_id, ItemStateError::NoCharacter) } pub fn set_bank(&mut self, bank: BankState) { self.proxied_state.character_bank.insert(bank.character_id, bank); } pub fn floor(&mut self, character_id: &CharacterEntityId) -> Result { let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, *character_id, ItemStateError::NoCharacter)?; Ok(FloorState { character_id: *character_id, local: get_or_clone(&self.item_state.character_floor, &mut self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter)?, shared: get_or_clone(&self.item_state.room_floor, &mut self.proxied_state.room_floor, room_id, ItemStateError::NoRoom)?, }) } pub fn set_floor(&mut self, floor: FloorState) { let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, floor.character_id, ItemStateError::NoCharacter).unwrap(); self.proxied_state.character_floor.insert(floor.character_id, floor.local); self.proxied_state.room_floor.insert(room_id, floor.shared); } pub fn new_item_id(&mut self) -> Result { self.item_state.new_item_id() } }