use std::collections::HashMap; use std::convert::{From, Into, TryFrom, TryInto}; use std::path::PathBuf; 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::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use crate::ship::map::area::MapAreaLookup; use crate::ship::map::enemy::RareMonsterAppearTable; 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, } 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) -> u8 { match self { RoomMode::Battle {..} => 1, _ => 0, } } pub fn challenge(&self) -> u8 { match self { RoomMode::Challenge {..} => 1, _ => 0, } } pub fn single_player(&self) -> u8 { match self { RoomMode::Single {..} => 1, _ => 0, } } } pub enum QuestCategoryType { Standard, Government, } impl From for QuestCategoryType { fn from(f: usize) -> QuestCategoryType { match f { 0 => QuestCategoryType::Standard, 1 => QuestCategoryType::Government, _ => QuestCategoryType::Standard, // TODO: panic? } } } impl QuestCategoryType { pub fn value(&self) -> usize { match self { QuestCategoryType::Standard => 0, QuestCategoryType::Government => 1, } } } pub struct RoomState { 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 rare_monster_table: Box, pub quest_group: QuestCategoryType, pub quests: Vec, // items on ground // 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 set_quest_group(&mut self, group: usize) { self.quest_group = QuestCategoryType::from(group); } pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID, event: ShipEvent) -> Result { if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::() > 1 { return Err(RoomCreationError::InvalidMode) } let room_mode = if create_room.battle == 1 { RoomMode::Battle { episode: create_room.episode.try_into()?, difficulty: create_room.difficulty.try_into()?, } } else if create_room.challenge == 1 { RoomMode::Challenge { episode: create_room.episode.try_into()?, } } else if create_room.single_player == 1 { RoomMode::Single { episode: create_room.episode.try_into()?, difficulty: create_room.difficulty.try_into()?, } } else { // normal multimode RoomMode::Multi { episode: create_room.episode.try_into()?, difficulty: create_room.difficulty.try_into()?, } }; let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode()); // push the usual set of quests for the selected mode let mut qpath = PathBuf::from("data/quests/bb"); qpath.push(room_mode.episode().to_string()); qpath.push(room_mode.to_string()); qpath.push("quests.toml"); let mut room_quests = Vec::new(); let quest_list = match quests::load_quests(qpath) { Ok(qlist) => qlist, Err(_) => return Err(RoomCreationError::CouldNotLoadQuests), }; room_quests.push(quest_list); // if multiplayer also push the government quests if let RoomMode::Multi {..} = room_mode { qpath = PathBuf::from("data/quests/bb/"); qpath.push(room_mode.episode().to_string()); qpath.push("government/quests.toml"); let quest_list = match quests::load_quests(qpath) { Ok(qlist) => qlist, Err(_) => return Err(RoomCreationError::CouldNotLoadQuests), }; room_quests.push(quest_list); } Ok(RoomState { monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?), mode: room_mode, random_seed: rand::thread_rng().gen(), rare_monster_table: Box::new(rare_monster_table.clone()), name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(), password: create_room.password, maps: Maps::new(room_mode, &rare_monster_table, event), section_id, drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)), bursting: false, map_areas: MapAreaLookup::new(&room_mode.episode()), quest_group: QuestCategoryType::Standard, quests: room_quests, }) } }