use std::collections::HashMap; use std::convert::{From, Into, TryFrom}; use async_std::sync::{Arc, RwLock, RwLockReadGuard}; use futures::future::BoxFuture; use futures::stream::{FuturesOrdered, Stream}; use thiserror::Error; use rand::Rng; use crate::ship::map::Maps; use crate::ship::drops::DropTable; use crate::entity::character::SectionID; use crate::entity::room::{RoomEntityId, RoomEntityMode}; use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use crate::ship::map::area::MapAreaLookup; use crate::ship::quests; use crate::ship::ship::{ShipError, ShipEvent}; use crate::ship::location::{MAX_ROOMS, RoomId}; #[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_else(|| ShipError::InvalidRoom(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_else(|| ShipError::InvalidRoom(room_id.0 as u32))? .read() .await; if let Some(room) = room.as_ref() { Ok(func(room).await) } else { Err(ShipError::InvalidRoom(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_else(|| ShipError::InvalidRoom(room_id.0 as u32))? .write() .await; if let Some(room) = room.as_mut() { Ok(func(room).await) } else { Err(ShipError::InvalidRoom(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, } #[derive(Debug, Copy, Clone, derive_more::Display)] pub enum Episode { #[display(fmt="ep1")] One, #[display(fmt="ep2")] Two, #[display(fmt="ep4")] Four, } #[derive(Debug, Copy, Clone)] pub enum PlayerMode{ Single, Multi, } impl PlayerMode { pub fn value(&self) -> u8 { match self { PlayerMode::Single => 1, PlayerMode::Multi => 0, } } } impl TryFrom for Episode { type Error = RoomCreationError; fn try_from(value: u8) -> Result { match value { 1 => Ok(Episode::One), 2 => Ok(Episode::Two), 3 => Ok(Episode::Four), _ => Err(RoomCreationError::InvalidEpisode(value)) } } } impl From for u8 { fn from(other: Episode) -> u8 { match other { Episode::One => 1, Episode::Two => 2, Episode::Four => 3, } } } impl Episode { pub fn from_quest(value: u8) -> Result { match value { 0 => Ok(Episode::One), 1 => Ok(Episode::Two), 2 => Ok(Episode::Four), _ => Err(RoomCreationError::InvalidEpisode(value)) } } } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] pub enum Difficulty { Normal, Hard, VeryHard, Ultimate, } impl TryFrom for Difficulty { type Error = RoomCreationError; fn try_from(value: u8) -> Result { match value { 0 => Ok(Difficulty::Normal), 1 => Ok(Difficulty::Hard), 2 => Ok(Difficulty::VeryHard), 3 => Ok(Difficulty::Ultimate), _ => Err(RoomCreationError::InvalidDifficulty(value)) } } } impl From for u8 { fn from(other: Difficulty) -> u8 { match other { Difficulty::Normal => 0, Difficulty::Hard => 1, Difficulty::VeryHard => 2, Difficulty::Ultimate => 3, } } } #[derive(Debug, Copy, Clone, derive_more::Display)] pub enum RoomMode { #[display(fmt="single")] Single { episode: Episode, difficulty: Difficulty, }, #[display(fmt="multi")] Multi { episode: Episode, difficulty: Difficulty, }, #[display(fmt="challenge")] Challenge { episode: Episode, }, #[display(fmt="battle")] Battle { episode: Episode, difficulty: Difficulty, } } impl RoomMode { pub fn difficulty(&self) -> Difficulty { match self { RoomMode::Single {difficulty, ..} => *difficulty, RoomMode::Multi {difficulty, ..} => *difficulty, RoomMode::Battle {difficulty, ..} => *difficulty, RoomMode::Challenge {..} => Difficulty::Normal, } } pub fn episode(&self) -> Episode { match self { RoomMode::Single {episode, ..} => *episode, RoomMode::Multi {episode, ..} => *episode, RoomMode::Battle {episode, ..} => *episode, RoomMode::Challenge {episode, ..} => *episode, } } pub fn battle(&self) -> bool { matches!(self, RoomMode::Battle {..}) } pub fn challenge(&self) -> bool { matches!(self, RoomMode::Challenge {..}) } pub fn player_mode(&self) -> PlayerMode { match self { RoomMode::Single {..} => PlayerMode::Single, _ => PlayerMode::Multi, } } } 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: quests::QuestList, pub government_quests: 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) -> &quests::QuestList { match self.quest_group { QuestCategoryType::Standard => &self.standard_quests, QuestCategoryType::Government => &self.government_quests, } } #[allow(clippy::too_many_arguments)] pub fn new (room_id: RoomEntityId, mode: RoomEntityMode, episode: Episode, difficulty: Difficulty, section_id: SectionID, name: String, password: [u16; 16], event: ShipEvent, map_builder: Arc Maps + Send + Sync>>, drop_table_builder: Arc DropTable + 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: Box::new(drop_table_builder(episode, difficulty, section_id)), bursting: false, map_areas: MapAreaLookup::new(&episode), quest_group: QuestCategoryType::Standard, standard_quests: quests::load_standard_quests(mode)?, government_quests: quests::load_government_quests(mode)?, }) } }