use std::collections::HashMap; use std::convert::{From, Into, TryFrom, TryInto}; 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 std::path::PathBuf; #[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) -> 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(&mut 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(&mut 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), // TODO: rare_monster_table here feels janky. is there some way to call the the RoomState.rare_monster_table we already created? 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, }) } }