remove some redundant files
This commit is contained in:
parent
62ad32a9ba
commit
f0f1b55a38
@ -1,178 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use async_std::sync::{Arc, RwLock, RwLockReadGuard};
|
|
||||||
|
|
||||||
use futures::future::BoxFuture;
|
|
||||||
|
|
||||||
use libpso::packet::ship::*;
|
|
||||||
use libpso::packet::login::Session;
|
|
||||||
|
|
||||||
use networking::serverstate::ClientId;
|
|
||||||
use entity::account::{UserAccountEntity, UserSettingsEntity};
|
|
||||||
use entity::character::CharacterEntity;
|
|
||||||
use entity::item;
|
|
||||||
|
|
||||||
use items;
|
|
||||||
use maps::area::MapArea;
|
|
||||||
use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum ClientError {
|
|
||||||
#[error("not found {0}")]
|
|
||||||
NotFound(ClientId),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct Clients(Arc<RwLock<HashMap<ClientId, RwLock<ClientState>>>>);
|
|
||||||
|
|
||||||
impl Clients {
|
|
||||||
pub async fn add(&mut self, client_id: ClientId, client_state: ClientState) {
|
|
||||||
self.0
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(client_id, RwLock::new(client_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove(&mut self, client_id: &ClientId) -> Option<ClientState> {
|
|
||||||
Some(self.0
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.remove(client_id)?
|
|
||||||
.into_inner())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
|
|
||||||
where
|
|
||||||
T: Send,
|
|
||||||
F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a,
|
|
||||||
{
|
|
||||||
let clients = self.0
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let client = clients
|
|
||||||
.get(&client_id)
|
|
||||||
.ok_or(ClientError::NotFound(client_id))?
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(func(&client).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, anyhow::Error>
|
|
||||||
where
|
|
||||||
T: Send,
|
|
||||||
F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a,
|
|
||||||
{
|
|
||||||
let clients = self.0
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut client_states: [std::mem::MaybeUninit<RwLockReadGuard<ClientState>>; N] = unsafe {
|
|
||||||
std::mem::MaybeUninit::uninit().assume_init()
|
|
||||||
};
|
|
||||||
|
|
||||||
for (cindex, client_id) in client_ids.iter().enumerate() {
|
|
||||||
let c = clients
|
|
||||||
.get(client_id)
|
|
||||||
.ok_or(ClientError::NotFound(*client_id))?
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
client_states[cindex].write(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
let client_states = unsafe {
|
|
||||||
// TODO: this should just be a normal transmute but due to compiler limitations it
|
|
||||||
// does not yet work with const generics
|
|
||||||
// https://github.com/rust-lang/rust/issues/61956
|
|
||||||
std::mem::transmute_copy::<_, [RwLockReadGuard<ClientState>; N]>(&client_states)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(func(client_states).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
|
|
||||||
where
|
|
||||||
T: Send,
|
|
||||||
F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a,
|
|
||||||
{
|
|
||||||
let clients = self.0
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let mut client = clients
|
|
||||||
.get(&client_id)
|
|
||||||
.ok_or(ClientError::NotFound(client_id))?
|
|
||||||
.write()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(func(&mut client).await)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ItemDropLocation {
|
|
||||||
pub map_area: MapArea,
|
|
||||||
pub x: f32,
|
|
||||||
pub z: f32,
|
|
||||||
pub item_id: items::ClientItemId,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LoadingQuest {
|
|
||||||
pub header_bin: Option<QuestHeader>,
|
|
||||||
pub header_dat: Option<QuestHeader>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct ClientState {
|
|
||||||
pub user: UserAccountEntity,
|
|
||||||
pub settings: UserSettingsEntity,
|
|
||||||
pub character: CharacterEntity,
|
|
||||||
_session: Session,
|
|
||||||
//guildcard: GuildCard,
|
|
||||||
pub block: usize,
|
|
||||||
pub item_drop_location: Option<ItemDropLocation>,
|
|
||||||
pub done_loading_quest: bool,
|
|
||||||
pub area: Option<MapArea>,
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub z: f32,
|
|
||||||
pub weapon_shop: Vec<WeaponShopItem>,
|
|
||||||
pub tool_shop: Vec<ToolShopItem>,
|
|
||||||
pub armor_shop: Vec<ArmorShopItem>,
|
|
||||||
pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>,
|
|
||||||
pub character_playtime: chrono::Duration,
|
|
||||||
pub log_on_time: chrono::DateTime<chrono::Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClientState {
|
|
||||||
pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState {
|
|
||||||
let character_playtime = chrono::Duration::seconds(character.playtime as i64);
|
|
||||||
ClientState {
|
|
||||||
user,
|
|
||||||
settings,
|
|
||||||
character,
|
|
||||||
_session: session,
|
|
||||||
block: 0,
|
|
||||||
item_drop_location: None,
|
|
||||||
done_loading_quest: false,
|
|
||||||
area: None,
|
|
||||||
x: 0.0,
|
|
||||||
y: 0.0,
|
|
||||||
z: 0.0,
|
|
||||||
weapon_shop: Vec::new(),
|
|
||||||
tool_shop: Vec::new(),
|
|
||||||
armor_shop: Vec::new(),
|
|
||||||
tek: None,
|
|
||||||
character_playtime,
|
|
||||||
log_on_time: chrono::Utc::now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_playtime(&mut self) {
|
|
||||||
let additional_playtime = chrono::Utc::now() - self.log_on_time;
|
|
||||||
self.character.playtime = (self.character_playtime + additional_playtime).num_seconds() as u32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,178 @@
|
|||||||
pub mod client;
|
use std::collections::HashMap;
|
||||||
|
use async_std::sync::{Arc, RwLock, RwLockReadGuard};
|
||||||
|
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
|
||||||
|
use libpso::packet::ship::*;
|
||||||
|
use libpso::packet::login::Session;
|
||||||
|
|
||||||
|
use networking::serverstate::ClientId;
|
||||||
|
use entity::account::{UserAccountEntity, UserSettingsEntity};
|
||||||
|
use entity::character::CharacterEntity;
|
||||||
|
use entity::item;
|
||||||
|
|
||||||
|
use items;
|
||||||
|
use maps::area::MapArea;
|
||||||
|
use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum ClientError {
|
||||||
|
#[error("not found {0}")]
|
||||||
|
NotFound(ClientId),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct Clients(Arc<RwLock<HashMap<ClientId, RwLock<ClientState>>>>);
|
||||||
|
|
||||||
|
impl Clients {
|
||||||
|
pub async fn add(&mut self, client_id: ClientId, client_state: ClientState) {
|
||||||
|
self.0
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(client_id, RwLock::new(client_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove(&mut self, client_id: &ClientId) -> Option<ClientState> {
|
||||||
|
Some(self.0
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.remove(client_id)?
|
||||||
|
.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a,
|
||||||
|
{
|
||||||
|
let clients = self.0
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let client = clients
|
||||||
|
.get(&client_id)
|
||||||
|
.ok_or(ClientError::NotFound(client_id))?
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(func(&client).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, anyhow::Error>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a,
|
||||||
|
{
|
||||||
|
let clients = self.0
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut client_states: [std::mem::MaybeUninit<RwLockReadGuard<ClientState>>; N] = unsafe {
|
||||||
|
std::mem::MaybeUninit::uninit().assume_init()
|
||||||
|
};
|
||||||
|
|
||||||
|
for (cindex, client_id) in client_ids.iter().enumerate() {
|
||||||
|
let c = clients
|
||||||
|
.get(client_id)
|
||||||
|
.ok_or(ClientError::NotFound(*client_id))?
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
client_states[cindex].write(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client_states = unsafe {
|
||||||
|
// TODO: this should just be a normal transmute but due to compiler limitations it
|
||||||
|
// does not yet work with const generics
|
||||||
|
// https://github.com/rust-lang/rust/issues/61956
|
||||||
|
std::mem::transmute_copy::<_, [RwLockReadGuard<ClientState>; N]>(&client_states)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(func(client_states).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a,
|
||||||
|
{
|
||||||
|
let clients = self.0
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let mut client = clients
|
||||||
|
.get(&client_id)
|
||||||
|
.ok_or(ClientError::NotFound(client_id))?
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(func(&mut client).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ItemDropLocation {
|
||||||
|
pub map_area: MapArea,
|
||||||
|
pub x: f32,
|
||||||
|
pub z: f32,
|
||||||
|
pub item_id: items::ClientItemId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LoadingQuest {
|
||||||
|
pub header_bin: Option<QuestHeader>,
|
||||||
|
pub header_dat: Option<QuestHeader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct ClientState {
|
||||||
|
pub user: UserAccountEntity,
|
||||||
|
pub settings: UserSettingsEntity,
|
||||||
|
pub character: CharacterEntity,
|
||||||
|
_session: Session,
|
||||||
|
//guildcard: GuildCard,
|
||||||
|
pub block: usize,
|
||||||
|
pub item_drop_location: Option<ItemDropLocation>,
|
||||||
|
pub done_loading_quest: bool,
|
||||||
|
pub area: Option<MapArea>,
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
pub weapon_shop: Vec<WeaponShopItem>,
|
||||||
|
pub tool_shop: Vec<ToolShopItem>,
|
||||||
|
pub armor_shop: Vec<ArmorShopItem>,
|
||||||
|
pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>,
|
||||||
|
pub character_playtime: chrono::Duration,
|
||||||
|
pub log_on_time: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientState {
|
||||||
|
pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState {
|
||||||
|
let character_playtime = chrono::Duration::seconds(character.playtime as i64);
|
||||||
|
ClientState {
|
||||||
|
user,
|
||||||
|
settings,
|
||||||
|
character,
|
||||||
|
_session: session,
|
||||||
|
block: 0,
|
||||||
|
item_drop_location: None,
|
||||||
|
done_loading_quest: false,
|
||||||
|
area: None,
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
weapon_shop: Vec::new(),
|
||||||
|
tool_shop: Vec::new(),
|
||||||
|
armor_shop: Vec::new(),
|
||||||
|
tek: None,
|
||||||
|
character_playtime,
|
||||||
|
log_on_time: chrono::Utc::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_playtime(&mut self) {
|
||||||
|
let additional_playtime = chrono::Utc::now() - self.log_on_time;
|
||||||
|
self.character.playtime = (self.character_playtime + additional_playtime).num_seconds() as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub use client::*;
|
|
||||||
|
@ -1,3 +1,676 @@
|
|||||||
pub mod location;
|
#![allow(dead_code, unused_must_use)]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use thiserror::Error;
|
||||||
|
use networking::serverstate::ClientId;
|
||||||
|
|
||||||
pub use location::*;
|
use async_std::sync::{Arc, RwLock};
|
||||||
|
use futures::{stream, StreamExt};
|
||||||
|
use std::pin::pin;
|
||||||
|
|
||||||
|
pub const MAX_ROOMS: usize = 128;
|
||||||
|
|
||||||
|
pub enum AreaType {
|
||||||
|
Room,
|
||||||
|
Lobby,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LobbyId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||||
|
pub struct RoomId(pub usize);
|
||||||
|
|
||||||
|
impl LobbyId {
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
self.0 as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum CreateRoomError {
|
||||||
|
#[error("no open slots")]
|
||||||
|
NoOpenSlots,
|
||||||
|
#[error("client already in area")]
|
||||||
|
ClientInAreaAlready,
|
||||||
|
#[error("join error")]
|
||||||
|
JoinError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum JoinRoomError {
|
||||||
|
#[error("room does not exist")]
|
||||||
|
RoomDoesNotExist,
|
||||||
|
#[error("room is full")]
|
||||||
|
RoomFull,
|
||||||
|
#[error("client already in area")]
|
||||||
|
ClientInAreaAlready,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum JoinLobbyError {
|
||||||
|
#[error("lobby does not exist")]
|
||||||
|
LobbyDoesNotExist,
|
||||||
|
#[error("lobby is full")]
|
||||||
|
LobbyFull,
|
||||||
|
#[error("client already in area")]
|
||||||
|
ClientInAreaAlready,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetAreaError {
|
||||||
|
#[error("not in a room")]
|
||||||
|
NotInRoom,
|
||||||
|
#[error("not in a lobby")]
|
||||||
|
NotInLobby,
|
||||||
|
#[error("get area: invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ClientRemovalError {
|
||||||
|
#[error("client removal: client not in area")]
|
||||||
|
ClientNotInArea,
|
||||||
|
#[error("client removal: invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetClientsError {
|
||||||
|
#[error("invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
#[error("invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetNeighborError {
|
||||||
|
#[error("get neighbor: invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
#[error("get neighbor: invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetLeaderError {
|
||||||
|
#[error("get leader: invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
#[error("get leader: invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
#[error("get leader: client not in area")]
|
||||||
|
NoClientInArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ClientLocationError {
|
||||||
|
#[error("create room error {0}")]
|
||||||
|
CreateRoomError(#[from] CreateRoomError),
|
||||||
|
#[error("join room error {0}")]
|
||||||
|
JoinRoomError(#[from] JoinRoomError),
|
||||||
|
#[error("join lobby error {0}")]
|
||||||
|
JoinLobbyError(#[from] JoinLobbyError),
|
||||||
|
#[error("get area error {0}")]
|
||||||
|
GetAreaError(#[from] GetAreaError),
|
||||||
|
#[error("client removal error {0}")]
|
||||||
|
ClientRemovalError(#[from] ClientRemovalError),
|
||||||
|
#[error("get clients error {0}")]
|
||||||
|
GetClientsError(#[from] GetClientsError),
|
||||||
|
#[error("get neighbor error {0}")]
|
||||||
|
GetNeighborError(#[from] GetNeighborError),
|
||||||
|
#[error("get leader error {0}")]
|
||||||
|
GetLeaderError(#[from] GetLeaderError)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LocalClientId(usize);
|
||||||
|
|
||||||
|
impl LocalClientId {
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
self.0 as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<u8> for LocalClientId {
|
||||||
|
fn eq(&self, other: &u8) -> bool {
|
||||||
|
self.0 == *other as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AreaClient {
|
||||||
|
pub client: ClientId,
|
||||||
|
pub local_client: LocalClientId,
|
||||||
|
time_join: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
struct Lobby([Option<AreaClient>; 12]);
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
struct Room([Option<AreaClient>; 4]);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum RoomLobby {
|
||||||
|
Room(RoomId),
|
||||||
|
Lobby(LobbyId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ClientLocation {
|
||||||
|
lobbies: [Arc<RwLock<Lobby>>; 15],
|
||||||
|
rooms: [Arc<RwLock<Option<Room>>>; MAX_ROOMS],
|
||||||
|
client_location: Arc<RwLock<HashMap<ClientId, RoomLobby>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientLocation {
|
||||||
|
fn default() -> ClientLocation {
|
||||||
|
ClientLocation {
|
||||||
|
lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))),
|
||||||
|
rooms: core::array::from_fn(|_| Arc::new(RwLock::new(None))),
|
||||||
|
client_location: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ClientLocation {
|
||||||
|
pub async fn add_client_to_lobby(&self, id: ClientId, lobby_id: LobbyId) -> Result<(), JoinLobbyError> {
|
||||||
|
{
|
||||||
|
let lobby = self.lobbies
|
||||||
|
.get(lobby_id.0)
|
||||||
|
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
if lobby.0.iter().all(|c| c.is_some()) {
|
||||||
|
return Err(JoinLobbyError::LobbyFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.remove_client_from_area(id).await;
|
||||||
|
|
||||||
|
let mut lobby = self.lobbies
|
||||||
|
.get(lobby_id.0)
|
||||||
|
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
let (index, empty_slot) = lobby.0.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, k)| k.is_none())
|
||||||
|
.ok_or(JoinLobbyError::LobbyFull)?;
|
||||||
|
*empty_slot = Some(AreaClient {
|
||||||
|
client: id,
|
||||||
|
local_client: LocalClientId(index),
|
||||||
|
time_join: SystemTime::now(),
|
||||||
|
});
|
||||||
|
self.client_location
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(id, RoomLobby::Lobby(lobby_id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_client_to_next_available_lobby(&self, id: ClientId, lobby: LobbyId) -> Result<LobbyId, JoinLobbyError> {
|
||||||
|
pin!(stream::iter(0..15)
|
||||||
|
.filter_map(|lobby_index| async move {
|
||||||
|
let new_lobby = LobbyId((lobby.0 + lobby_index) % 15);
|
||||||
|
Some((new_lobby, self.add_client_to_lobby(id, new_lobby).await.ok()?))
|
||||||
|
}))
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.map(|l| l.0)
|
||||||
|
.ok_or(JoinLobbyError::LobbyFull)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
|
||||||
|
let (index, empty_slot) = Box::pin(stream::iter(self.rooms.iter())
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, r)| async {r.read().await.is_none()}))
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(CreateRoomError::NoOpenSlots)?;
|
||||||
|
*empty_slot.write().await = Some(Room([None; 4]));
|
||||||
|
self.add_client_to_room(id, RoomId(index))
|
||||||
|
.await
|
||||||
|
.map_err(|_err| CreateRoomError::JoinError)?;
|
||||||
|
Ok(RoomId(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> {
|
||||||
|
let mut r = self.rooms.get(room.0)
|
||||||
|
.ok_or(JoinRoomError::RoomDoesNotExist)?
|
||||||
|
.as_ref()
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
let r = r.as_mut()
|
||||||
|
.ok_or(JoinRoomError::RoomDoesNotExist)?;
|
||||||
|
let (index, empty_slot) = r.0.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, k)| k.is_none())
|
||||||
|
.ok_or(JoinRoomError::RoomFull)?;
|
||||||
|
*empty_slot = Some(AreaClient {
|
||||||
|
client: id,
|
||||||
|
local_client: LocalClientId(index),
|
||||||
|
time_join: SystemTime::now(),
|
||||||
|
});
|
||||||
|
self.remove_client_from_area(id).await;
|
||||||
|
self.client_location
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(id, RoomLobby::Room(room));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_clients_by_client(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetNeighborError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.collect())
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_client_neighbors(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetNeighborError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| c.client != id)
|
||||||
|
.collect())
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| c.client != id)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_room_leader(&self, room: RoomId) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
let r = self.rooms[room.0]
|
||||||
|
.as_ref()
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.ok_or(GetLeaderError::InvalidArea)?;
|
||||||
|
let mut r = r
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
r.sort_by_key(|k| k.time_join);
|
||||||
|
let c = r.get(0)
|
||||||
|
.ok_or(GetLeaderError::NoClientInArea)?;
|
||||||
|
Ok(**c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_lobby_leader(&self, lobby: LobbyId) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
let l = self.lobbies[lobby.0]
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let mut l = l
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
l.sort_by_key(|k| k.time_join);
|
||||||
|
let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?;
|
||||||
|
Ok(**c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_area_leader(&self, roomlobby: RoomLobby) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
match roomlobby {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
self.get_room_leader(room).await
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
self.get_lobby_leader(lobby).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_leader_by_client(&self, id: ClientId) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetLeaderError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
self.get_room_leader(*room).await
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
self.get_lobby_leader(*lobby).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result<Vec<AreaClient>, GetClientsError> {
|
||||||
|
Ok(self.lobbies
|
||||||
|
.get(lobby.0)
|
||||||
|
.ok_or(GetClientsError::InvalidArea)?
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|client| {
|
||||||
|
client.map(|c| {
|
||||||
|
c
|
||||||
|
})
|
||||||
|
}).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_clients_in_room(&self, room: RoomId) -> Result<Vec<AreaClient>, GetClientsError> {
|
||||||
|
Ok(self.rooms.get(room.0)
|
||||||
|
.ok_or(GetClientsError::InvalidArea)?
|
||||||
|
.as_ref()
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.ok_or(GetClientsError::InvalidArea)?
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|client| {
|
||||||
|
client.map(|c| {
|
||||||
|
c
|
||||||
|
})
|
||||||
|
}).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_local_client(&self, id: ClientId) -> Result<AreaClient, GetClientsError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetClientsError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
self.get_clients_in_room(*room)
|
||||||
|
.await
|
||||||
|
.map_err(|_| GetClientsError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|c| c.client == id)
|
||||||
|
.ok_or(GetClientsError::InvalidClient)
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
self.get_clients_in_lobby(*lobby)
|
||||||
|
.await
|
||||||
|
.map_err(|_| GetClientsError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|c| c.client == id)
|
||||||
|
.ok_or(GetClientsError::InvalidClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_area(&self, id: ClientId) -> Result<RoomLobby, GetAreaError> {
|
||||||
|
self.client_location
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetAreaError::InvalidClient)
|
||||||
|
.map(Clone::clone)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_room(&self, id: ClientId) -> Result<RoomId, GetAreaError> {
|
||||||
|
if let RoomLobby::Room(room) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
|
||||||
|
Ok(*room)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(GetAreaError::NotInRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_lobby(&self, id: ClientId) -> Result<LobbyId, GetAreaError> {
|
||||||
|
if let RoomLobby::Lobby(lobby) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
|
||||||
|
Ok(*lobby)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(GetAreaError::NotInLobby)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_client_from_area(&self, id: ClientId) -> Result<(), ClientRemovalError> {
|
||||||
|
fn remove_client<const N: usize>(id: ClientId, client_list : &mut [Option<AreaClient>; N]) {
|
||||||
|
client_list
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|client| {
|
||||||
|
client.map_or(false, |c| {
|
||||||
|
c.client == id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.for_each(|client| {
|
||||||
|
*client = None
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(ClientRemovalError::ClientNotInArea)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
let mut r = self.rooms.get(room.0)
|
||||||
|
.ok_or(ClientRemovalError::InvalidArea)?
|
||||||
|
.as_ref()
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
if let Some(r) = r.as_mut() {
|
||||||
|
remove_client(id, &mut r.0)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Err(ClientRemovalError::InvalidArea)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
remove_client(id, &mut self.lobbies[lobby.0].write().await.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_lobby() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(13), LobbyId(1)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(14), LobbyId(0)).await.unwrap();
|
||||||
|
|
||||||
|
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.into_iter().flatten().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(12), LocalClientId(0)),
|
||||||
|
(ClientId(14), LocalClientId(1)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_full_lobby() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
|
||||||
|
}
|
||||||
|
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await == Err(JoinLobbyError::LobbyFull));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_next_available_lobby() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for lobby in 1..4 {
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await == Ok(LobbyId(4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_to_lobby_when_all_are_full() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for lobby in 0..15 {
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await, Err(JoinLobbyError::LobbyFull));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_new_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
assert!(cl.create_new_room(ClientId(12)).await == Ok(RoomId(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(12)).await.unwrap();
|
||||||
|
assert!(cl.add_client_to_room(ClientId(234), room).await == Ok(()));
|
||||||
|
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(12), LocalClientId(0)),
|
||||||
|
(ClientId(234), LocalClientId(1)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_no_new_room_slots() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
for i in 0..128 {
|
||||||
|
cl.create_new_room(ClientId(i)).await;
|
||||||
|
}
|
||||||
|
assert!(cl.create_new_room(ClientId(234)).await == Err(CreateRoomError::NoOpenSlots));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_joining_full_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(0)).await.unwrap();
|
||||||
|
assert!(cl.add_client_to_room(ClientId(1), room).await == Ok(()));
|
||||||
|
assert!(cl.add_client_to_room(ClientId(2), room).await == Ok(()));
|
||||||
|
assert!(cl.add_client_to_room(ClientId(3), room).await == Ok(()));
|
||||||
|
assert!(cl.add_client_to_room(ClientId(234), room).await == Err(JoinRoomError::RoomFull));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_adding_client_to_room_removes_from_lobby() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
|
||||||
|
|
||||||
|
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
||||||
|
assert!(cl.add_client_to_room(ClientId(93), room).await == Ok(()));
|
||||||
|
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(23), LocalClientId(1)),
|
||||||
|
(ClientId(12), LocalClientId(3)),
|
||||||
|
]);
|
||||||
|
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(51), LocalClientId(0)),
|
||||||
|
(ClientId(93), LocalClientId(1)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_getting_neighbors() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
|
||||||
|
|
||||||
|
assert!(cl.get_client_neighbors(ClientId(23)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(93), LocalClientId(0)),
|
||||||
|
(ClientId(51), LocalClientId(2)),
|
||||||
|
(ClientId(12), LocalClientId(3)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_failing_to_join_lobby_does_not_remove_from_current_area() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
|
||||||
|
}
|
||||||
|
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(1)).await.is_ok());
|
||||||
|
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await.is_err());
|
||||||
|
assert_eq!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().len(), 12);
|
||||||
|
assert_eq!(
|
||||||
|
cl.get_clients_in_lobby(LobbyId(1)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>(),
|
||||||
|
vec![(ClientId(99), LocalClientId(0))]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_get_leader() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
|
||||||
|
|
||||||
|
assert!(cl.get_leader_by_client(ClientId(51)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_remove_client_from_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(93), room).await;
|
||||||
|
cl.add_client_to_room(ClientId(23), room).await;
|
||||||
|
cl.remove_client_from_area(ClientId(51)).await;
|
||||||
|
cl.add_client_to_room(ClientId(12), room).await;
|
||||||
|
|
||||||
|
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(12), LocalClientId(0)),
|
||||||
|
(ClientId(93), LocalClientId(1)),
|
||||||
|
(ClientId(23), LocalClientId(2)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_leader_changes_on_leader_leaving() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(93), room).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(23), room).await.unwrap();
|
||||||
|
cl.remove_client_from_area(ClientId(51)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(12), room).await.unwrap();
|
||||||
|
cl.remove_client_from_area(ClientId(23)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(99), room).await.unwrap();
|
||||||
|
|
||||||
|
assert!(cl.get_leader_by_client(ClientId(12)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,676 +0,0 @@
|
|||||||
#![allow(dead_code, unused_must_use)]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use thiserror::Error;
|
|
||||||
use networking::serverstate::ClientId;
|
|
||||||
|
|
||||||
use async_std::sync::{Arc, RwLock};
|
|
||||||
use futures::{stream, StreamExt};
|
|
||||||
use std::pin::pin;
|
|
||||||
|
|
||||||
pub const MAX_ROOMS: usize = 128;
|
|
||||||
|
|
||||||
pub enum AreaType {
|
|
||||||
Room,
|
|
||||||
Lobby,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct LobbyId(pub usize);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
|
||||||
pub struct RoomId(pub usize);
|
|
||||||
|
|
||||||
impl LobbyId {
|
|
||||||
pub fn id(&self) -> u8 {
|
|
||||||
self.0 as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum CreateRoomError {
|
|
||||||
#[error("no open slots")]
|
|
||||||
NoOpenSlots,
|
|
||||||
#[error("client already in area")]
|
|
||||||
ClientInAreaAlready,
|
|
||||||
#[error("join error")]
|
|
||||||
JoinError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum JoinRoomError {
|
|
||||||
#[error("room does not exist")]
|
|
||||||
RoomDoesNotExist,
|
|
||||||
#[error("room is full")]
|
|
||||||
RoomFull,
|
|
||||||
#[error("client already in area")]
|
|
||||||
ClientInAreaAlready,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum JoinLobbyError {
|
|
||||||
#[error("lobby does not exist")]
|
|
||||||
LobbyDoesNotExist,
|
|
||||||
#[error("lobby is full")]
|
|
||||||
LobbyFull,
|
|
||||||
#[error("client already in area")]
|
|
||||||
ClientInAreaAlready,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum GetAreaError {
|
|
||||||
#[error("not in a room")]
|
|
||||||
NotInRoom,
|
|
||||||
#[error("not in a lobby")]
|
|
||||||
NotInLobby,
|
|
||||||
#[error("get area: invalid client")]
|
|
||||||
InvalidClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ClientRemovalError {
|
|
||||||
#[error("client removal: client not in area")]
|
|
||||||
ClientNotInArea,
|
|
||||||
#[error("client removal: invalid area")]
|
|
||||||
InvalidArea,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum GetClientsError {
|
|
||||||
#[error("invalid client")]
|
|
||||||
InvalidClient,
|
|
||||||
#[error("invalid area")]
|
|
||||||
InvalidArea,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum GetNeighborError {
|
|
||||||
#[error("get neighbor: invalid client")]
|
|
||||||
InvalidClient,
|
|
||||||
#[error("get neighbor: invalid area")]
|
|
||||||
InvalidArea,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum GetLeaderError {
|
|
||||||
#[error("get leader: invalid client")]
|
|
||||||
InvalidClient,
|
|
||||||
#[error("get leader: invalid area")]
|
|
||||||
InvalidArea,
|
|
||||||
#[error("get leader: client not in area")]
|
|
||||||
NoClientInArea,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ClientLocationError {
|
|
||||||
#[error("create room error {0}")]
|
|
||||||
CreateRoomError(#[from] CreateRoomError),
|
|
||||||
#[error("join room error {0}")]
|
|
||||||
JoinRoomError(#[from] JoinRoomError),
|
|
||||||
#[error("join lobby error {0}")]
|
|
||||||
JoinLobbyError(#[from] JoinLobbyError),
|
|
||||||
#[error("get area error {0}")]
|
|
||||||
GetAreaError(#[from] GetAreaError),
|
|
||||||
#[error("client removal error {0}")]
|
|
||||||
ClientRemovalError(#[from] ClientRemovalError),
|
|
||||||
#[error("get clients error {0}")]
|
|
||||||
GetClientsError(#[from] GetClientsError),
|
|
||||||
#[error("get neighbor error {0}")]
|
|
||||||
GetNeighborError(#[from] GetNeighborError),
|
|
||||||
#[error("get leader error {0}")]
|
|
||||||
GetLeaderError(#[from] GetLeaderError)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct LocalClientId(usize);
|
|
||||||
|
|
||||||
impl LocalClientId {
|
|
||||||
pub fn id(&self) -> u8 {
|
|
||||||
self.0 as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<u8> for LocalClientId {
|
|
||||||
fn eq(&self, other: &u8) -> bool {
|
|
||||||
self.0 == *other as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct AreaClient {
|
|
||||||
pub client: ClientId,
|
|
||||||
pub local_client: LocalClientId,
|
|
||||||
time_join: SystemTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
struct Lobby([Option<AreaClient>; 12]);
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
struct Room([Option<AreaClient>; 4]);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum RoomLobby {
|
|
||||||
Room(RoomId),
|
|
||||||
Lobby(LobbyId),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ClientLocation {
|
|
||||||
lobbies: [Arc<RwLock<Lobby>>; 15],
|
|
||||||
rooms: [Arc<RwLock<Option<Room>>>; MAX_ROOMS],
|
|
||||||
client_location: Arc<RwLock<HashMap<ClientId, RoomLobby>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ClientLocation {
|
|
||||||
fn default() -> ClientLocation {
|
|
||||||
ClientLocation {
|
|
||||||
lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))),
|
|
||||||
rooms: core::array::from_fn(|_| Arc::new(RwLock::new(None))),
|
|
||||||
client_location: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl ClientLocation {
|
|
||||||
pub async fn add_client_to_lobby(&self, id: ClientId, lobby_id: LobbyId) -> Result<(), JoinLobbyError> {
|
|
||||||
{
|
|
||||||
let lobby = self.lobbies
|
|
||||||
.get(lobby_id.0)
|
|
||||||
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
if lobby.0.iter().all(|c| c.is_some()) {
|
|
||||||
return Err(JoinLobbyError::LobbyFull);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.remove_client_from_area(id).await;
|
|
||||||
|
|
||||||
let mut lobby = self.lobbies
|
|
||||||
.get(lobby_id.0)
|
|
||||||
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
|
|
||||||
.write()
|
|
||||||
.await;
|
|
||||||
let (index, empty_slot) = lobby.0.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, k)| k.is_none())
|
|
||||||
.ok_or(JoinLobbyError::LobbyFull)?;
|
|
||||||
*empty_slot = Some(AreaClient {
|
|
||||||
client: id,
|
|
||||||
local_client: LocalClientId(index),
|
|
||||||
time_join: SystemTime::now(),
|
|
||||||
});
|
|
||||||
self.client_location
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(id, RoomLobby::Lobby(lobby_id));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_client_to_next_available_lobby(&self, id: ClientId, lobby: LobbyId) -> Result<LobbyId, JoinLobbyError> {
|
|
||||||
pin!(stream::iter(0..15)
|
|
||||||
.filter_map(|lobby_index| async move {
|
|
||||||
let new_lobby = LobbyId((lobby.0 + lobby_index) % 15);
|
|
||||||
Some((new_lobby, self.add_client_to_lobby(id, new_lobby).await.ok()?))
|
|
||||||
}))
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.map(|l| l.0)
|
|
||||||
.ok_or(JoinLobbyError::LobbyFull)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
|
|
||||||
let (index, empty_slot) = Box::pin(stream::iter(self.rooms.iter())
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, r)| async {r.read().await.is_none()}))
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.ok_or(CreateRoomError::NoOpenSlots)?;
|
|
||||||
*empty_slot.write().await = Some(Room([None; 4]));
|
|
||||||
self.add_client_to_room(id, RoomId(index))
|
|
||||||
.await
|
|
||||||
.map_err(|_err| CreateRoomError::JoinError)?;
|
|
||||||
Ok(RoomId(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> {
|
|
||||||
let mut r = self.rooms.get(room.0)
|
|
||||||
.ok_or(JoinRoomError::RoomDoesNotExist)?
|
|
||||||
.as_ref()
|
|
||||||
.write()
|
|
||||||
.await;
|
|
||||||
let r = r.as_mut()
|
|
||||||
.ok_or(JoinRoomError::RoomDoesNotExist)?;
|
|
||||||
let (index, empty_slot) = r.0.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, k)| k.is_none())
|
|
||||||
.ok_or(JoinRoomError::RoomFull)?;
|
|
||||||
*empty_slot = Some(AreaClient {
|
|
||||||
client: id,
|
|
||||||
local_client: LocalClientId(index),
|
|
||||||
time_join: SystemTime::now(),
|
|
||||||
});
|
|
||||||
self.remove_client_from_area(id).await;
|
|
||||||
self.client_location
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(id, RoomLobby::Room(room));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_all_clients_by_client(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
|
|
||||||
let area = self.client_location
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let area = area
|
|
||||||
.get(&id)
|
|
||||||
.ok_or(GetNeighborError::InvalidClient)?;
|
|
||||||
match area {
|
|
||||||
RoomLobby::Room(room) => {
|
|
||||||
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
|
|
||||||
.into_iter()
|
|
||||||
.collect())
|
|
||||||
},
|
|
||||||
RoomLobby::Lobby(lobby) => {
|
|
||||||
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
|
|
||||||
.into_iter()
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_client_neighbors(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
|
|
||||||
let area = self.client_location
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let area = area
|
|
||||||
.get(&id)
|
|
||||||
.ok_or(GetNeighborError::InvalidClient)?;
|
|
||||||
match area {
|
|
||||||
RoomLobby::Room(room) => {
|
|
||||||
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|c| c.client != id)
|
|
||||||
.collect())
|
|
||||||
},
|
|
||||||
RoomLobby::Lobby(lobby) => {
|
|
||||||
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|c| c.client != id)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_room_leader(&self, room: RoomId) -> Result<AreaClient, GetLeaderError> {
|
|
||||||
let r = self.rooms[room.0]
|
|
||||||
.as_ref()
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.ok_or(GetLeaderError::InvalidArea)?;
|
|
||||||
let mut r = r
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
r.sort_by_key(|k| k.time_join);
|
|
||||||
let c = r.get(0)
|
|
||||||
.ok_or(GetLeaderError::NoClientInArea)?;
|
|
||||||
Ok(**c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_lobby_leader(&self, lobby: LobbyId) -> Result<AreaClient, GetLeaderError> {
|
|
||||||
let l = self.lobbies[lobby.0]
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let mut l = l
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
l.sort_by_key(|k| k.time_join);
|
|
||||||
let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?;
|
|
||||||
Ok(**c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_area_leader(&self, roomlobby: RoomLobby) -> Result<AreaClient, GetLeaderError> {
|
|
||||||
match roomlobby {
|
|
||||||
RoomLobby::Room(room) => {
|
|
||||||
self.get_room_leader(room).await
|
|
||||||
},
|
|
||||||
RoomLobby::Lobby(lobby) => {
|
|
||||||
self.get_lobby_leader(lobby).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_leader_by_client(&self, id: ClientId) -> Result<AreaClient, GetLeaderError> {
|
|
||||||
let area = self.client_location
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let area = area
|
|
||||||
.get(&id)
|
|
||||||
.ok_or(GetLeaderError::InvalidClient)?;
|
|
||||||
match area {
|
|
||||||
RoomLobby::Room(room) => {
|
|
||||||
self.get_room_leader(*room).await
|
|
||||||
},
|
|
||||||
RoomLobby::Lobby(lobby) => {
|
|
||||||
self.get_lobby_leader(*lobby).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result<Vec<AreaClient>, GetClientsError> {
|
|
||||||
Ok(self.lobbies
|
|
||||||
.get(lobby.0)
|
|
||||||
.ok_or(GetClientsError::InvalidArea)?
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.filter_map(|client| {
|
|
||||||
client.map(|c| {
|
|
||||||
c
|
|
||||||
})
|
|
||||||
}).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_clients_in_room(&self, room: RoomId) -> Result<Vec<AreaClient>, GetClientsError> {
|
|
||||||
Ok(self.rooms.get(room.0)
|
|
||||||
.ok_or(GetClientsError::InvalidArea)?
|
|
||||||
.as_ref()
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.ok_or(GetClientsError::InvalidArea)?
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.filter_map(|client| {
|
|
||||||
client.map(|c| {
|
|
||||||
c
|
|
||||||
})
|
|
||||||
}).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_local_client(&self, id: ClientId) -> Result<AreaClient, GetClientsError> {
|
|
||||||
let area = self.client_location
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let area = area
|
|
||||||
.get(&id)
|
|
||||||
.ok_or(GetClientsError::InvalidClient)?;
|
|
||||||
match area {
|
|
||||||
RoomLobby::Room(room) => {
|
|
||||||
self.get_clients_in_room(*room)
|
|
||||||
.await
|
|
||||||
.map_err(|_| GetClientsError::InvalidArea)?
|
|
||||||
.into_iter()
|
|
||||||
.find(|c| c.client == id)
|
|
||||||
.ok_or(GetClientsError::InvalidClient)
|
|
||||||
},
|
|
||||||
RoomLobby::Lobby(lobby) => {
|
|
||||||
self.get_clients_in_lobby(*lobby)
|
|
||||||
.await
|
|
||||||
.map_err(|_| GetClientsError::InvalidArea)?
|
|
||||||
.into_iter()
|
|
||||||
.find(|c| c.client == id)
|
|
||||||
.ok_or(GetClientsError::InvalidClient)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_area(&self, id: ClientId) -> Result<RoomLobby, GetAreaError> {
|
|
||||||
self.client_location
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.get(&id)
|
|
||||||
.ok_or(GetAreaError::InvalidClient)
|
|
||||||
.map(Clone::clone)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_room(&self, id: ClientId) -> Result<RoomId, GetAreaError> {
|
|
||||||
if let RoomLobby::Room(room) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
|
|
||||||
Ok(*room)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Err(GetAreaError::NotInRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_lobby(&self, id: ClientId) -> Result<LobbyId, GetAreaError> {
|
|
||||||
if let RoomLobby::Lobby(lobby) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
|
|
||||||
Ok(*lobby)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Err(GetAreaError::NotInLobby)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove_client_from_area(&self, id: ClientId) -> Result<(), ClientRemovalError> {
|
|
||||||
fn remove_client<const N: usize>(id: ClientId, client_list : &mut [Option<AreaClient>; N]) {
|
|
||||||
client_list
|
|
||||||
.iter_mut()
|
|
||||||
.filter(|client| {
|
|
||||||
client.map_or(false, |c| {
|
|
||||||
c.client == id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.for_each(|client| {
|
|
||||||
*client = None
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let area = self.client_location
|
|
||||||
.read()
|
|
||||||
.await;
|
|
||||||
let area = area
|
|
||||||
.get(&id)
|
|
||||||
.ok_or(ClientRemovalError::ClientNotInArea)?;
|
|
||||||
match area {
|
|
||||||
RoomLobby::Room(room) => {
|
|
||||||
let mut r = self.rooms.get(room.0)
|
|
||||||
.ok_or(ClientRemovalError::InvalidArea)?
|
|
||||||
.as_ref()
|
|
||||||
.write()
|
|
||||||
.await;
|
|
||||||
if let Some(r) = r.as_mut() {
|
|
||||||
remove_client(id, &mut r.0)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Err(ClientRemovalError::InvalidArea)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RoomLobby::Lobby(lobby) => {
|
|
||||||
remove_client(id, &mut self.lobbies[lobby.0].write().await.0)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_add_client_to_lobby() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
|
|
||||||
cl.add_client_to_lobby(ClientId(13), LobbyId(1)).await.unwrap();
|
|
||||||
cl.add_client_to_lobby(ClientId(14), LobbyId(0)).await.unwrap();
|
|
||||||
|
|
||||||
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.into_iter().flatten().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
|
||||||
(ClientId(12), LocalClientId(0)),
|
|
||||||
(ClientId(14), LocalClientId(1)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_add_client_to_full_lobby() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
for i in 0..12 {
|
|
||||||
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
|
|
||||||
}
|
|
||||||
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await == Err(JoinLobbyError::LobbyFull));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_add_client_to_next_available_lobby() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
for lobby in 1..4 {
|
|
||||||
for i in 0..12 {
|
|
||||||
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await == Ok(LobbyId(4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_add_to_lobby_when_all_are_full() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
for lobby in 0..15 {
|
|
||||||
for i in 0..12 {
|
|
||||||
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await, Err(JoinLobbyError::LobbyFull));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_new_room() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
assert!(cl.create_new_room(ClientId(12)).await == Ok(RoomId(0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_add_client_to_room() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
let room = cl.create_new_room(ClientId(12)).await.unwrap();
|
|
||||||
assert!(cl.add_client_to_room(ClientId(234), room).await == Ok(()));
|
|
||||||
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
|
||||||
(ClientId(12), LocalClientId(0)),
|
|
||||||
(ClientId(234), LocalClientId(1)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_no_new_room_slots() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
for i in 0..128 {
|
|
||||||
cl.create_new_room(ClientId(i)).await;
|
|
||||||
}
|
|
||||||
assert!(cl.create_new_room(ClientId(234)).await == Err(CreateRoomError::NoOpenSlots));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_joining_full_room() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
let room = cl.create_new_room(ClientId(0)).await.unwrap();
|
|
||||||
assert!(cl.add_client_to_room(ClientId(1), room).await == Ok(()));
|
|
||||||
assert!(cl.add_client_to_room(ClientId(2), room).await == Ok(()));
|
|
||||||
assert!(cl.add_client_to_room(ClientId(3), room).await == Ok(()));
|
|
||||||
assert!(cl.add_client_to_room(ClientId(234), room).await == Err(JoinRoomError::RoomFull));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_adding_client_to_room_removes_from_lobby() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
|
|
||||||
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
|
|
||||||
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
|
|
||||||
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
|
|
||||||
|
|
||||||
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
|
||||||
assert!(cl.add_client_to_room(ClientId(93), room).await == Ok(()));
|
|
||||||
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
|
||||||
(ClientId(23), LocalClientId(1)),
|
|
||||||
(ClientId(12), LocalClientId(3)),
|
|
||||||
]);
|
|
||||||
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
|
||||||
(ClientId(51), LocalClientId(0)),
|
|
||||||
(ClientId(93), LocalClientId(1)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_getting_neighbors() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await.unwrap();
|
|
||||||
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await.unwrap();
|
|
||||||
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await.unwrap();
|
|
||||||
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
|
|
||||||
|
|
||||||
assert!(cl.get_client_neighbors(ClientId(23)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
|
||||||
(ClientId(93), LocalClientId(0)),
|
|
||||||
(ClientId(51), LocalClientId(2)),
|
|
||||||
(ClientId(12), LocalClientId(3)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_failing_to_join_lobby_does_not_remove_from_current_area() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
for i in 0..12 {
|
|
||||||
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
|
|
||||||
}
|
|
||||||
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(1)).await.is_ok());
|
|
||||||
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await.is_err());
|
|
||||||
assert_eq!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().len(), 12);
|
|
||||||
assert_eq!(
|
|
||||||
cl.get_clients_in_lobby(LobbyId(1)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>(),
|
|
||||||
vec![(ClientId(99), LocalClientId(0))]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_get_leader() {
|
|
||||||
let cl = ClientLocation::default();
|
|
||||||
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
|
|
||||||
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
|
|
||||||
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
|
|
||||||
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
|
|
||||||
|
|
||||||
assert!(cl.get_leader_by_client(ClientId(51)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_remove_client_from_room() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
|
||||||
cl.add_client_to_room(ClientId(93), room).await;
|
|
||||||
cl.add_client_to_room(ClientId(23), room).await;
|
|
||||||
cl.remove_client_from_area(ClientId(51)).await;
|
|
||||||
cl.add_client_to_room(ClientId(12), room).await;
|
|
||||||
|
|
||||||
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
|
||||||
(ClientId(12), LocalClientId(0)),
|
|
||||||
(ClientId(93), LocalClientId(1)),
|
|
||||||
(ClientId(23), LocalClientId(2)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_leader_changes_on_leader_leaving() {
|
|
||||||
let mut cl = ClientLocation::default();
|
|
||||||
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
|
||||||
cl.add_client_to_room(ClientId(93), room).await.unwrap();
|
|
||||||
cl.add_client_to_room(ClientId(23), room).await.unwrap();
|
|
||||||
cl.remove_client_from_area(ClientId(51)).await.unwrap();
|
|
||||||
cl.add_client_to_room(ClientId(12), room).await.unwrap();
|
|
||||||
cl.remove_client_from_area(ClientId(23)).await.unwrap();
|
|
||||||
cl.add_client_to_room(ClientId(99), room).await.unwrap();
|
|
||||||
|
|
||||||
assert!(cl.get_leader_by_client(ClientId(12)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1))));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,328 @@
|
|||||||
pub mod quests;
|
use log::warn;
|
||||||
|
use std::collections::{HashMap, BTreeMap, BTreeSet};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write, Cursor, Seek, SeekFrom};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use async_std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder};
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
|
use libpso::util::array_to_utf16;
|
||||||
|
use maps::area::{MapArea, MapAreaError};
|
||||||
|
use maps::object::MapObject;
|
||||||
|
use maps::enemy::MapEnemy;
|
||||||
|
use maps::maps::{objects_from_stream, enemy_data_from_stream};
|
||||||
|
use maps::room::{Episode, RoomMode};
|
||||||
|
use maps::area::{MapAreaLookup, MapAreaLookupBuilder};
|
||||||
|
|
||||||
|
|
||||||
pub use quests::*;
|
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct QuestCategory {
|
||||||
|
index: usize,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash)]
|
||||||
|
struct QuestListEntry {
|
||||||
|
bin: String,
|
||||||
|
dat: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Hash)]
|
||||||
|
struct QuestListCategory {
|
||||||
|
list_order: usize,
|
||||||
|
description: String,
|
||||||
|
quests: Vec<QuestListEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct QuestListConfig {
|
||||||
|
questlist: HashMap<String, Vec<QuestListEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("")]
|
||||||
|
pub enum ParseDatError {
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
MapError(#[from] MapAreaError),
|
||||||
|
UnknownDatHeader(u32),
|
||||||
|
CouldNotDetermineEpisode,
|
||||||
|
InvalidMapAreaId(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
const DAT_OBJECT_HEADER_ID: u32 = 1;
|
||||||
|
const DAT_ENEMY_HEADER_ID: u32 = 2;
|
||||||
|
const DAT_WAVE_HEADER_ID: u32 = 3;
|
||||||
|
|
||||||
|
enum DatBlock {
|
||||||
|
Object(Vec<Option<MapObject>>),
|
||||||
|
Enemy(Vec<Option<MapEnemy>>),
|
||||||
|
Wave,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode, map_areas: &MapAreaLookup) -> Result<DatBlock, ParseDatError> {
|
||||||
|
let header = cursor.read_u32::<LittleEndian>()?;
|
||||||
|
let _offset = cursor.read_u32::<LittleEndian>()?;
|
||||||
|
let area = cursor.read_u16::<LittleEndian>()?;
|
||||||
|
let _unknown1 = cursor.read_u16::<LittleEndian>()?;
|
||||||
|
let length = cursor.read_u32::<LittleEndian>()?;
|
||||||
|
|
||||||
|
let map_area = map_areas.get_area_map(area).map_err(|_| ParseDatError::InvalidMapAreaId(area))?;
|
||||||
|
|
||||||
|
match header {
|
||||||
|
DAT_OBJECT_HEADER_ID => {
|
||||||
|
let mut obj_data = vec![0u8; length as usize];
|
||||||
|
cursor.read_exact(&mut obj_data)?;
|
||||||
|
let mut obj_cursor = Cursor::new(obj_data);
|
||||||
|
|
||||||
|
let objects = objects_from_stream(&mut obj_cursor, episode, &map_area);
|
||||||
|
Ok(DatBlock::Object(objects))
|
||||||
|
},
|
||||||
|
DAT_ENEMY_HEADER_ID => {
|
||||||
|
let mut enemy_data = vec![0u8; length as usize];
|
||||||
|
cursor.read_exact(&mut enemy_data)?;
|
||||||
|
let mut enemy_cursor = Cursor::new(enemy_data);
|
||||||
|
|
||||||
|
let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode);
|
||||||
|
|
||||||
|
Ok(DatBlock::Enemy(enemies))
|
||||||
|
},
|
||||||
|
DAT_WAVE_HEADER_ID => {
|
||||||
|
cursor.seek(SeekFrom::Current(length as i64))?;
|
||||||
|
Ok(DatBlock::Wave)
|
||||||
|
},
|
||||||
|
_ => Err(ParseDatError::UnknownDatHeader(header))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quest_episode(bin: &[u8]) -> Option<Episode> {
|
||||||
|
for bytes in bin.windows(3) {
|
||||||
|
// set_episode
|
||||||
|
if bytes[0] == 0xF8 && bytes[1] == 0xBC {
|
||||||
|
return Episode::from_quest(bytes[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_area_mappings(bin: &[u8]) -> MapAreaLookup {
|
||||||
|
let mut map_areas = MapAreaLookupBuilder::default();
|
||||||
|
for bytes in bin.windows(4) {
|
||||||
|
// BB_Map_Designate
|
||||||
|
if bytes[0] == 0xF9 && bytes[1] == 0x51 {
|
||||||
|
//return Some(Episode::from_quest(bytes[2]).ok()?)
|
||||||
|
let floor_value = bytes[2] as u16;
|
||||||
|
if let Some(map_area) = MapArea::from_bb_map_designate(bytes[3]) {
|
||||||
|
map_areas = map_areas.add(floor_value, map_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map_areas.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), ParseDatError> {
|
||||||
|
let mut cursor = Cursor::new(dat);
|
||||||
|
|
||||||
|
let header_iter = std::iter::from_fn(move || {
|
||||||
|
match read_dat_section_header(&mut cursor, episode, map_areas) {
|
||||||
|
Ok(dat_block) => Some(dat_block),
|
||||||
|
Err(err) => {
|
||||||
|
warn!("unknown header in dat: {:?}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(header_iter.fold((Vec::new(), Vec::new()), |(mut enemies, mut objects), dat_block| {
|
||||||
|
match dat_block {
|
||||||
|
DatBlock::Object(mut objs) => {
|
||||||
|
objects.append(&mut objs)
|
||||||
|
},
|
||||||
|
DatBlock::Enemy(mut enemy) => {
|
||||||
|
enemies.append(&mut enemy)
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
(enemies, objects)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum QuestLoadError {
|
||||||
|
#[error("io error {0}")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
#[error("parse dat error {0}")]
|
||||||
|
ParseDatError(#[from] ParseDatError),
|
||||||
|
#[error("could not read metadata")]
|
||||||
|
CouldNotReadMetadata,
|
||||||
|
#[error("could not load config file")]
|
||||||
|
CouldNotLoadConfigFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Quest {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub full_description: String,
|
||||||
|
pub language: u16,
|
||||||
|
pub id: u16,
|
||||||
|
pub bin_blob: Arc<Vec<u8>>,
|
||||||
|
pub dat_blob: Arc<Vec<u8>>,
|
||||||
|
pub enemies: Vec<Option<MapEnemy>>, // TODO: Arc?
|
||||||
|
pub objects: Vec<Option<MapObject>>, // TODO: Arc?
|
||||||
|
pub map_areas: MapAreaLookup, // TODO: Arc?
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Quest {
|
||||||
|
fn from_bin_dat(bin: Vec<u8>, dat: Vec<u8>) -> Result<Quest, QuestLoadError> {
|
||||||
|
let id = u16::from_le_bytes(bin[16..18].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?);
|
||||||
|
let language = u16::from_le_bytes(bin[18..20].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?);
|
||||||
|
let name = array_to_utf16(&bin[24..88]);
|
||||||
|
let description = array_to_utf16(&bin[88..334]);
|
||||||
|
let full_description = array_to_utf16(&bin[334..920]);
|
||||||
|
|
||||||
|
let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?;
|
||||||
|
let map_areas = map_area_mappings(&bin);
|
||||||
|
let (enemies, objects) = parse_dat(&dat, &episode, &map_areas)?;
|
||||||
|
|
||||||
|
let mut prs_bin = LegacyPrsEncoder::new(Vec::new());
|
||||||
|
prs_bin.write_all(&bin)?;
|
||||||
|
let mut prs_dat = LegacyPrsEncoder::new(Vec::new());
|
||||||
|
prs_dat.write_all(&dat)?;
|
||||||
|
|
||||||
|
Ok(Quest {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
full_description,
|
||||||
|
id,
|
||||||
|
language,
|
||||||
|
bin_blob: Arc::new(prs_bin.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?),
|
||||||
|
dat_blob: Arc::new(prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?),
|
||||||
|
enemies,
|
||||||
|
objects,
|
||||||
|
map_areas,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuestCollection
|
||||||
|
pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
|
||||||
|
|
||||||
|
pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf, quest_path: PathBuf) -> Option<Quest> {
|
||||||
|
let dat_file = File::open(quest_path.join(dat_path.clone()))
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!("could not load quest file {:?}: {:?}", dat_path, err)
|
||||||
|
}).ok()?;
|
||||||
|
let bin_file = File::open(quest_path.join(bin_path.clone()))
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!("could not load quest file {:?}: {:?}", bin_path, err)
|
||||||
|
}).ok()?;
|
||||||
|
let mut dat_prs = LegacyPrsDecoder::new(dat_file);
|
||||||
|
let mut bin_prs = LegacyPrsDecoder::new(bin_file);
|
||||||
|
|
||||||
|
let mut dat = Vec::new();
|
||||||
|
let mut bin = Vec::new();
|
||||||
|
dat_prs.read_to_end(&mut dat).ok()?;
|
||||||
|
bin_prs.read_to_end(&mut bin).ok()?;
|
||||||
|
|
||||||
|
let quest = Quest::from_bin_dat(bin, dat).map_err(|err| {
|
||||||
|
warn!("could not parse quest file {:?}/{:?}: {:?}", bin_path, dat_path, err)
|
||||||
|
}).ok()?;
|
||||||
|
Some(quest)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn load_quests_path(mut quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
||||||
|
let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
||||||
|
let mut s = String::new();
|
||||||
|
f.read_to_string(&mut s)?;
|
||||||
|
quest_path.pop(); // remove quests.toml from the path
|
||||||
|
let mut used_quest_ids = BTreeSet::new();
|
||||||
|
let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
||||||
|
|
||||||
|
Ok(ql.into_iter().map(|(category, category_details)| {
|
||||||
|
(
|
||||||
|
QuestCategory {
|
||||||
|
index: category_details.list_order,
|
||||||
|
name: category,
|
||||||
|
description: category_details.description,
|
||||||
|
},
|
||||||
|
category_details.quests
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|quest| {
|
||||||
|
load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf())
|
||||||
|
.and_then(|quest | {
|
||||||
|
if used_quest_ids.contains(&quest.id) {
|
||||||
|
warn!("quest id already exists: {}", quest.id);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
used_quest_ids.insert(quest.id);
|
||||||
|
Some(quest)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
)
|
||||||
|
}).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_standard_quests(mode: RoomMode) -> Result<QuestList, QuestLoadError> {
|
||||||
|
match mode {
|
||||||
|
RoomMode::Single {episode, .. } => {
|
||||||
|
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "single", "quests.toml"]))
|
||||||
|
},
|
||||||
|
RoomMode::Multi {episode, .. } => {
|
||||||
|
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "multi", "quests.toml"]))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Ok(BTreeMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn load_government_quests(mode: RoomMode) -> Result<QuestList, QuestLoadError> {
|
||||||
|
match mode {
|
||||||
|
RoomMode::Single {episode, .. } => {
|
||||||
|
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
|
||||||
|
},
|
||||||
|
RoomMode::Multi {episode, .. } => {
|
||||||
|
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Ok(BTreeMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// the quest phantasmal world 4 uses the tower map twice, to do this it had to remap
|
||||||
|
// one of the other maps to be a second tower
|
||||||
|
#[test]
|
||||||
|
fn test_quest_with_remapped_floors() {
|
||||||
|
let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into(), "data/quests/bb/ep2/multi".into()).unwrap();
|
||||||
|
let enemies_not_in_tower = pw4.enemies.iter()
|
||||||
|
.filter(|enemy| {
|
||||||
|
enemy.is_some()
|
||||||
|
})
|
||||||
|
.filter(|enemy| {
|
||||||
|
enemy.unwrap().map_area != MapArea::Tower
|
||||||
|
});
|
||||||
|
assert!(enemies_not_in_tower.count() == 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,328 +0,0 @@
|
|||||||
use log::warn;
|
|
||||||
use std::collections::{HashMap, BTreeMap, BTreeSet};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Write, Cursor, Seek, SeekFrom};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use async_std::sync::Arc;
|
|
||||||
use thiserror::Error;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder};
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
|
||||||
use libpso::util::array_to_utf16;
|
|
||||||
use maps::area::{MapArea, MapAreaError};
|
|
||||||
use maps::object::MapObject;
|
|
||||||
use maps::enemy::MapEnemy;
|
|
||||||
use maps::maps::{objects_from_stream, enemy_data_from_stream};
|
|
||||||
use maps::room::{Episode, RoomMode};
|
|
||||||
use maps::area::{MapAreaLookup, MapAreaLookupBuilder};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct QuestCategory {
|
|
||||||
index: usize,
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Hash)]
|
|
||||||
struct QuestListEntry {
|
|
||||||
bin: String,
|
|
||||||
dat: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Hash)]
|
|
||||||
struct QuestListCategory {
|
|
||||||
list_order: usize,
|
|
||||||
description: String,
|
|
||||||
quests: Vec<QuestListEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct QuestListConfig {
|
|
||||||
questlist: HashMap<String, Vec<QuestListEntry>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("")]
|
|
||||||
pub enum ParseDatError {
|
|
||||||
IoError(#[from] std::io::Error),
|
|
||||||
MapError(#[from] MapAreaError),
|
|
||||||
UnknownDatHeader(u32),
|
|
||||||
CouldNotDetermineEpisode,
|
|
||||||
InvalidMapAreaId(u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
const DAT_OBJECT_HEADER_ID: u32 = 1;
|
|
||||||
const DAT_ENEMY_HEADER_ID: u32 = 2;
|
|
||||||
const DAT_WAVE_HEADER_ID: u32 = 3;
|
|
||||||
|
|
||||||
enum DatBlock {
|
|
||||||
Object(Vec<Option<MapObject>>),
|
|
||||||
Enemy(Vec<Option<MapEnemy>>),
|
|
||||||
Wave,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode, map_areas: &MapAreaLookup) -> Result<DatBlock, ParseDatError> {
|
|
||||||
let header = cursor.read_u32::<LittleEndian>()?;
|
|
||||||
let _offset = cursor.read_u32::<LittleEndian>()?;
|
|
||||||
let area = cursor.read_u16::<LittleEndian>()?;
|
|
||||||
let _unknown1 = cursor.read_u16::<LittleEndian>()?;
|
|
||||||
let length = cursor.read_u32::<LittleEndian>()?;
|
|
||||||
|
|
||||||
let map_area = map_areas.get_area_map(area).map_err(|_| ParseDatError::InvalidMapAreaId(area))?;
|
|
||||||
|
|
||||||
match header {
|
|
||||||
DAT_OBJECT_HEADER_ID => {
|
|
||||||
let mut obj_data = vec![0u8; length as usize];
|
|
||||||
cursor.read_exact(&mut obj_data)?;
|
|
||||||
let mut obj_cursor = Cursor::new(obj_data);
|
|
||||||
|
|
||||||
let objects = objects_from_stream(&mut obj_cursor, episode, &map_area);
|
|
||||||
Ok(DatBlock::Object(objects))
|
|
||||||
},
|
|
||||||
DAT_ENEMY_HEADER_ID => {
|
|
||||||
let mut enemy_data = vec![0u8; length as usize];
|
|
||||||
cursor.read_exact(&mut enemy_data)?;
|
|
||||||
let mut enemy_cursor = Cursor::new(enemy_data);
|
|
||||||
|
|
||||||
let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode);
|
|
||||||
|
|
||||||
Ok(DatBlock::Enemy(enemies))
|
|
||||||
},
|
|
||||||
DAT_WAVE_HEADER_ID => {
|
|
||||||
cursor.seek(SeekFrom::Current(length as i64))?;
|
|
||||||
Ok(DatBlock::Wave)
|
|
||||||
},
|
|
||||||
_ => Err(ParseDatError::UnknownDatHeader(header))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quest_episode(bin: &[u8]) -> Option<Episode> {
|
|
||||||
for bytes in bin.windows(3) {
|
|
||||||
// set_episode
|
|
||||||
if bytes[0] == 0xF8 && bytes[1] == 0xBC {
|
|
||||||
return Episode::from_quest(bytes[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_area_mappings(bin: &[u8]) -> MapAreaLookup {
|
|
||||||
let mut map_areas = MapAreaLookupBuilder::default();
|
|
||||||
for bytes in bin.windows(4) {
|
|
||||||
// BB_Map_Designate
|
|
||||||
if bytes[0] == 0xF9 && bytes[1] == 0x51 {
|
|
||||||
//return Some(Episode::from_quest(bytes[2]).ok()?)
|
|
||||||
let floor_value = bytes[2] as u16;
|
|
||||||
if let Some(map_area) = MapArea::from_bb_map_designate(bytes[3]) {
|
|
||||||
map_areas = map_areas.add(floor_value, map_area);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map_areas.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), ParseDatError> {
|
|
||||||
let mut cursor = Cursor::new(dat);
|
|
||||||
|
|
||||||
let header_iter = std::iter::from_fn(move || {
|
|
||||||
match read_dat_section_header(&mut cursor, episode, map_areas) {
|
|
||||||
Ok(dat_block) => Some(dat_block),
|
|
||||||
Err(err) => {
|
|
||||||
warn!("unknown header in dat: {:?}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(header_iter.fold((Vec::new(), Vec::new()), |(mut enemies, mut objects), dat_block| {
|
|
||||||
match dat_block {
|
|
||||||
DatBlock::Object(mut objs) => {
|
|
||||||
objects.append(&mut objs)
|
|
||||||
},
|
|
||||||
DatBlock::Enemy(mut enemy) => {
|
|
||||||
enemies.append(&mut enemy)
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
(enemies, objects)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum QuestLoadError {
|
|
||||||
#[error("io error {0}")]
|
|
||||||
IoError(#[from] std::io::Error),
|
|
||||||
#[error("parse dat error {0}")]
|
|
||||||
ParseDatError(#[from] ParseDatError),
|
|
||||||
#[error("could not read metadata")]
|
|
||||||
CouldNotReadMetadata,
|
|
||||||
#[error("could not load config file")]
|
|
||||||
CouldNotLoadConfigFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Quest {
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub full_description: String,
|
|
||||||
pub language: u16,
|
|
||||||
pub id: u16,
|
|
||||||
pub bin_blob: Arc<Vec<u8>>,
|
|
||||||
pub dat_blob: Arc<Vec<u8>>,
|
|
||||||
pub enemies: Vec<Option<MapEnemy>>, // TODO: Arc?
|
|
||||||
pub objects: Vec<Option<MapObject>>, // TODO: Arc?
|
|
||||||
pub map_areas: MapAreaLookup, // TODO: Arc?
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Quest {
|
|
||||||
fn from_bin_dat(bin: Vec<u8>, dat: Vec<u8>) -> Result<Quest, QuestLoadError> {
|
|
||||||
let id = u16::from_le_bytes(bin[16..18].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?);
|
|
||||||
let language = u16::from_le_bytes(bin[18..20].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?);
|
|
||||||
let name = array_to_utf16(&bin[24..88]);
|
|
||||||
let description = array_to_utf16(&bin[88..334]);
|
|
||||||
let full_description = array_to_utf16(&bin[334..920]);
|
|
||||||
|
|
||||||
let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?;
|
|
||||||
let map_areas = map_area_mappings(&bin);
|
|
||||||
let (enemies, objects) = parse_dat(&dat, &episode, &map_areas)?;
|
|
||||||
|
|
||||||
let mut prs_bin = LegacyPrsEncoder::new(Vec::new());
|
|
||||||
prs_bin.write_all(&bin)?;
|
|
||||||
let mut prs_dat = LegacyPrsEncoder::new(Vec::new());
|
|
||||||
prs_dat.write_all(&dat)?;
|
|
||||||
|
|
||||||
Ok(Quest {
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
full_description,
|
|
||||||
id,
|
|
||||||
language,
|
|
||||||
bin_blob: Arc::new(prs_bin.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?),
|
|
||||||
dat_blob: Arc::new(prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?),
|
|
||||||
enemies,
|
|
||||||
objects,
|
|
||||||
map_areas,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QuestCollection
|
|
||||||
pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
|
|
||||||
|
|
||||||
pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf, quest_path: PathBuf) -> Option<Quest> {
|
|
||||||
let dat_file = File::open(quest_path.join(dat_path.clone()))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("could not load quest file {:?}: {:?}", dat_path, err)
|
|
||||||
}).ok()?;
|
|
||||||
let bin_file = File::open(quest_path.join(bin_path.clone()))
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("could not load quest file {:?}: {:?}", bin_path, err)
|
|
||||||
}).ok()?;
|
|
||||||
let mut dat_prs = LegacyPrsDecoder::new(dat_file);
|
|
||||||
let mut bin_prs = LegacyPrsDecoder::new(bin_file);
|
|
||||||
|
|
||||||
let mut dat = Vec::new();
|
|
||||||
let mut bin = Vec::new();
|
|
||||||
dat_prs.read_to_end(&mut dat).ok()?;
|
|
||||||
bin_prs.read_to_end(&mut bin).ok()?;
|
|
||||||
|
|
||||||
let quest = Quest::from_bin_dat(bin, dat).map_err(|err| {
|
|
||||||
warn!("could not parse quest file {:?}/{:?}: {:?}", bin_path, dat_path, err)
|
|
||||||
}).ok()?;
|
|
||||||
Some(quest)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn load_quests_path(mut quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
|
||||||
let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
|
||||||
let mut s = String::new();
|
|
||||||
f.read_to_string(&mut s)?;
|
|
||||||
quest_path.pop(); // remove quests.toml from the path
|
|
||||||
let mut used_quest_ids = BTreeSet::new();
|
|
||||||
let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
|
||||||
|
|
||||||
Ok(ql.into_iter().map(|(category, category_details)| {
|
|
||||||
(
|
|
||||||
QuestCategory {
|
|
||||||
index: category_details.list_order,
|
|
||||||
name: category,
|
|
||||||
description: category_details.description,
|
|
||||||
},
|
|
||||||
category_details.quests
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|quest| {
|
|
||||||
load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf())
|
|
||||||
.and_then(|quest | {
|
|
||||||
if used_quest_ids.contains(&quest.id) {
|
|
||||||
warn!("quest id already exists: {}", quest.id);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
used_quest_ids.insert(quest.id);
|
|
||||||
Some(quest)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
)
|
|
||||||
}).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_standard_quests(mode: RoomMode) -> Result<QuestList, QuestLoadError> {
|
|
||||||
match mode {
|
|
||||||
RoomMode::Single {episode, .. } => {
|
|
||||||
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "single", "quests.toml"]))
|
|
||||||
},
|
|
||||||
RoomMode::Multi {episode, .. } => {
|
|
||||||
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "multi", "quests.toml"]))
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
Ok(BTreeMap::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn load_government_quests(mode: RoomMode) -> Result<QuestList, QuestLoadError> {
|
|
||||||
match mode {
|
|
||||||
RoomMode::Single {episode, .. } => {
|
|
||||||
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
|
|
||||||
},
|
|
||||||
RoomMode::Multi {episode, .. } => {
|
|
||||||
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
Ok(BTreeMap::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// the quest phantasmal world 4 uses the tower map twice, to do this it had to remap
|
|
||||||
// one of the other maps to be a second tower
|
|
||||||
#[test]
|
|
||||||
fn test_quest_with_remapped_floors() {
|
|
||||||
let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into(), "data/quests/bb/ep2/multi".into()).unwrap();
|
|
||||||
let enemies_not_in_tower = pw4.enemies.iter()
|
|
||||||
.filter(|enemy| {
|
|
||||||
enemy.is_some()
|
|
||||||
})
|
|
||||||
.filter(|enemy| {
|
|
||||||
enemy.unwrap().map_area != MapArea::Tower
|
|
||||||
});
|
|
||||||
assert!(enemies_not_in_tower.count() == 0);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
273
room/src/lib.rs
273
room/src/lib.rs
@ -1,3 +1,272 @@
|
|||||||
pub mod room;
|
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};
|
||||||
|
|
||||||
pub use room::*;
|
use thiserror::Error;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
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 quests;
|
||||||
|
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<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(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<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(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<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(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<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,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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, 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<Box<dyn Fn(RoomMode, Holiday) -> 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)?,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
272
room/src/room.rs
272
room/src/room.rs
@ -1,272 +0,0 @@
|
|||||||
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 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 quests;
|
|
||||||
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<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(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<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(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<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(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<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,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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, 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<Box<dyn Fn(RoomMode, Holiday) -> 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)?,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
132
trade/src/lib.rs
132
trade/src/lib.rs
@ -1,4 +1,132 @@
|
|||||||
pub mod trade;
|
use std::collections::HashMap;
|
||||||
|
use networking::serverstate::ClientId;
|
||||||
|
use items;
|
||||||
|
use async_std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use futures::future::{Future, OptionFuture};
|
||||||
|
use items::trade::TradeItem;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum TradeStatus {
|
||||||
|
SentRequest,
|
||||||
|
ReceivedRequest,
|
||||||
|
Trading,
|
||||||
|
Confirmed,
|
||||||
|
FinalConfirm,
|
||||||
|
ItemsChecked,
|
||||||
|
TradeComplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub use trade::*;
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ClientTradeState {
|
||||||
|
client: ClientId,
|
||||||
|
other_client: ClientId,
|
||||||
|
pub items: Vec<TradeItem>,
|
||||||
|
pub meseta: usize,
|
||||||
|
pub status: TradeStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ClientTradeState {
|
||||||
|
pub fn client(&self) -> ClientId {
|
||||||
|
self.client
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn other_client(&self) -> ClientId {
|
||||||
|
self.other_client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum TradeStateError {
|
||||||
|
#[error("client not in trade {0}")]
|
||||||
|
ClientNotInTrade(ClientId),
|
||||||
|
#[error("mismatched trade {0} {1}")]
|
||||||
|
MismatchedTrade(ClientId, ClientId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct TradeState {
|
||||||
|
trades: HashMap<ClientId, Arc<Mutex<ClientTradeState>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TradeState {
|
||||||
|
pub fn new_trade(&mut self, sender: &ClientId, receiver: &ClientId) {
|
||||||
|
let state = ClientTradeState {
|
||||||
|
client: *sender,
|
||||||
|
other_client: *receiver,
|
||||||
|
items: Default::default(),
|
||||||
|
meseta: 0,
|
||||||
|
status: TradeStatus::SentRequest,
|
||||||
|
};
|
||||||
|
self.trades.insert(*sender, Arc::new(Mutex::new(state)));
|
||||||
|
|
||||||
|
let state = ClientTradeState {
|
||||||
|
client: *receiver,
|
||||||
|
other_client: *sender,
|
||||||
|
items: Default::default(),
|
||||||
|
meseta: 0,
|
||||||
|
status: TradeStatus::ReceivedRequest,
|
||||||
|
};
|
||||||
|
self.trades.insert(*receiver, Arc::new(Mutex::new(state)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn in_trade(&self, client: &ClientId) -> bool {
|
||||||
|
self.trades.contains_key(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result<T, TradeStateError>
|
||||||
|
where
|
||||||
|
F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a,
|
||||||
|
Fut: Future<Output=T>
|
||||||
|
{
|
||||||
|
let c1 = self.trades.get(client).ok_or(TradeStateError::ClientNotInTrade(*client))?.lock().await;
|
||||||
|
let c2 = self.trades.get(&c1.other_client).ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?.lock().await;
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if c1.client != c2.other_client {
|
||||||
|
return Err(TradeStateError::MismatchedTrade(c1.client, c2.client));
|
||||||
|
}
|
||||||
|
Ok(func(c1, c2).await)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result<T, TradeStateError>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
//F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> BoxFuture<'b, T> + Send + 'a
|
||||||
|
//F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> Fut + Send + 'a,
|
||||||
|
F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a,
|
||||||
|
Fut: Future<Output = T>,
|
||||||
|
{
|
||||||
|
let c1 = self.trades
|
||||||
|
.get(client)
|
||||||
|
.ok_or(TradeStateError::ClientNotInTrade(*client))?
|
||||||
|
.lock()
|
||||||
|
.await;
|
||||||
|
let c2 = self.trades
|
||||||
|
.get(&c1.other_client)
|
||||||
|
.ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?
|
||||||
|
.lock()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if c1.client != c2.other_client {
|
||||||
|
return Err(TradeStateError::MismatchedTrade(c1.client, c2.client));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(func(c1, c2).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is it possible for this to not return Options?
|
||||||
|
pub async fn remove_trade(&mut self, client: &ClientId) -> (Option<ClientTradeState>, Option<ClientTradeState>) {
|
||||||
|
let c1 = OptionFuture::from(self.trades.remove(client).map(|c| async move {c.lock().await.clone()} )).await;
|
||||||
|
let c2 = if let Some(ref state) = c1 {
|
||||||
|
OptionFuture::from(self.trades.remove(&state.other_client).map(|c| async move {c.lock().await.clone()})).await
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
(c1, c2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use networking::serverstate::ClientId;
|
|
||||||
use items;
|
|
||||||
use async_std::sync::{Arc, Mutex, MutexGuard};
|
|
||||||
use futures::future::{Future, OptionFuture};
|
|
||||||
use items::trade::TradeItem;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub enum TradeStatus {
|
|
||||||
SentRequest,
|
|
||||||
ReceivedRequest,
|
|
||||||
Trading,
|
|
||||||
Confirmed,
|
|
||||||
FinalConfirm,
|
|
||||||
ItemsChecked,
|
|
||||||
TradeComplete,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ClientTradeState {
|
|
||||||
client: ClientId,
|
|
||||||
other_client: ClientId,
|
|
||||||
pub items: Vec<TradeItem>,
|
|
||||||
pub meseta: usize,
|
|
||||||
pub status: TradeStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl ClientTradeState {
|
|
||||||
pub fn client(&self) -> ClientId {
|
|
||||||
self.client
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn other_client(&self) -> ClientId {
|
|
||||||
self.other_client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum TradeStateError {
|
|
||||||
#[error("client not in trade {0}")]
|
|
||||||
ClientNotInTrade(ClientId),
|
|
||||||
#[error("mismatched trade {0} {1}")]
|
|
||||||
MismatchedTrade(ClientId, ClientId),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct TradeState {
|
|
||||||
trades: HashMap<ClientId, Arc<Mutex<ClientTradeState>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TradeState {
|
|
||||||
pub fn new_trade(&mut self, sender: &ClientId, receiver: &ClientId) {
|
|
||||||
let state = ClientTradeState {
|
|
||||||
client: *sender,
|
|
||||||
other_client: *receiver,
|
|
||||||
items: Default::default(),
|
|
||||||
meseta: 0,
|
|
||||||
status: TradeStatus::SentRequest,
|
|
||||||
};
|
|
||||||
self.trades.insert(*sender, Arc::new(Mutex::new(state)));
|
|
||||||
|
|
||||||
let state = ClientTradeState {
|
|
||||||
client: *receiver,
|
|
||||||
other_client: *sender,
|
|
||||||
items: Default::default(),
|
|
||||||
meseta: 0,
|
|
||||||
status: TradeStatus::ReceivedRequest,
|
|
||||||
};
|
|
||||||
self.trades.insert(*receiver, Arc::new(Mutex::new(state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn in_trade(&self, client: &ClientId) -> bool {
|
|
||||||
self.trades.contains_key(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result<T, TradeStateError>
|
|
||||||
where
|
|
||||||
F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a,
|
|
||||||
Fut: Future<Output=T>
|
|
||||||
{
|
|
||||||
let c1 = self.trades.get(client).ok_or(TradeStateError::ClientNotInTrade(*client))?.lock().await;
|
|
||||||
let c2 = self.trades.get(&c1.other_client).ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?.lock().await;
|
|
||||||
|
|
||||||
// sanity check
|
|
||||||
if c1.client != c2.other_client {
|
|
||||||
return Err(TradeStateError::MismatchedTrade(c1.client, c2.client));
|
|
||||||
}
|
|
||||||
Ok(func(c1, c2).await)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result<T, TradeStateError>
|
|
||||||
where
|
|
||||||
T: Send,
|
|
||||||
//F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> BoxFuture<'b, T> + Send + 'a
|
|
||||||
//F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> Fut + Send + 'a,
|
|
||||||
F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a,
|
|
||||||
Fut: Future<Output = T>,
|
|
||||||
{
|
|
||||||
let c1 = self.trades
|
|
||||||
.get(client)
|
|
||||||
.ok_or(TradeStateError::ClientNotInTrade(*client))?
|
|
||||||
.lock()
|
|
||||||
.await;
|
|
||||||
let c2 = self.trades
|
|
||||||
.get(&c1.other_client)
|
|
||||||
.ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?
|
|
||||||
.lock()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if c1.client != c2.other_client {
|
|
||||||
return Err(TradeStateError::MismatchedTrade(c1.client, c2.client));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(func(c1, c2).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is it possible for this to not return Options?
|
|
||||||
pub async fn remove_trade(&mut self, client: &ClientId) -> (Option<ClientTradeState>, Option<ClientTradeState>) {
|
|
||||||
let c1 = OptionFuture::from(self.trades.remove(client).map(|c| async move {c.lock().await.clone()} )).await;
|
|
||||||
let c2 = if let Some(ref state) = c1 {
|
|
||||||
OptionFuture::from(self.trades.remove(&state.other_client).map(|c| async move {c.lock().await.clone()})).await
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
(c1, c2)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user