use std::collections::HashMap; use async_std::sync::{Arc, RwLock}; use crate::ship::items::ClientItemId; use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankName}; use futures::future::join_all; 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, } #[derive(Clone)] pub struct ItemState { character_inventory: Arc>>>, character_bank: Arc>>>, character_room: Arc>>, character_floor: Arc>>>, room_floor: Arc>>>, room_item_id_counter: Arc>, } impl Default for ItemState { fn default() -> ItemState { ItemState { character_inventory: Arc::new(RwLock::new(HashMap::new())), character_bank: Arc::new(RwLock::new(HashMap::new())), character_room: Arc::new(RwLock::new(HashMap::new())), character_floor: Arc::new(RwLock::new(HashMap::new())), room_floor: Arc::new(RwLock::new(HashMap::new())), room_item_id_counter: Arc::new(RwLock::new(0x00810000)), } } } impl ItemState { pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result { Ok(self.character_inventory .read() .await .get(&character.id) .ok_or(ItemStateError::NoCharacter(character.id))? .read() .await .clone()) } pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result { Ok(self.character_bank .read() .await .get(&character.id) .ok_or(ItemStateError::NoCharacter(character.id))? .read() .await .clone()) } } impl ItemState { async fn new_item_id(&mut self) -> Result { //self.room_item_id_counter += 1; *self.room_item_id_counter .write() .await += 1; Ok(ClientItemId(*self.room_item_id_counter.read().await)) } 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::, ItemStateError>>()?; 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 = join_all( bank.items.into_iter() .map(|item| { let mut citem_state = self.clone(); async move { Ok(match item { BankItemEntity::Individual(item) => { BankItem { item_id: citem_state.new_item_id().await?, item: BankItemDetail::Individual(IndividualItemDetail { entity_id: item.id, item: item.item, }) } }, BankItemEntity::Stacked(items) => { BankItem { item_id: citem_state.new_item_id().await?, 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::>()) .await .into_iter() .collect::, ItemStateError>>()?; 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 .write() .await .insert(character.id, RwLock::new(inventory_state)); self.character_bank .write() .await .insert(character.id, RwLock::new(bank_state)); Ok(()) } pub async 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(); self.character_inventory .read() .await .get(&character.id) .unwrap() .write() .await .initialize_item_ids(base_inventory_id); //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); self.character_bank .read() .await .get(&character.id) .unwrap() .write() .await .initialize_item_ids(base_bank_id); //if let Some(default_bank) = default_bank { //default_bank.initialize_item_ids(base_bank_id); //} self.character_room .write() .await .insert(character.id, room_id); self.character_floor .write() .await .insert(character.id, RwLock::new(LocalFloor::default())); self.room_floor .write() .await .entry(room_id) .or_insert_with(Default::default); } pub async fn remove_character_from_room(&mut self, character: &CharacterEntity) { self.character_inventory .write() .await .remove(&character.id); self.character_floor .write() .await .remove(&character.id); let removed = { self.character_room.write().await.remove(&character.id) }; if let Some(room) = removed.as_ref() { // TODO: this looks wrong, .all(r != room) maybe? if self.character_room.read().await.iter().any(|(_, r)| r == room) { self.room_floor .write() .await .remove(room); } } } pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), ItemStateError> { let local_floors = self.character_floor .read() .await; let local_floor = local_floors .get(character_id) .ok_or(ItemStateError::NoCharacter(*character_id))? .read() .await; let rooms = self.character_room .read() .await; let room = rooms .get(character_id) .ok_or(ItemStateError::NoCharacter(*character_id))?; let shared_floors = self.room_floor .read() .await; let shared_floor = shared_floors .get(room) .ok_or(ItemStateError::NoCharacter(*character_id))? .read() .await; local_floor.0 .iter() .find(|item| item.item_id == *item_id) .map(|item| (item.clone(), FloorType::Local)) .or_else(|| { shared_floor.0 .iter() .find(|item| item.item_id == *item_id) .map(|item| (item.clone(), 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, //_a: std::marker::PhantomData<&'a ()>, // TODO: remove } impl<'a> ItemStateProxy<'a> { pub async fn commit(self) { async fn copy_back(master: &Arc>>>, proxy: HashMap) where K: Eq + std::hash::Hash, { for (key, value) in proxy { if let Some(element) = master .read() .await .get(&key) { *element .write() .await = value; } } } copy_back(&self.item_state.character_inventory, self.proxied_state.character_inventory).await; copy_back(&self.item_state.character_bank, self.proxied_state.character_bank).await; //copy_back(self.item_state.character_room, self.proxied_state.character_room).await; copy_back(&self.item_state.character_floor, self.proxied_state.character_floor).await; copy_back(&self.item_state.room_floor, self.proxied_state.room_floor).await; /* self.item_state.character_inventory .write() .await .extend(self.proxied_state.character_inventory.clone()); self.item_state.character_bank .write() .await .extend(self.proxied_state.character_bank.clone()); self.item_state.character_room .write() .await .extend(self.proxied_state.character_room.clone()); self.item_state.character_floor .write() .await .extend(self.proxied_state.character_floor.clone()); self.item_state.room_floor .write() .await .extend(self.proxied_state.room_floor); */ /* for (character_id, character_inventory) in self.proxied_state.character_inventory { if let Some(inventory) = self.item_state.character_inventory .read() .await .get(&character_id) { *inventory .write() .await = character_inventory; } } */ } } async fn get_or_clone(master: &Arc>>>, 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()) */ let existing_element = master .read() .await .get(&key) .ok_or_else(|| err(key))? .read() .await .clone(); Ok(proxy.entry(key) .or_insert_with(|| existing_element) .clone()) } impl<'a> ItemStateProxy<'a> { pub fn new(item_state: &'a mut ItemState) -> Self { ItemStateProxy { item_state, proxied_state: Default::default(), //_a: Default::default(), } } pub async 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).await } pub fn set_inventory(&mut self, inventory: InventoryState) { self.proxied_state.character_inventory.insert(inventory.character_id, inventory); } pub async 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).await } pub fn set_bank(&mut self, bank: BankState) { self.proxied_state.character_bank.insert(bank.character_id, bank); } pub async 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)?; let room_id = *self.item_state.character_room.read().await.get(character_id).unwrap(); 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).await?, shared: get_or_clone(&self.item_state.room_floor, &mut self.proxied_state.room_floor, room_id, ItemStateError::NoRoom).await?, }) } pub async 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(); let room_id = *self.item_state.character_room.read().await.get(&floor.character_id).unwrap(); self.proxied_state.character_floor.insert(floor.character_id, floor.local); self.proxied_state.room_floor.insert(room_id, floor.shared); } pub async fn new_item_id(&mut self) -> Result { self.item_state.new_item_id().await } }