You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

411 lines
11 KiB

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<RwLock<Option<RoomState>>>; 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<T, anyhow::Error>
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<T, anyhow::Error>
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<Option<RoomState>> {
self.0
.get(room_id.0)
.unwrap()
.read()
.await
}
pub fn stream(&self) -> impl Stream<Item = RwLockReadGuard<Option<RoomState>>> {
self.0
.iter()
.map(|room| async move {
room
.read()
.await
})
.collect::<FuturesOrdered<_>>()
}
}
#[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<u8> for Episode {
type Error = RoomCreationError;
fn try_from(value: u8) -> Result<Episode, RoomCreationError> {
match value {
1 => Ok(Episode::One),
2 => Ok(Episode::Two),
3 => Ok(Episode::Four),
_ => Err(RoomCreationError::InvalidEpisode(value))
}
}
}
impl From<Episode> 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<Episode, RoomCreationError> {
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<u8> for Difficulty {
type Error = RoomCreationError;
fn try_from(value: u8) -> Result<Difficulty, RoomCreationError> {
match value {
0 => Ok(Difficulty::Normal),
1 => Ok(Difficulty::Hard),
2 => Ok(Difficulty::VeryHard),
3 => Ok(Difficulty::Ultimate),
_ => Err(RoomCreationError::InvalidDifficulty(value))
}
}
}
impl From<Difficulty> 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<usize> for QuestCategoryType {
fn from(f: usize) -> QuestCategoryType {
match f {
0 => QuestCategoryType::Standard,
_ => QuestCategoryType::Government,
}
}
}
impl From<u32> 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<DropTable>,
pub section_id: SectionID,
pub random_seed: u32,
pub bursting: bool,
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
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<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>,
) -> Result<RoomState, anyhow::Error> {
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)?,
})
}
}