use std::collections::HashMap; use std::convert::{From, Into}; use async_std::sync::{Arc, RwLock, RwLockReadGuard}; use futures::future::BoxFuture; use futures::stream::{FuturesOrdered, Stream}; use thiserror::Error; use rand::Rng; use quests::{QuestList, QuestLoadError}; use maps::maps::Maps; use drops::DropTable; use entity::character::SectionID; use entity::room::{RoomEntityId, RoomEntityMode}; use maps::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use maps::area::MapAreaLookup; use maps::Holiday; use location::{MAX_ROOMS, RoomId}; use maps::room::{Episode, Difficulty, RoomMode}; #[derive(Error, Debug)] pub enum RoomError { #[error("invalid room id {0}")] Invalid(u32), } #[derive(Clone)] pub struct Rooms([Arc>>; MAX_ROOMS]); impl Default for Rooms { fn default() -> Rooms { Rooms(core::array::from_fn(|_| Arc::new(RwLock::new(None)))) } } impl Rooms { pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> { *self.0 .get(room_id.0) .ok_or(RoomError::Invalid(room_id.0 as u32))? .write() .await = Some(room); Ok(()) } pub async fn remove(&self, room_id: RoomId) { if let Some(room) = self.0.get(room_id.0) { *room .write() .await = None; } } pub async fn exists(&self, room_id: RoomId) -> bool { match self.0.get(room_id.0) { Some(room) => { room .read() .await .is_some() }, None => false, } } pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result where T: Send, F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a { let room = self.0 .get(room_id.0) .ok_or(RoomError::Invalid(room_id.0 as u32))? .read() .await; if let Some(room) = room.as_ref() { Ok(func(room).await) } else { Err(RoomError::Invalid(room_id.0 as u32).into()) } } pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result where T: Send, F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a { let mut room = self.0 .get(room_id.0) .ok_or(RoomError::Invalid(room_id.0 as u32))? .write() .await; if let Some(room) = room.as_mut() { Ok(func(room).await) } else { Err(RoomError::Invalid(room_id.0 as u32).into()) } } pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard> { self.0 .get(room_id.0) .unwrap() .read() .await } pub fn stream(&self) -> impl Stream>> { self.0 .iter() .map(|room| async move { room .read() .await }) .collect::>() } } #[derive(Debug, Error)] #[error("")] pub enum RoomCreationError { InvalidMode, InvalidEpisode(u8), InvalidDifficulty(u8), CouldNotLoadMonsterStats(RoomMode), CouldNotLoadQuests, } pub enum QuestCategoryType { Standard, Government, } impl From for QuestCategoryType { fn from(f: usize) -> QuestCategoryType { match f { 0 => QuestCategoryType::Standard, _ => QuestCategoryType::Government, } } } impl From for QuestCategoryType { fn from(f: u32) -> QuestCategoryType { match f { 0 => QuestCategoryType::Standard, _ => QuestCategoryType::Government, } } } impl QuestCategoryType { pub fn value(&self) -> usize { match self { QuestCategoryType::Standard => 0, QuestCategoryType::Government => 1, } } } pub struct RoomState { pub room_id: RoomEntityId, pub mode: RoomMode, pub name: String, pub password: [u16; 16], pub maps: Maps, pub drop_table: Box, pub section_id: SectionID, pub random_seed: u32, pub bursting: bool, pub monster_stats: Box>, pub map_areas: MapAreaLookup, pub quest_group: QuestCategoryType, pub standard_quests: QuestList, pub government_quests: QuestList, // enemy info } impl RoomState { pub fn get_flags_for_room_list(&self) -> u8 { let mut flags = 0u8; match self.mode { RoomMode::Single {..} => {flags += 0x04} RoomMode::Battle {..} => {flags += 0x10}, RoomMode::Challenge {..} => {flags += 0x20}, _ => {flags += 0x40}, }; if self.password[0] > 0 { flags += 0x02; } flags } pub fn get_episode_for_room_list(&self) -> u8 { let episode: u8 = self.mode.episode().into(); match self.mode { RoomMode::Single {..} => episode + 0x10, _ => episode + 0x40, } } pub fn get_difficulty_for_room_list(&self) -> u8 { let difficulty: u8 = self.mode.difficulty().into(); difficulty + 0x22 } pub fn quests(&self) -> &QuestList { match self.quest_group { QuestCategoryType::Standard => &self.standard_quests, QuestCategoryType::Government => &self.government_quests, } } #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub fn new (room_id: RoomEntityId, mode: RoomEntityMode, episode: Episode, difficulty: Difficulty, section_id: SectionID, name: String, password: [u16; 16], event: Holiday, map_builder: Arc Maps + Send + Sync>>, drop_table_builder: Arc Box + Send + Sync>>, standard_quest_builder: Arc Result + Send + Sync>>, government_quest_builder: Arc Result + Send + Sync>>, ) -> Result { let mode = match mode { RoomEntityMode::Single => RoomMode::Single { episode, difficulty, }, RoomEntityMode::Multi => RoomMode::Multi { episode, difficulty, }, RoomEntityMode::Challenge => RoomMode::Challenge { episode, }, RoomEntityMode::Battle => RoomMode::Battle { episode, difficulty, }, }; Ok(RoomState { room_id, monster_stats: Box::new(load_monster_stats_table(&mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(mode))?), mode, random_seed: rand::thread_rng().gen(), name, password, maps: map_builder(mode, event), section_id, drop_table: drop_table_builder(episode, difficulty, section_id), bursting: false, map_areas: MapAreaLookup::new(&episode), quest_group: QuestCategoryType::Standard, standard_quests: standard_quest_builder(mode)?, government_quests: government_quest_builder(mode)?, }) } }