use std::collections::{HashMap, BinaryHeap}; use std::cmp::Reverse; use async_std::sync::{Arc, RwLock, Mutex}; use futures::stream::{FuturesOrdered, StreamExt}; use anyhow::Context; use entity::gateway::{EntityGateway, GatewayError}; use entity::character::{CharacterEntity, CharacterEntityId}; use entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankIdentifier}; use entity::item::tool::Tool; use entity::item::weapon::Weapon; use entity::item::mag::Mag; use crate::ship::drops::ItemDrop; use crate::ship::items::ClientItemId; 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}; use crate::ship::location::{AreaClient, RoomId}; #[derive(thiserror::Error, Debug)] pub enum ItemStateError { #[error("character {0} not found")] NoCharacter(CharacterEntityId), #[error("room {0} not found")] NoRoom(RoomId), #[error("inventory item {0} not found")] NoInventoryItem(ClientItemId), #[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 {0}")] 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_weapon_mut(&mut self) -> Option<&mut Weapon> { match &mut self.item { ItemDetail::Weapon(weapon) => Some(weapon), _ => 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, Debug)] struct RoomGemItemIdCounter { inventory: [Arc>; 4], bank: [Arc>; 4], } impl Default for RoomGemItemIdCounter { fn default() -> RoomGemItemIdCounter { RoomGemItemIdCounter { inventory: core::array::from_fn(|gem| Arc::new(Mutex::new(((gem as u32) << 21) | 0x10000))), bank: core::array::from_fn(|gem| Arc::new(Mutex::new(((gem as u32) << 21) | 0x20000))), } } } impl RoomGemItemIdCounter { fn inventory(&self, area_client: &AreaClient) -> Arc> { self.inventory[area_client.local_client.id() as usize].clone() } fn bank(&self, area_client: &AreaClient) -> Arc> { self.bank[area_client.local_client.id() as usize].clone() } } #[derive(Clone, Debug)] pub struct ItemState { character_inventory: Arc>>>, character_bank: Arc>>>, character_room: Arc>>, character_floor: Arc>>>, room_floor: Arc>>>, room_gem_item_ids: 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_gem_item_ids: 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_else(|| 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_else(|| ItemStateError::NoCharacter(character.id))? .read() .await .clone()) } } impl ItemState { async fn new_item_id(&mut self) -> Result { *self.room_item_id_counter .write() .await += 1; Ok(ClientItemId(*self.room_item_id_counter.read().await)) } pub async fn load_character_inventory(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> { let inventory = entity_gateway.get_character_inventory(&character.id).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::, anyhow::Error>>()?; let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let inventory_state = InventoryState { character_id: character.id, item_id_counter: Arc::new(Mutex::new(0)), inventory: Inventory::new(inventory_items), equipped, meseta: character_meseta, }; self.character_inventory .write() .await .insert(character.id, RwLock::new(inventory_state)); Ok(()) } pub async fn load_character_bank(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, bank_identifier: BankIdentifier) -> Result<(), anyhow::Error> { let bank = entity_gateway.get_character_bank(&character.id, &bank_identifier).await?; let bank_items = bank.items .into_iter() .map(|item| { Ok(Reverse(match item { BankItemEntity::Individual(item) => { BankItemDetail::Individual(IndividualItemDetail { entity_id: item.id, item: item.item, }) }, BankItemEntity::Stacked(items) => { 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::, anyhow::Error>>()? .into_iter() .map(|item| { let mut citem_state = self.clone(); async move { Ok(BankItem { item_id: citem_state.new_item_id().await?, item: item.0, }) } }) .collect::>() .collect::>() .await .into_iter() .collect::, anyhow::Error>>()?; let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &bank_identifier).await?; let bank_state = BankState::new(character.id, bank_identifier, Bank::new(bank_items), bank_meseta); self.character_bank .write() .await .insert(character.id, RwLock::new(bank_state)); Ok(()) } pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> { self.load_character_inventory(entity_gateway, character).await?; self.load_character_bank(entity_gateway, character, BankIdentifier::Character).await?; Ok(()) } pub async fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) { let mut base_item_ids = self.room_gem_item_ids .write() .await; let base_item_ids = base_item_ids .entry(room_id) .or_insert_with(RoomGemItemIdCounter::default); self.character_inventory .read() .await .get(&character.id) .unwrap() .write() .await .initialize_item_ids(base_item_ids.inventory(&area_client).clone()) .await; self.character_bank .read() .await .get(&character.id) .unwrap() .write() .await .initialize_item_ids(base_item_ids.bank(&area_client)) .await; 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), anyhow::Error> { let local_floors = self.character_floor .read() .await; let local_floor = local_floors .get(character_id) .ok_or_else(|| ItemStateError::NoCharacter(*character_id))? .read() .await; let rooms = self.character_room .read() .await; let room = rooms .get(character_id) .ok_or_else(||ItemStateError::NoCharacter(*character_id))?; let shared_floors = self.room_floor .read() .await; let shared_floor = shared_floors .get(room) .ok_or_else(||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_else(|| ItemStateError::NoFloorItem(*item_id)) .with_context(|| format!("character {character_id}\nlocal floors: {local_floors:#?}\nshared floors: {shared_floors:#?}")) } } #[derive(Default, Clone)] struct ProxiedItemState { character_inventory: Arc>>, character_bank: Arc>>, //character_room: HashMap, character_floor: Arc>>, room_floor: Arc>>, } #[derive(Clone)] pub struct ItemStateProxy { item_state: ItemState, proxied_state: ProxiedItemState, } impl ItemStateProxy { pub async fn commit(self) { async fn copy_back(master: &Arc>>>, proxy: Arc>>) where K: Eq + std::hash::Hash, V: Clone, { for (key, value) in proxy.lock().await.iter() { if let Some(element) = master .read() .await .get(key) { *element .write() .await = value.clone(); } } } 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; } } async fn get_or_clone(master: &Arc>>>, proxy: &Arc>>, key: K, err: fn(K) -> ItemStateError) -> Result where K: Eq + std::hash::Hash + Copy, V: Clone { let existing_element = master .read() .await .get(&key) .ok_or_else(|| err(key))? .read() .await .clone(); Ok(proxy .lock() .await .entry(key) .or_insert_with(|| existing_element) .clone()) } impl ItemStateProxy { pub fn new(item_state: ItemState) -> Self { ItemStateProxy { item_state, proxied_state: Default::default(), } } pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_inventory, &self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter).await } pub async fn set_inventory(&mut self, inventory: InventoryState) { self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory); } pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_bank, &self.proxied_state.character_bank, *character_id, ItemStateError::NoCharacter).await } pub async fn set_bank(&mut self, bank: BankState) { self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank); } pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result { let room_id = *self.item_state.character_room.read().await.get(character_id) .ok_or_else(|| anyhow::Error::from(ItemStateError::NoCharacter(*character_id))) .with_context(|| format!("character {character_id}\nrooms: {:#?}", self.item_state.character_room))?; Ok(FloorState { character_id: *character_id, local: get_or_clone(&self.item_state.character_floor, &self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter).await .with_context(|| format!("no local_floor state: {character_id:?} {:#?}\nproxy: {:#?}", self.item_state.character_floor, self.proxied_state.character_floor))?, shared: get_or_clone(&self.item_state.room_floor, &self.proxied_state.room_floor, room_id, ItemStateError::NoRoom).await .with_context(|| format!("no share_floor state: {character_id:?} {:#?}\nproxy: {:#?}", self.item_state.room_floor, self.proxied_state.room_floor))?, }) } pub async fn set_floor(&mut self, floor: FloorState) { let room_id = *self.item_state.character_room.read().await.get(&floor.character_id) .ok_or_else(|| anyhow::Error::from(ItemStateError::NoCharacter(floor.character_id))) .with_context(|| format!("character {}\nrooms: {:#?}", floor.character_id, self.item_state.character_room)).unwrap(); self.proxied_state.character_floor.lock().await.insert(floor.character_id, floor.local); self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared); } pub async fn new_item_id(&mut self) -> Result { self.item_state.new_item_id().await } }