diff --git a/Cargo.lock b/Cargo.lock index f343af7..fcb706f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" dependencies = [ "backtrace", ] @@ -1040,6 +1040,7 @@ checksum = "739e9d7726dc32173fed2d69d17eef3c54682169e4e20ff1d0a45dcd37063cef" [[package]] name = "libpso" version = "0.1.0" +source = "git+http://git.sharnoth.com/jake/libpso#5051514fb1d3b39a7eb6ff97b624a9ceebd93e40" dependencies = [ "chrono", "psopacket", @@ -1398,6 +1399,7 @@ dependencies = [ [[package]] name = "psopacket" version = "1.0.0" +source = "git+http://git.sharnoth.com/jake/libpso#5051514fb1d3b39a7eb6ff97b624a9ceebd93e40" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e746d70..d62e110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,4 @@ refinery = { version = "0.5.0", features = ["postgres"] } sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] } strum = "0.19.5" strum_macros = "0.19" -anyhow = { version = "1.0.47", features = ["backtrace"] } +anyhow = { version = "1.0.68", features = ["backtrace"] } diff --git a/src/bin/main.rs b/src/bin/main.rs index eb7c687..9ff056b 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -52,7 +52,7 @@ fn main() { for i in 0..5 { let fake_user = NewUserAccountEntity { - email: format!("fake{}@email.com", i), + email: format!("fake{i}@email.com"), username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) }, password: bcrypt::hash("qwer", 5).unwrap(), guildcard: i + 1, @@ -64,12 +64,12 @@ fn main() { }; let fake_user = entity_gateway.create_user(fake_user).await.unwrap(); entity_gateway.create_user_settings(NewUserSettingsEntity::new(fake_user.id)).await.unwrap(); - let mut character = NewCharacterEntity::new(fake_user.id, 1); + let mut character = NewCharacterEntity::new(fake_user.id); character.name = format!("Test Char {}", i*2); let character = entity_gateway.create_character(character).await.unwrap(); entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap(); entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap(); - let mut character = NewCharacterEntity::new(fake_user.id, 1); + let mut character = NewCharacterEntity::new(fake_user.id); character.slot = 2; character.name = "ItemRefactor".into(); character.exp = 80000000; diff --git a/src/common/mainloop/client.rs b/src/common/mainloop/client.rs index 1d24600..0ace697 100644 --- a/src/common/mainloop/client.rs +++ b/src/common/mainloop/client.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; use std::fmt::Debug; +use std::io::Write; use async_std::channel; use async_std::io::prelude::{ReadExt, WriteExt}; use async_std::sync::{Arc, RwLock}; use futures::future::Future; -use log::{trace, info, warn}; +use log::{trace, info, warn, error}; use libpso::crypto::{PSOCipher, NullCipher, CipherError}; use libpso::PacketParseError; @@ -132,7 +133,7 @@ where match pkt_receiver.recv_pkts::().await { Ok(pkts) => { for pkt in pkts { - info!("[recv from {:?}] {:#?}", client_id, pkt); + trace!("[recv from {:?}] {:#?}", client_id, pkt); match state.handle(client_id, pkt).await { Ok(response) => { for resp in response { @@ -147,7 +148,27 @@ where } }, Err(err) => { - warn!("[client recv {:?}] error {:?} ", client_id, err); + error!("[client recv {:?}] error {:?} ", client_id, err); + + let mut f = std::fs::File::options().create(true).append(true).open("errors.txt").unwrap(); + f.write_all(format!("[{client_id:?}] {err:?}").as_bytes()).unwrap(); + + // disconnect client on an error + for pkt in state.on_disconnect(client_id).await.unwrap() { + clients + .read() + .await + .get(&pkt.0) + .unwrap() + .send(pkt.1) + .await + .unwrap(); + } + clients + .write() + .await + .remove(&client_id); + break; } } } @@ -173,7 +194,7 @@ where break; } _ => { - warn!("[client {:?} recv error] {:?}", client_id, err); + error!("[client {:?} recv error] {:?}", client_id, err); } } } @@ -206,7 +227,7 @@ where Ok(pkt) => { info!("[send to {:?}] {:#?}", client_id, pkt); if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await { - warn!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err); + error!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err); } }, Err(err) => { diff --git a/src/entity/character.rs b/src/entity/character.rs index bb5a6d0..080bb1b 100644 --- a/src/entity/character.rs +++ b/src/entity/character.rs @@ -2,8 +2,8 @@ use std::convert::{From, Into}; use std::collections::HashMap; use serde::{Serialize, Deserialize}; -use libpso::packet::ship::{UpdateConfig, WriteInfoboard, KeyboardConfig, GamepadConfig}; -use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU, DEFAULT_KEYBOARD_CONFIG1, DEFAULT_KEYBOARD_CONFIG2, DEFAULT_KEYBOARD_CONFIG3, DEFAULT_KEYBOARD_CONFIG4, DEFAULT_GAMEPAD_CONFIG}; +use libpso::packet::ship::{UpdateConfig, WriteInfoboard}; +use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU}; use crate::entity::item::tech::Technique; use crate::entity::account::UserAccountId; @@ -157,7 +157,7 @@ pub struct CharacterAppearance { } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TechLevel(pub u8); #[derive(Clone, Debug, Default)] @@ -167,16 +167,14 @@ pub struct CharacterTechniques { impl CharacterTechniques { pub fn set_tech(&mut self, tech: Technique, level: TechLevel) { - self.techs.insert(tech, TechLevel(level.0 - 1)); + self.techs.insert(tech, TechLevel(level.0)); } - // from_bytes - pub fn as_bytes(&self) -> [u8; 20] { self.techs.iter() .fold([0xFF; 20], |mut techlist, (tech, level)| { let index = tech.as_value(); - techlist[index as usize] = level.0; + techlist[index as usize] = level.0 - 1; techlist }) } @@ -264,82 +262,6 @@ pub struct CharacterMaterials { pub tp: u32, } -#[derive(Clone, Debug)] -pub struct CharacterKeyboardConfig { - pub keyboard_config: [u8; 0x16C], -} - -impl Default for CharacterKeyboardConfig { - fn default() -> CharacterKeyboardConfig { - CharacterKeyboardConfig { - keyboard_config: DEFAULT_KEYBOARD_CONFIG1, - } - } -} - -impl CharacterKeyboardConfig { - fn new(preset: usize) -> CharacterKeyboardConfig { - match preset { - 1 => { - CharacterKeyboardConfig { - keyboard_config: DEFAULT_KEYBOARD_CONFIG1, - } - }, - 2 => { - CharacterKeyboardConfig { - keyboard_config: DEFAULT_KEYBOARD_CONFIG2, - } - }, - 3 => { - CharacterKeyboardConfig { - keyboard_config: DEFAULT_KEYBOARD_CONFIG3, - } - }, - 4 => { - CharacterKeyboardConfig { - keyboard_config: DEFAULT_KEYBOARD_CONFIG4, - } - }, - _ => { - CharacterKeyboardConfig { - keyboard_config: DEFAULT_KEYBOARD_CONFIG1, - } - }, - } - } - - pub fn update(&mut self, new_config: &KeyboardConfig) { - self.keyboard_config = new_config.keyboard_config; - } - - pub fn as_bytes(&self) -> [u8; 0x16C] { - self.keyboard_config - } -} - -#[derive(Clone, Debug)] -pub struct CharacterGamepadConfig { - pub gamepad_config: [u8; 0x38], -} - -impl Default for CharacterGamepadConfig { - fn default() -> CharacterGamepadConfig { - CharacterGamepadConfig { - gamepad_config: DEFAULT_GAMEPAD_CONFIG, - } - } -} - -impl CharacterGamepadConfig { - pub fn update(&mut self, new_config: &GamepadConfig) { - self.gamepad_config = new_config.gamepad_config; - } - - pub fn as_bytes(&self) -> [u8; 0x38] { - self.gamepad_config - } -} - #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)] pub struct CharacterEntityId(pub u32); @@ -363,12 +285,10 @@ pub struct NewCharacterEntity { pub tech_menu: CharacterTechMenu, pub option_flags: u32, - pub keyboard_config: CharacterKeyboardConfig, - pub gamepad_config: CharacterGamepadConfig, } impl NewCharacterEntity { - pub fn new(user: UserAccountId, keyboard_config_preset: usize,) -> NewCharacterEntity { + pub fn new(user: UserAccountId) -> NewCharacterEntity { NewCharacterEntity { user_id: user, slot: 0, @@ -384,8 +304,6 @@ impl NewCharacterEntity { materials: CharacterMaterials::default(), tech_menu: CharacterTechMenu::default(), option_flags: 0, - keyboard_config: CharacterKeyboardConfig::new(keyboard_config_preset), - gamepad_config: CharacterGamepadConfig::default(), } } } @@ -411,8 +329,6 @@ pub struct CharacterEntity { pub tech_menu: CharacterTechMenu, pub option_flags: u32, - pub keyboard_config: CharacterKeyboardConfig, - pub gamepad_config: CharacterGamepadConfig, pub playtime: u32, } diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index 75518e1..a8c47dc 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -1,4 +1,3 @@ -use std::convert::From; use thiserror::Error; use futures::Future; @@ -10,9 +9,10 @@ use crate::entity::item::*; // TODO: better granularity? //#[derive(Error, Debug)] #[derive(Error, Debug)] -#[error("")] pub enum GatewayError { + #[error("unknown error")] Error, + #[error("postgres error {0}")] PgError(#[from] sqlx::Error) } @@ -21,12 +21,11 @@ pub enum GatewayError { pub trait EntityGateway: Send + Sync { type Transaction: EntityGatewayTransaction + Clone; - async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result + async fn with_transaction<'a, F, Fut, R>(&'a mut self, _func: F) -> Result where - Fut: Future> + Send + 'a, + Fut: Future> + Send + 'a, F: FnOnce(Self::Transaction) -> Fut + Send, R: Send, - E: From, Self: Sized { unimplemented!(); diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index a813a93..88636ec 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -72,6 +72,10 @@ impl EntityGateway for InMemoryGatewayTransaction { } } + async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> { + self.original_gateway.save_user_settings(settings).await + } + async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> { copy_if_needed(&mut *self.working_gateway.characters.lock().await, &*self.original_gateway.characters.lock().await, @@ -304,12 +308,11 @@ fn apply_modifiers(items: &BTreeMap, impl EntityGateway for InMemoryGateway { type Transaction = InMemoryGatewayTransaction; - async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result + async fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> Result where - Fut: Future> + Send + 'a, + Fut: Future> + Send + 'a, F: FnOnce(Self::Transaction) -> Fut + Send, R: Send, - E: From, { let users = self.users.lock().await.clone(); let user_settings = self.user_settings.lock().await.clone(); @@ -419,6 +422,12 @@ impl EntityGateway for InMemoryGateway { .ok_or(GatewayError::Error) } + async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> { + let mut user_settings = self.user_settings.lock().await; + user_settings.insert(settings.id, settings.clone()); + Ok(()) + } + async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { let characters = self.characters.lock().await; const NONE: Option = None; @@ -453,8 +462,6 @@ impl EntityGateway for InMemoryGateway { materials: character.materials, tech_menu: character.tech_menu, option_flags: character.option_flags, - keyboard_config: character.keyboard_config, - gamepad_config: character.gamepad_config, playtime: 0, }; characters.insert(new_character.id, new_character.clone()); diff --git a/src/entity/gateway/postgres/migrations/V0004__meseta.sql b/src/entity/gateway/postgres/migrations/V0004__meseta.sql index 97f6b2e..efca1db 100644 --- a/src/entity/gateway/postgres/migrations/V0004__meseta.sql +++ b/src/entity/gateway/postgres/migrations/V0004__meseta.sql @@ -1,10 +1,10 @@ create table character_meseta ( - pchar integer references character (id) not null unique, - meseta integer not null, + pchar integer references player_character (id) not null unique, + meseta integer not null ); create table bank_meseta ( - pchar integer references character (id) not null, + pchar integer references player_character (id) not null, bank varchar(128) not null, meseta integer not null, unique (pchar, bank) @@ -12,4 +12,5 @@ create table bank_meseta ( alter table player_character - drop column meseta, bank_meseta; + drop column meseta, + drop column bank_meseta; diff --git a/src/entity/gateway/postgres/migrations/V0005__trade.sql b/src/entity/gateway/postgres/migrations/V0005__trade.sql index 16d3b84..4043c6b 100644 --- a/src/entity/gateway/postgres/migrations/V0005__trade.sql +++ b/src/entity/gateway/postgres/migrations/V0005__trade.sql @@ -1,5 +1,5 @@ create table trades ( id serial primary key not null, - character1 integer references character (id) not null, - character2 integer references character (id) not null, + character1 integer references player_character (id) not null, + character2 integer references player_character (id) not null ); diff --git a/src/entity/gateway/postgres/migrations/V0007__player_keyconfig.sql b/src/entity/gateway/postgres/migrations/V0007__player_keyconfig.sql new file mode 100644 index 0000000..23067d5 --- /dev/null +++ b/src/entity/gateway/postgres/migrations/V0007__player_keyconfig.sql @@ -0,0 +1,3 @@ +alter table player_character + add keyboard_config bytea not null, + add gamepad_config bytea not null; diff --git a/src/entity/gateway/postgres/migrations/V0008__playtime_not_null.sql b/src/entity/gateway/postgres/migrations/V0008__playtime_not_null.sql new file mode 100644 index 0000000..60e689b --- /dev/null +++ b/src/entity/gateway/postgres/migrations/V0008__playtime_not_null.sql @@ -0,0 +1,5 @@ +alter table player_character + drop column playtime; + +alter table player_character + add playtime integer not null; diff --git a/src/entity/gateway/postgres/migrations/V0009__no_player_keyconfig.sql b/src/entity/gateway/postgres/migrations/V0009__no_player_keyconfig.sql new file mode 100644 index 0000000..b70d210 --- /dev/null +++ b/src/entity/gateway/postgres/migrations/V0009__no_player_keyconfig.sql @@ -0,0 +1,3 @@ +alter table player_character + drop column keyboard_config, + drop column gamepad_config; diff --git a/src/entity/gateway/postgres/migrations/V0010__char_create_timestamp.sql b/src/entity/gateway/postgres/migrations/V0010__char_create_timestamp.sql new file mode 100644 index 0000000..5ba1fd0 --- /dev/null +++ b/src/entity/gateway/postgres/migrations/V0010__char_create_timestamp.sql @@ -0,0 +1,2 @@ +alter table player_character + add created_at timestamptz default current_timestamp not null; diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index c7a378b..c310dda 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -49,8 +49,8 @@ pub struct PgUserSettings { id: i32, user_account: i32, blocked_users: Vec, //[u32; 0x1E], - keyboard_config: Vec, //[u8; 0x16C], - gamepad_config: Vec, //[u8; 0x38], + key_config: Vec, //[u8; 0x16C], + joystick_config: Vec, //[u8; 0x38], option_flags: i32, shortcuts: Vec, //[u8; 0xA40], symbol_chats: Vec, //[u8; 0x4E0], @@ -64,8 +64,8 @@ impl From for UserSettingsEntity { user_id: UserAccountId(other.user_account as u32), settings: settings::UserSettings { blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()), - keyboard_config: vec_to_array(other.keyboard_config), - gamepad_config: vec_to_array(other.gamepad_config), + keyboard_config: vec_to_array(other.key_config), + gamepad_config: vec_to_array(other.joystick_config), option_flags: other.option_flags as u32, shortcuts: vec_to_array(other.shortcuts), symbol_chats: vec_to_array(other.symbol_chats), @@ -217,8 +217,6 @@ pub struct PgCharacter { tp: i16, tech_menu: Vec, - keyboard_config: Vec, - gamepad_config: Vec, playtime: i32, } @@ -246,7 +244,7 @@ impl From for CharacterEntity { prop_y: other.prop_y, }, techs: CharacterTechniques { - techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect() + techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t + 1)) ).collect() }, config: CharacterConfig { raw_data: vec_to_array(other.config) @@ -270,12 +268,6 @@ impl From for CharacterEntity { tech_menu: CharacterTechMenu { tech_menu: vec_to_array(other.tech_menu) }, - keyboard_config: CharacterKeyboardConfig { - keyboard_config: vec_to_array(other.keyboard_config) - }, - gamepad_config: CharacterGamepadConfig { - gamepad_config: vec_to_array(other.gamepad_config) - }, playtime: other.playtime as u32, } } diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs index d6ab8ee..dbc49d4 100644 --- a/src/entity/gateway/postgres/postgres.rs +++ b/src/entity/gateway/postgres/postgres.rs @@ -2,7 +2,7 @@ #![allow(clippy::explicit_auto_deref)] use std::convert::{From, TryFrom, Into}; -use futures::{Future, TryStreamExt}; +use futures::Future; use async_std::stream::StreamExt; use async_std::sync::{Arc, Mutex}; use libpso::character::guildcard; @@ -67,7 +67,7 @@ impl<'t> PostgresGateway<'t> { let pool = async_std::task::block_on(async move { PgPoolOptions::new() .max_connections(5) - .connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap() + .connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap() }); PostgresGateway { @@ -217,17 +217,26 @@ async fn save_user_settings(conn: &mut sqlx::PgConnection, settings: &UserSettin .bind(&settings.settings.symbol_chats.to_vec()) .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) .bind(settings.id.0) - .fetch_one(conn).await?; + .execute(conn).await?; Ok(()) } async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result { let q = r#"insert into player_character - (user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs, - config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags) + (user_account, slot, name, exp, class, + section_id, costume, skin, face, head, + hair, hair_r, hair_g, hair_b, prop_x, + prop_y, techs, config, infoboard, guildcard, + power, mind, def, evade, luck, + hp, tp, tech_menu, option_flags, playtime) values - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31) + ($1, $2, $3, $4, $5, + $6, $7, $8, $9, $10, + $11, $12, $13, $14, $15, + $16, $17, $18, $19, $20, + $21, $22, $23, $24, $25, + $26, $27, $28, $29, $30) returning *;"#; let character = sqlx::query_as::<_, PgCharacter>(q) .bind(char.user_id.0) @@ -259,6 +268,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit .bind(char.materials.tp as i16) .bind(char.tech_menu.tech_menu.to_vec()) .bind(char.option_flags as i32) + .bind(0) .fetch_one(conn).await?; Ok(character.into()) @@ -266,17 +276,17 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { - let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot") + let stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by created_at;") .bind(user.id.0) .fetch(conn); - const NONE: Option = None; - let mut result = [NONE; 4]; - while let Some(character) = stream.try_next().await? { - let index = character.slot as usize; - result[index] = Some(character.into()) - } - Ok(result) + Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| { + if let Ok(char) = char { + let slot = char.slot as usize; + acc[slot] = Some(char.into()) + } + acc + }).await) } async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -> Result<(), GatewayError> @@ -285,7 +295,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) - user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12, hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23, evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29, playtime=$30 - where id=$31;"#; + where id=$31;"#; sqlx::query(q) .bind(char.user_id.0) // $1 .bind(char.slot as i16) // $2 @@ -316,7 +326,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) - .bind(char.materials.tp as i16) // $27 .bind(char.tech_menu.tech_menu.to_vec()) // $28 .bind(char.option_flags as i32) // $29 - .bind(char.playtime as i32) // $20 + .bind(char.playtime as i32) // $30 .bind(char.id.0 as i32) // $31 .execute(conn).await?; Ok(()) @@ -530,7 +540,7 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> { - sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2") + sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2") .bind(char_id.0) .bind(meseta.0 as i32) .execute(conn) @@ -542,7 +552,7 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character { #[derive(sqlx::FromRow)] struct PgMeseta(i32); - let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#) + let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#) .bind(char_id.0) .fetch_one(conn) .await?; @@ -551,10 +561,10 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> { - sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2") + sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set meseta = $3") .bind(char_id.0) - .bind(meseta.0 as i32) .bind(bank.0.clone()) + .bind(meseta.0 as i32) .execute(conn) .await?; Ok(()) @@ -564,7 +574,7 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit { #[derive(sqlx::FromRow)] struct PgMeseta(i32); - let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1 and bank = $2"#) + let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1 and bank = $2"#) .bind(char_id.0) .bind(bank.0.clone()) .fetch_one(conn) @@ -584,10 +594,10 @@ async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityI async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> { - sqlx::query_as::<_, PgTradeEntity>(r#"update player_character set playtime=$2 where id=$1;"#) + sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#) .bind(char_id.0) .bind(playtime) - .fetch_one(conn) + .execute(conn) .await?; Ok(()) } @@ -596,18 +606,17 @@ async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &Charact impl<'t> EntityGateway for PostgresGateway<'t> { type Transaction = PostgresTransaction<'t>; - async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result + async fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> Result where - Fut: Future> + Send + 'a, + Fut: Future> + Send + 'a, F: FnOnce(Self::Transaction) -> Fut + Send, R: Send, - E: From, { let transaction = PostgresTransaction { - pgtransaction: Arc::new(Mutex::new(self.pool.begin().await.map_err(|_| ()).unwrap())) + pgtransaction: Arc::new(Mutex::new(self.pool.begin().await?)) }; - let (transaction, result) = func(transaction).await.map_err(|_| ()).unwrap(); - transaction.commit().await.map_err(|_| ()).unwrap(); + let (transaction, result) = func(transaction).await?; + transaction.commit().await?; Ok(result) } diff --git a/src/entity/item/mag.rs b/src/entity/item/mag.rs index dbdb4a7..17fa1b4 100644 --- a/src/entity/item/mag.rs +++ b/src/entity/item/mag.rs @@ -521,7 +521,7 @@ pub enum MagCellError { #[derive(Debug, Clone, PartialEq, Eq)] pub enum MagModifier { - FeedMag{ + FeedMag { food: ItemEntityId, }, BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags diff --git a/src/lib.rs b/src/lib.rs index 176a9fe..2c3d9c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ +#![allow(clippy::type_complexity)] #![allow(incomplete_features)] #![feature(inline_const)] #![feature(drain_filter)] #![feature(try_blocks)] #![feature(once_cell)] -#![feature(pin_macro)] #![feature(test)] +#![feature(error_generic_member_access)] +#![feature(provide_any)] extern crate test; diff --git a/src/login/character.rs b/src/login/character.rs index b6e28ab..349d1dd 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -38,13 +38,18 @@ pub const CHARACTER_PORT: u16 = 12001; pub const SHIP_MENU_ID: u32 = 1; #[derive(thiserror::Error, Debug)] -#[error("")] pub enum CharacterError { + #[error("invalid menu selection {0} {1}")] InvalidMenuSelection(u32, u32), + #[error("client not found {0}")] ClientNotFound(ClientId), - CouldNotLoadSettings, + #[error("could not load settings {0}")] + CouldNotLoadSettings(GatewayError), + #[error("could not load characters")] CouldNotLoadCharacters, + #[error("could not load guildcard")] CouldNotLoadGuildcard, + #[error("gateway error {0}")] GatewayError(#[from] GatewayError), } @@ -206,6 +211,7 @@ async fn new_character(entity_gateway: &mut EG, user: let character = entity_gateway.create_character(character).await?; entity_gateway.set_character_meseta(&character.id, Meseta(300)).await?; + entity_gateway.set_bank_meseta(&character.id, &BankName("".into()), Meseta(0)).await?; let new_weapon = match character.char_class { CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber, @@ -260,6 +266,8 @@ async fn new_character(entity_gateway: &mut EG, user: character_id: character.id, }).await?; + entity_gateway.change_mag_owner(&mag.id, &character).await?; + let mut monomates = Vec::new(); for _ in 0..4usize { let monomate = entity_gateway.create_item( @@ -385,7 +393,7 @@ impl CharacterServerState { Ok(settings) => settings, Err(_) => { let user_settings = NewUserSettingsEntity::new(user.id); - self.entity_gateway.create_user_settings(user_settings).await.map_err(|_| CharacterError::CouldNotLoadSettings)? + self.entity_gateway.create_user_settings(user_settings).await.map_err(CharacterError::CouldNotLoadSettings)? } }; @@ -741,7 +749,7 @@ impl InterserverActor for CharacterServerState { fn new_character_from_preview(user: &UserAccountEntity, preview: &CharacterPreview) -> NewCharacterEntity { - let mut character = NewCharacterEntity::new(user.id, 1); // it should not be possible for the client to specify the kbm config preset from the char create screen + let mut character = NewCharacterEntity::new(user.id); character.slot = preview.slot; character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into(); character.section_id = preview.character.section_id.into(); diff --git a/src/login/login.rs b/src/login/login.rs index e37b0ff..bce909a 100644 --- a/src/login/login.rs +++ b/src/login/login.rs @@ -21,8 +21,8 @@ pub const LOGIN_PORT: u16 = 12000; pub const COMMUNICATION_PORT: u16 = 12123; #[derive(thiserror::Error, Debug)] -#[error("")] pub enum LoginError { + #[error("dberror")] DbError } diff --git a/src/patch/patch.rs b/src/patch/patch.rs index 230c5bf..26441f8 100644 --- a/src/patch/patch.rs +++ b/src/patch/patch.rs @@ -395,18 +395,18 @@ pub struct PatchConfig { pub fn load_config() -> PatchConfig { let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) { - Err(err) => panic!("Failed to open patch.ron config file. \n{}", err), + Err(err) => panic!("Failed to open patch.ron config file. \n{err}"), Ok(ini_file) => ini_file, }; let mut s = String::new(); if let Err(err) = (&ini_file).read_to_string(&mut s) { - panic!("Failed to read patch.ron config file. \n{}", err); + panic!("Failed to read patch.ron config file. \n{err}"); } let config: PatchConfig = match from_str(s.as_str()) { Ok(config) => config, - Err(err) => panic!("Failed to load values from patch.ron \n{}",err), + Err(err) => panic!("Failed to load values from patch.ron \n{err}"), }; config } diff --git a/src/ship/client.rs b/src/ship/client.rs index 795c880..5495309 100644 --- a/src/ship/client.rs +++ b/src/ship/client.rs @@ -36,7 +36,7 @@ impl Clients { .into_inner()) } - pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result + pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result where T: Send, F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a, @@ -53,7 +53,7 @@ impl Clients { Ok(func(&client).await) } - pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result + pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result where T: Send, F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a, @@ -85,7 +85,7 @@ impl Clients { Ok(func(client_states).await) } - pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result + pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result where T: Send, F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a, diff --git a/src/ship/drops/mod.rs b/src/ship/drops/mod.rs index 5e9747f..5d8062c 100644 --- a/src/ship/drops/mod.rs +++ b/src/ship/drops/mod.rs @@ -6,7 +6,7 @@ mod drop_table; -mod rare_drop_table; +pub mod rare_drop_table; mod generic_weapon; mod generic_armor; mod generic_shield; @@ -112,7 +112,7 @@ pub struct ItemDrop { } -pub struct DropTable { +pub struct DropTable { monster_stats: HashMap, rare_table: RareDropTable, weapon_table: GenericWeaponTable, @@ -121,11 +121,11 @@ pub struct DropTable { unit_table: GenericUnitTable, tool_table: ToolTable, box_table: BoxDropTable, - rng: R, + rng: rand_chacha::ChaCha20Rng, } -impl DropTable { - pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { +impl DropTable { + pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { let monster_stats: HashMap = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); DropTable { @@ -137,7 +137,21 @@ impl DropTable { unit_table: GenericUnitTable::new(episode, difficulty, section_id), tool_table: ToolTable::new(episode, difficulty, section_id), box_table: BoxDropTable::new(episode, difficulty, section_id), - rng: R::from_entropy(), + rng: rand_chacha::ChaCha20Rng::from_entropy(), + } + } + + pub fn builder() -> DropTableBuilder { + DropTableBuilder { + monster_stats: None, + rare_table: None, + weapon_table: None, + armor_table: None, + shield_table: None, + unit_table: None, + tool_table: None, + box_table: None, + rng: None, } } @@ -189,6 +203,66 @@ impl DropTable { } +pub struct DropTableBuilder { + monster_stats: Option>, + rare_table: Option, + weapon_table: Option, + armor_table: Option, + shield_table: Option, + unit_table: Option, + tool_table: Option, + box_table: Option, + rng: Option, +} + +// TODO: add the rest of these later I just need these ones right now +impl DropTableBuilder { + #[must_use] + pub fn monster_stats(mut self, monster_stats: HashMap) -> DropTableBuilder { + self.monster_stats = Some(monster_stats); + self + } + + #[must_use] + pub fn monster_stat(mut self, monster_type: MonsterType, drop_stats: MonsterDropStats) -> DropTableBuilder { + match &mut self.monster_stats { + Some(monster_stats) => { + monster_stats.insert(monster_type, drop_stats); + }, + None => { + let mut monster_stats = HashMap::default(); + monster_stats.insert(monster_type, drop_stats); + self.monster_stats = Some(monster_stats); + } + } + self + } + + #[must_use] + pub fn rare_table(mut self, rare_table: RareDropTable) -> DropTableBuilder { + self.rare_table = Some(rare_table); + self + } + + pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { + DropTable { + monster_stats: self.monster_stats.unwrap_or_else(|| { + let monster_stats: HashMap = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); + monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect() + }), + rare_table: self.rare_table.unwrap_or_else(|| RareDropTable::new(episode, difficulty, section_id)), + weapon_table: self.weapon_table.unwrap_or_else(|| GenericWeaponTable::new(episode, difficulty, section_id)), + armor_table: self.armor_table.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)), + shield_table: self.shield_table.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)), + unit_table: self.unit_table.unwrap_or_else(|| GenericUnitTable::new(episode, difficulty, section_id)), + tool_table: self.tool_table.unwrap_or_else(|| ToolTable::new(episode, difficulty, section_id)), + box_table: self.box_table.unwrap_or_else(|| BoxDropTable::new(episode, difficulty, section_id)), + rng: self.rng.unwrap_or_else(rand_chacha::ChaCha20Rng::from_entropy), + } + } +} + + #[cfg(test)] mod test { use super::*; @@ -203,6 +277,6 @@ mod test { let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum, SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill] .into_iter().choose(&mut rng).unwrap(); - DropTable::::new(episode, difficulty, section_id); + DropTable::new(episode, difficulty, section_id); } } diff --git a/src/ship/drops/rare_drop_table.rs b/src/ship/drops/rare_drop_table.rs index 5d5f4c8..51151ba 100644 --- a/src/ship/drops/rare_drop_table.rs +++ b/src/ship/drops/rare_drop_table.rs @@ -50,9 +50,9 @@ impl RareDropItem { } -struct RareDropRate { - rate: f32, - item: RareDropItem +pub struct RareDropRate { + pub rate: f32, + pub item: RareDropItem } @@ -71,30 +71,41 @@ pub struct RareDropTable { shield_stats: GenericShieldTable, } -impl RareDropTable { - pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable { - let cfg: HashMap> = load_data_file(episode, difficulty, section_id, "rare_rate.toml"); - - let rates = cfg.into_iter() - .map(|(monster, drops)| { - let monster = monster.parse().unwrap(); - let drops = drops.into_iter().map(|drop| { - RareDropRate { - rate: drop.rate, - item: RareDropItem::from_string(drop.item), - } - }).collect(); - (monster, drops) +fn load_default_monster_rates(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> HashMap> { + let cfg: HashMap> = load_data_file(episode, difficulty, section_id, "rare_rate.toml"); + + cfg.into_iter() + .map(|(monster, drops)| { + let monster = monster.parse().unwrap(); + let drops = drops.into_iter().map(|drop| { + RareDropRate { + rate: drop.rate, + item: RareDropItem::from_string(drop.item), + } }).collect(); + (monster, drops) + }).collect() +} +impl RareDropTable { + pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable { RareDropTable { - rates, + rates: load_default_monster_rates(episode, difficulty, section_id), attribute_table: AttributeTable::new(episode, difficulty, section_id), armor_stats: GenericArmorTable::new(episode, difficulty, section_id), shield_stats: GenericShieldTable::new(episode, difficulty, section_id), } } + pub fn builder() -> RareDropTableBuilder { + RareDropTableBuilder { + rates: None, + attribute_table: None, + armor_stats: None, + shield_stats: None, + } + } + pub fn apply_item_stats(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType { match item { RareDropItem::Weapon(weapon) => { @@ -155,3 +166,46 @@ impl RareDropTable { }) } } + + +pub struct RareDropTableBuilder { + rates: Option>>, + attribute_table: Option, + armor_stats: Option, + shield_stats: Option, +} + + +// TODO: add the rest of these later I just need these ones right now +impl RareDropTableBuilder { + pub fn rates(mut self, rates: HashMap>) -> RareDropTableBuilder { + self.rates = Some(rates); + self + } + + #[must_use] + pub fn rate(mut self, monster_type: MonsterType, drop_rate: RareDropRate) -> RareDropTableBuilder { + match &mut self.rates { + Some(rates) => { + rates.entry(monster_type) + .or_insert(Vec::new()) + .push(drop_rate); + }, + None => { + let mut rates = HashMap::default(); + rates.insert(monster_type, vec![drop_rate]); + self.rates = Some(rates); + } + } + self + } + + pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable { + RareDropTable { + rates: self.rates.unwrap_or_else(|| load_default_monster_rates(episode, difficulty, section_id)), + attribute_table: self.attribute_table.unwrap_or_else(|| AttributeTable::new(episode, difficulty, section_id)), + armor_stats: self.armor_stats.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)), + shield_stats: self.shield_stats.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)), + } + } +} diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index c6875ef..be6748b 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -35,7 +35,7 @@ pub(super) fn take_item_from_floor( character_id: CharacterEntityId, item_id: ClientItemId ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway + Send, TR: EntityGatewayTransaction + 'static, @@ -54,7 +54,7 @@ where pub(super) fn add_floor_item_to_inventory( character: &CharacterEntity ) -> impl Fn((ItemStateProxy, TR), FloorItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + Clone + 'static, @@ -103,7 +103,7 @@ pub(super) fn take_item_from_inventory( item_id: ClientItemId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -111,7 +111,7 @@ where move |(mut item_state, mut transaction), _| { Box::pin(async move { let mut inventory = item_state.inventory(&character_id).await?; - let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoFloorItem(item_id))?; + let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoInventoryItem(item_id))?; transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; item_state.set_inventory(inventory).await; @@ -127,7 +127,7 @@ pub(super) fn add_inventory_item_to_shared_floor( map_area: MapArea, drop_position: (f32, f32, f32), ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -160,7 +160,7 @@ pub(super) fn take_meseta_from_inventory( character_id: CharacterEntityId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -181,7 +181,7 @@ pub(super) fn add_meseta_to_inventory( character_id: CharacterEntityId, amount: u32 ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -204,7 +204,7 @@ pub(super) fn add_meseta_to_shared_floor( map_area: MapArea, drop_position: (f32, f32) ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -233,7 +233,7 @@ pub(super) fn take_meseta_from_bank( character_id: CharacterEntityId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -243,6 +243,7 @@ where let mut bank = item_state.bank(&character_id).await?; bank.remove_meseta(amount)?; transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; + item_state.set_bank(bank).await; Ok(((item_state, transaction), ())) }) @@ -253,7 +254,7 @@ pub(super) fn add_meseta_from_bank_to_inventory( character_id: CharacterEntityId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -274,7 +275,7 @@ pub(super) fn add_meseta_to_bank( character_id: CharacterEntityId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -284,6 +285,7 @@ where let mut bank = item_state.bank(&character_id).await?; bank.add_meseta(amount)?; transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; + item_state.set_bank(bank).await; Ok(((item_state, transaction), ())) }) @@ -296,7 +298,7 @@ pub(super) fn take_item_from_bank( item_id: ClientItemId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -316,7 +318,7 @@ where pub(super) fn add_bank_item_to_inventory( character: &CharacterEntity, ) -> impl Fn((ItemStateProxy, TR), BankItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -367,7 +369,7 @@ where pub(super) fn add_inventory_item_to_bank( character_id: CharacterEntityId, ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -403,7 +405,7 @@ pub(super) fn equip_inventory_item( item_id: ClientItemId, equip_slot: u8, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -425,7 +427,7 @@ pub(super) fn unequip_inventory_item( character_id: CharacterEntityId, item_id: ClientItemId, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -448,7 +450,7 @@ pub(super) fn sort_inventory_items( character_id: CharacterEntityId, item_ids: Vec, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -470,7 +472,7 @@ where pub(super) fn use_consumed_item( character: &CharacterEntity, ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture), ItemStateError>> + -> BoxFuture), anyhow::Error>> where EG: EntityGateway + Clone + 'static, TR: EntityGatewayTransaction + 'static, @@ -497,7 +499,7 @@ pub(super) fn feed_mag_item( character: CharacterEntity, mag_item_id: ClientItemId, ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -551,7 +553,7 @@ pub(super) fn add_bought_item_to_inventory<'a, EG, TR>( item_id: ClientItemId, amount: u32, ) -> impl Fn((ItemStateProxy, TR), ()) - -> Pin> + Send + 'a>> + -> Pin> + Send + 'a>> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -613,7 +615,7 @@ where pub(super) fn sell_inventory_item( character_id: CharacterEntityId, ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -643,7 +645,7 @@ async fn iterate_inner<'a, EG, TR, I, O, T, F, FR>( mut input: Vec, func: F, arg: T, -) -> Result<((ItemStateProxy, TR), Vec), ItemStateError> +) -> Result<((ItemStateProxy, TR), Vec), anyhow::Error> where 'a: 'async_recursion, EG: EntityGateway, @@ -653,7 +655,7 @@ where T: Clone + Send + Sync, F: Fn(I) -> FR + Send + Sync + Clone + 'static, FR: Fn((ItemStateProxy, TR), T) - -> BoxFuture> + Send + Sync, + -> BoxFuture> + Send + Sync, { let item = match input.pop() { Some(item) => item, @@ -673,7 +675,7 @@ pub(super) fn iterate( input: Vec, func: F, ) -> impl Fn((ItemStateProxy, TR), T) - -> BoxFuture), ItemStateError>> + -> BoxFuture), anyhow::Error>> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -682,7 +684,7 @@ where T: Send + Clone + 'static + std::fmt::Debug, F: Fn(I) -> FR + Send + Sync + Clone + 'static, FR: Fn((ItemStateProxy, TR), T) - -> BoxFuture> + Send + Sync, + -> BoxFuture> + Send + Sync, T: Clone + Send + Sync, { move |(item_state, transaction), arg| { @@ -701,7 +703,7 @@ async fn foreach_inner<'a, EG, TR, O, T, F, I>( state: (ItemStateProxy, TR), mut input: I, func: Arc, -) -> Result<((ItemStateProxy, TR), Vec), ItemStateError> +) -> Result<((ItemStateProxy, TR), Vec), anyhow::Error> where 'a: 'async_recursion, EG: EntityGateway, @@ -709,7 +711,7 @@ where O: Send, T: Send, F: Fn((ItemStateProxy, TR), T) - -> BoxFuture> + Send + Sync, + -> BoxFuture> + Send + Sync, I: Iterator + Send + Sync + 'static, { let item = match input.next() { @@ -728,14 +730,14 @@ where pub(super) fn foreach( func: F ) -> impl Fn((ItemStateProxy, TR), I) - -> BoxFuture), ItemStateError>> + -> BoxFuture), anyhow::Error>> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, O: Send, T: Send + Clone + 'static + std::fmt::Debug, F: Fn((ItemStateProxy, TR), T) - -> BoxFuture> + Send + Sync + 'static, + -> BoxFuture> + Send + Sync + 'static, T: Send + Sync, I: IntoIterator + Send + Sync + 'static, I::IntoIter: Send + Sync, @@ -754,7 +756,7 @@ where pub(super) fn insert<'a, EG, TR, T>( element: T ) -> impl Fn((ItemStateProxy, TR), ()) - -> Pin> + Send + 'a>> + -> Pin> + Send + 'a>> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -772,12 +774,12 @@ pub(super) fn fork( func1: F1, func2: F2, ) -> impl Fn((ItemStateProxy, TR), T) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, - F1: Fn((ItemStateProxy, TR), T) -> BoxFuture> + Send + Sync + 'static, - F2: Fn((ItemStateProxy, TR), T) -> BoxFuture> + Send + Sync + 'static, + F1: Fn((ItemStateProxy, TR), T) -> BoxFuture> + Send + Sync + 'static, + F2: Fn((ItemStateProxy, TR), T) -> BoxFuture> + Send + Sync + 'static, T: Send + Sync + Clone + 'static, O1: Send, O2: Send, @@ -799,7 +801,7 @@ where pub(super) fn add_item_to_inventory( character: CharacterEntity, ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + Clone + -> BoxFuture> + Clone where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -829,7 +831,7 @@ pub(super) fn record_trade( character_to: CharacterEntityId, character_from: CharacterEntityId, ) -> impl Fn((ItemStateProxy, TR), Vec) - -> BoxFuture), ItemStateError>> + Clone + -> BoxFuture), anyhow::Error>> + Clone where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -855,7 +857,7 @@ where pub(super) fn assign_new_item_id( ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + Clone + -> BoxFuture> + Clone where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -873,7 +875,7 @@ pub(super) fn convert_item_drop_to_floor_item( character_id: CharacterEntityId, item_drop: ItemDrop, ) -> impl Fn((ItemStateProxy, TR), ()) - -> BoxFuture> + Clone + -> BoxFuture> + Clone where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -974,7 +976,7 @@ where pub(super) fn add_item_to_local_floor( character_id: CharacterEntityId, ) -> impl Fn((ItemStateProxy, TR), FloorItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -993,7 +995,7 @@ where pub(super) fn apply_modifier_to_inventory_item( modifier: ItemModifier, ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -1006,7 +1008,7 @@ where weapon.apply_modifier(&modifier); transaction.gateway().add_weapon_modifier(entity_id, modifier).await?; }, - _ => return Err(ItemStateError::InvalidModifier) + _ => return Err(ItemStateError::InvalidModifier.into()) } Ok(((item_state, transaction), inventory_item)) @@ -1016,7 +1018,7 @@ where pub(super) fn as_individual_item( ) -> impl Fn((ItemStateProxy, TR), InventoryItem) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -1025,7 +1027,7 @@ where Box::pin(async move { let item = match inventory_item.item { InventoryItemDetail::Individual(individual_item) => individual_item, - _ => return Err(ItemStateError::WrongItemType(inventory_item.item_id)) + _ => return Err(ItemStateError::WrongItemType(inventory_item.item_id).into()) }; Ok(((item_state, transaction), item)) @@ -1038,7 +1040,7 @@ pub(super) fn apply_item_action_packets( character_id: CharacterEntityId, area_client: AreaClient, ) -> impl Fn((ItemStateProxy, TR), ApplyItemAction) - -> BoxFuture), ItemStateError>> + -> BoxFuture), anyhow::Error>> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, @@ -1095,7 +1097,7 @@ where pub(super) fn apply_item_action_character( character: &CharacterEntity ) -> impl Fn((ItemStateProxy, TR), Vec) - -> BoxFuture> + -> BoxFuture> where EG: EntityGateway, TR: EntityGatewayTransaction + 'static, diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs index b78a94b..9820226 100644 --- a/src/ship/items/apply_item.rs +++ b/src/ship/items/apply_item.rs @@ -1,14 +1,16 @@ use std::convert::TryInto; use futures::future::join_all; use thiserror::Error; +use anyhow::Context; use rand::SeedableRng; use rand::distributions::{WeightedIndex, Distribution}; use crate::entity::gateway::{EntityGateway, GatewayError}; -use crate::entity::character::CharacterEntity; +use crate::entity::character::{CharacterEntity, TechLevel}; use crate::entity::item::mag::{MagCell, MagCellError}; use crate::entity::item::tool::{Tool, ToolType}; +use crate::entity::item::tech::TechniqueDisk; use crate::entity::item::{ItemDetail, ItemEntityId}; -use crate::ship::items::state::{ItemStateProxy, ItemStateError}; +use crate::ship::items::state::ItemStateProxy; use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; @@ -18,14 +20,12 @@ pub enum ApplyItemError { NoCharacter, #[error("item not equipped")] ItemNotEquipped, - #[error("invalid item")] + #[error("could not use item invalid item")] InvalidItem, + #[error("invalid tool")] + InvalidTool, #[error("gateway error {0}")] GatewayError(#[from] GatewayError), - - #[error("itemstate error {0}")] - ItemStateError(Box), - #[error("magcell error {0}")] MagCellError(#[from] MagCellError), } @@ -38,49 +38,43 @@ pub enum ApplyItemAction { //RemoveItem, } -impl From for ApplyItemError { - fn from(other: ItemStateError) -> ApplyItemError { - ApplyItemError::ItemStateError(Box::new(other)) - } -} - -async fn power_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn power_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.power += 1; entity_gateway.save_character(character).await?; Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } -async fn mind_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn mind_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.mind += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } -async fn evade_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn evade_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.evade += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } -async fn def_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn def_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.def += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } -async fn luck_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn luck_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.luck += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } -async fn hp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn hp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.hp += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) } -async fn tp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, ApplyItemError> { +async fn tp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result, anyhow::Error> { character.materials.tp += 1; entity_gateway.save_character(character).await.unwrap(); Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) @@ -113,7 +107,7 @@ async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy, character: &CharacterEntity, cell_entity_id: ItemEntityId, mag_cell_type: MagCell) - -> Result, ApplyItemError> + -> Result, anyhow::Error> where EG: EntityGateway + ?Sized, { @@ -229,7 +223,7 @@ pub async fn liberta_kit(entity_gateway: &mut EG, used_cell: */ -fn jack_o_lantern() -> Result, ApplyItemError> +fn jack_o_lantern() -> Result, anyhow::Error> { let mag_rate = WeightedIndex::new(&[13, 13, 13, 13, 12, 12, 12, 12]).unwrap(); let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) { @@ -252,7 +246,7 @@ async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy, character: &mut CharacterEntity, entity_id: ItemEntityId, tool: ToolType) - -> Result, ApplyItemError> + -> Result, anyhow::Error> where EG: EntityGateway + ?Sized, { @@ -270,6 +264,13 @@ where ToolType::Monofluid => Ok(Vec::new()), ToolType::Difluid => Ok(Vec::new()), ToolType::Trifluid => Ok(Vec::new()), + ToolType::SolAtomizer => Ok(Vec::new()), + ToolType::MoonAtomizer => Ok(Vec::new()), + ToolType::StarAtomizer => Ok(Vec::new()), + ToolType::Telepipe => Ok(Vec::new()), + ToolType::Antidote => Ok(Vec::new()), + ToolType::Antiparalysis => Ok(Vec::new()), + ToolType::TrapVision => Ok(Vec::new()), ToolType::HuntersReport => Ok(Vec::new()), ToolType::CellOfMag502 | ToolType::CellOfMag213 @@ -299,17 +300,36 @@ where } ToolType::JackOLantern => jack_o_lantern(), // TODO: rest of these - _ => Err(ApplyItemError::InvalidItem) + _ => Err(anyhow::Error::from(ApplyItemError::InvalidTool)) + .with_context(|| { + format!("invalid tool {tool:?}") + }) + } } +async fn apply_tech<'a, EG>(_item_state: &mut ItemStateProxy, + entity_gateway: &mut EG, + character: &mut CharacterEntity, + _entity_id: ItemEntityId, + tech: TechniqueDisk) + -> Result, anyhow::Error> +where + EG: EntityGateway + ?Sized, +{ + // TODO: make sure the class can learn that specific tech + character.techs.set_tech(tech.tech, TechLevel(tech.level as u8)); + entity_gateway.save_character(character).await.unwrap(); + Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) + +} pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy, entity_gateway: &mut EG, character: &mut CharacterEntity, item: InventoryItem -) -> Result, ApplyItemError> +) -> Result, anyhow::Error> where EG: EntityGateway + ?Sized + Clone + 'static { @@ -317,7 +337,11 @@ where InventoryItemDetail::Individual(individual_item) => { match individual_item.item { ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await, - _ => Err(ApplyItemError::InvalidItem) + ItemDetail::TechniqueDisk(tech) => apply_tech(item_state, entity_gateway, character, individual_item.entity_id, tech).await, + _ => Err(anyhow::Error::from(ApplyItemError::InvalidItem)) + .with_context(|| { + format!("item {individual_item:?}") + }) } }, InventoryItemDetail::Stacked(stacked_item) => { diff --git a/src/ship/items/bank.rs b/src/ship/items/bank.rs index 5abef95..5494102 100644 --- a/src/ship/items/bank.rs +++ b/src/ship/items/bank.rs @@ -64,10 +64,10 @@ pub struct BankItem { } impl BankItem { - pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result where F: FnMut(T, ItemEntityId) -> Fut, - Fut: Future>, + Fut: Future>, { match &self.item { BankItemDetail::Individual(individual_item) => { @@ -125,27 +125,27 @@ impl BankState { self.item_id_counter = base_item_id + self.bank.0.len() as u32; } - pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> { if self.meseta.0 + amount > 999999 { - return Err(ItemStateError::FullOfMeseta) + return Err(ItemStateError::FullOfMeseta.into()) } self.meseta.0 += amount; Ok(()) } - pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> { if amount > self.meseta.0 { - return Err(ItemStateError::InvalidMesetaRemoval(amount)) + return Err(ItemStateError::InvalidMesetaRemoval(amount).into()) } self.meseta.0 -= amount; Ok(()) } - pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result { + pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result { match item.item { InventoryItemDetail::Individual(iitem) => { if self.bank.0.len() >= 30 { - Err(BankError::BankFull) + Err(BankError::BankFull.into()) } else { self.bank.0.push(BankItem { @@ -166,7 +166,7 @@ impl BankState { match existing_stack { Some(existing_stack) => { if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { - Err(BankError::StackFull) + Err(BankError::StackFull.into()) } else { existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); @@ -175,7 +175,7 @@ impl BankState { }, None => { if self.bank.0.len() >= 30 { - Err(BankError::BankFull) + Err(BankError::BankFull.into()) } else { self.bank.0.push(BankItem { diff --git a/src/ship/items/floor.rs b/src/ship/items/floor.rs index 2aab742..c71b7fd 100644 --- a/src/ship/items/floor.rs +++ b/src/ship/items/floor.rs @@ -33,7 +33,7 @@ pub struct FloorItem { } impl FloorItem { - pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result where F: FnMut(T, ItemEntityId) -> Fut, Fut: Future>, @@ -53,10 +53,10 @@ impl FloorItem { Ok(param) } - pub async fn with_mag(&self, mut param: T, mut func: F) -> Result + pub async fn with_mag(&self, mut param: T, mut func: F) -> Result where F: FnMut(T, ItemEntityId, Mag) -> Fut, - Fut: Future>, + Fut: Future>, { if let FloorItemDetail::Individual(individual_item) = &self.item { if let ItemDetail::Mag(mag) = &individual_item.item { diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index e10a808..bfd709b 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -60,7 +60,7 @@ impl InventoryItemDetail { } // TODO: this should probably go somewhere a bit more fundamental like ItemDetail - pub fn sell_price(&self) -> Result { + pub fn sell_price(&self) -> Result { match self { InventoryItemDetail::Individual(individual_item) => { match &individual_item.item { @@ -102,7 +102,7 @@ impl InventoryItemDetail { Ok((ToolShopItem::from(d).price() / 8) as u32) }, ItemDetail::Mag(_m) => { - Err(ItemStateError::ItemNotSellable) + Err(ItemStateError::ItemNotSellable.into()) }, ItemDetail::ESWeapon(_e) => { Ok(10u32) @@ -126,10 +126,10 @@ pub struct InventoryItem { } impl InventoryItem { - pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result where F: FnMut(T, ItemEntityId) -> Fut, - Fut: Future>, + Fut: Future>, { match &self.item { InventoryItemDetail::Individual(individual_item) => { @@ -145,10 +145,10 @@ impl InventoryItem { Ok(param) } - pub async fn with_mag(&self, mut param: T, mut func: F) -> Result + pub async fn with_mag(&self, mut param: T, mut func: F) -> Result where F: FnMut(T, ItemEntityId, Mag) -> Fut, - Fut: Future>, + Fut: Future>, { if let InventoryItemDetail::Individual(individual_item) = &self.item { if let ItemDetail::Mag(mag) = &individual_item.item { @@ -205,11 +205,11 @@ impl InventoryState { self.inventory.0.len() } - pub fn add_floor_item(&mut self, item: FloorItem) -> Result { + pub fn add_floor_item(&mut self, item: FloorItem) -> Result { match item.item { FloorItemDetail::Individual(iitem) => { if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) + Err(InventoryError::InventoryFull.into()) } else { self.inventory.0.push(InventoryItem { @@ -229,7 +229,7 @@ impl InventoryState { match existing_stack { Some(existing_stack) => { if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { - Err(InventoryError::StackFull) + Err(InventoryError::StackFull.into()) } else { existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); @@ -238,7 +238,7 @@ impl InventoryState { }, None => { if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) + Err(InventoryError::InventoryFull.into()) } else { self.inventory.0.push(InventoryItem { @@ -253,7 +253,7 @@ impl InventoryState { }, FloorItemDetail::Meseta(meseta) => { if self.meseta == Meseta(999999) { - Err(InventoryError::MesetaFull) + Err(InventoryError::MesetaFull.into()) } else { self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999); @@ -263,11 +263,11 @@ impl InventoryState { } } - pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> { + pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), anyhow::Error> { match &item.item { InventoryItemDetail::Individual(_) => { if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) + Err(InventoryError::InventoryFull.into()) } else { self.inventory.0.push(item); @@ -290,7 +290,7 @@ impl InventoryState { match existing_stack { Some(existing_stack) => { if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { - Err(InventoryError::StackFull) + Err(InventoryError::StackFull.into()) } else { existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); @@ -307,7 +307,7 @@ impl InventoryState { }, None => { if self.inventory.0.len() >= 30 { - Err(InventoryError::InventoryFull) + Err(InventoryError::InventoryFull.into()) } else { self.inventory.0.push(item); @@ -370,25 +370,25 @@ impl InventoryState { .find(|i| i.item_id == *item_id) } - pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> { if self.meseta.0 == 999999 { - return Err(ItemStateError::FullOfMeseta) + return Err(ItemStateError::FullOfMeseta.into()) } self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999); Ok(()) } - pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> { + pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), anyhow::Error> { if self.meseta.0 + amount > 999999 { - return Err(ItemStateError::FullOfMeseta) + return Err(ItemStateError::FullOfMeseta.into()) } self.meseta.0 += amount; Ok(()) } - pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> { if amount > self.meseta.0 { - return Err(ItemStateError::InvalidMesetaRemoval(amount)) + return Err(ItemStateError::InvalidMesetaRemoval(amount).into()) } self.meseta.0 -= amount; Ok(()) diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index 6473f38..a166d8b 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use async_std::sync::{Arc, RwLock, Mutex}; use futures::future::join_all; +use anyhow::Context; use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::{CharacterEntity, CharacterEntityId}; @@ -21,6 +22,8 @@ pub enum ItemStateError { NoCharacter(CharacterEntityId), #[error("room {0} not found")] NoRoom(RoomId), + #[error("inventory item {0} not found")] + NoInventoryItem(ClientItemId), #[error("floor item {0} not found")] NoFloorItem(ClientItemId), #[error("expected {0} to be a tool")] @@ -55,7 +58,7 @@ pub enum ItemStateError { ItemNotSellable, #[error("could not modify item")] InvalidModifier, - #[error("wrong item type ")] + #[error("wrong item type {0}")] WrongItemType(ClientItemId), } @@ -150,7 +153,7 @@ impl Default for ItemState { } impl ItemState { - pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result { + pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result { Ok(self.character_inventory .read() .await @@ -161,7 +164,7 @@ impl ItemState { .clone()) } - pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result { + pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result { Ok(self.character_bank .read() .await @@ -174,20 +177,20 @@ impl ItemState { } impl ItemState { - async fn new_item_id(&mut self) -> Result { + async fn new_item_id(&mut self) -> Result { *self.room_item_id_counter .write() .await += 1; Ok(ClientItemId(*self.room_item_id_counter.read().await)) } - pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> { + pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> { let inventory = entity_gateway.get_character_inventory(&character.id).await?; let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?; let equipped = entity_gateway.get_character_equips(&character.id).await?; let inventory_items = inventory.items.into_iter() - .map(|item| -> Result { + .map(|item| -> Result { Ok(match item { InventoryItemEntity::Individual(item) => { InventoryItem { @@ -214,7 +217,7 @@ impl ItemState { }, }) }) - .collect::, ItemStateError>>()?; + .collect::, anyhow::Error>>()?; let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let inventory_state = InventoryState { @@ -259,7 +262,7 @@ impl ItemState { .collect::>()) .await .into_iter() - .collect::, ItemStateError>>()?; + .collect::, anyhow::Error>>()?; let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; let bank_state = BankState::new(character.id, BankName("".into()), Bank::new(bank_items), bank_meseta); @@ -334,7 +337,7 @@ impl ItemState { } } - pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), ItemStateError> { + pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), anyhow::Error> { let local_floors = self.character_floor .read() .await; @@ -369,6 +372,7 @@ impl ItemState { .map(|item| (item.clone(), FloorType::Shared)) }) .ok_or_else(|| ItemStateError::NoFloorItem(*item_id)) + .with_context(|| format!("character {character_id}\nlocal floors: {local_floors:#?}\nshared floors: {shared_floors:#?}")) } } @@ -421,7 +425,7 @@ impl ItemStateProxy { async fn get_or_clone(master: &Arc>>>, proxy: &Arc>>, key: K, - err: fn(K) -> ItemStateError) -> Result + err: fn(K) -> ItemStateError) -> Result where K: Eq + std::hash::Hash + Copy, V: Clone @@ -451,7 +455,7 @@ impl ItemStateProxy { } } - pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result { + pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_inventory, &self.proxied_state.character_inventory, *character_id, @@ -462,7 +466,7 @@ impl ItemStateProxy { self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory); } - pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result { + pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result { get_or_clone(&self.item_state.character_bank, &self.proxied_state.character_bank, *character_id, @@ -473,7 +477,7 @@ impl ItemStateProxy { self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank); } - pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result { + pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result { let room_id = *self.item_state.character_room.read().await.get(character_id).unwrap(); Ok(FloorState { character_id: *character_id, @@ -488,7 +492,7 @@ impl ItemStateProxy { self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared); } - pub async fn new_item_id(&mut self) -> Result { + pub async fn new_item_id(&mut self) -> Result { self.item_state.new_item_id().await } } diff --git a/src/ship/items/tasks.rs b/src/ship/items/tasks.rs index a1ab65c..ad3e585 100644 --- a/src/ship/items/tasks.rs +++ b/src/ship/items/tasks.rs @@ -5,7 +5,7 @@ use crate::ship::ship::SendShipPacket; use crate::ship::map::MapArea; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction}; -use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateError, IndividualItemDetail}; +use crate::ship::items::state::{ItemState, ItemStateProxy, IndividualItemDetail}; use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction}; use crate::ship::items::inventory::InventoryItem; use crate::ship::items::floor::FloorItem; @@ -22,7 +22,7 @@ pub async fn pick_up_item( entity_gateway: &mut EG, character: &CharacterEntity, item_id: &ClientItemId) - -> Result + -> Result where EG: EntityGateway + 'static, EG::Transaction: Clone, @@ -46,7 +46,7 @@ pub async fn drop_item( item_id: &ClientItemId, map_area: MapArea, drop_position: (f32, f32, f32)) - -> Result + -> Result where EG: EntityGateway + 'static, { @@ -70,7 +70,7 @@ pub async fn drop_partial_item<'a, EG>( map_area: MapArea, drop_position: (f32, f32), amount: u32) - -> Result + -> Result where EG: EntityGateway + 'static, { @@ -95,7 +95,7 @@ pub async fn drop_meseta<'a, EG>( map_area: MapArea, drop_position: (f32, f32), amount: u32) - -> Result + -> Result where EG: EntityGateway + 'static, { @@ -117,7 +117,7 @@ pub async fn withdraw_meseta<'a, EG>( entity_gateway: &mut EG, character: &CharacterEntity, amount: u32) - -> Result<(), ItemStateError> + -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -139,7 +139,7 @@ pub async fn deposit_meseta<'a, EG>( entity_gateway: &mut EG, character: &CharacterEntity, amount: u32) - -> Result<(), ItemStateError> + -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -162,7 +162,7 @@ pub async fn withdraw_item<'a, EG>( character: &CharacterEntity, item_id: &ClientItemId, amount: u32) - -> Result + -> Result where EG: EntityGateway + 'static, { @@ -187,7 +187,7 @@ pub async fn deposit_item<'a, EG> ( character: &CharacterEntity, item_id: &ClientItemId, amount: u32) - -> Result<(), ItemStateError> + -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -209,7 +209,7 @@ pub async fn equip_item<'a, EG> ( character: &CharacterEntity, item_id: &ClientItemId, equip_slot: u8, -) -> Result<(), ItemStateError> +) -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -230,7 +230,7 @@ pub async fn unequip_item<'a, EG> ( entity_gateway: &mut EG, character: &CharacterEntity, item_id: &ClientItemId, -) -> Result<(), ItemStateError> +) -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -251,7 +251,7 @@ pub async fn sort_inventory<'a, EG> ( entity_gateway: &mut EG, character: &CharacterEntity, item_ids: Vec, -) -> Result<(), ItemStateError> +) -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -274,7 +274,7 @@ pub async fn use_item<'a, EG> ( area_client: AreaClient, item_id: &ClientItemId, amount: u32, -) -> Result, ItemStateError> +) -> Result, anyhow::Error> where EG: EntityGateway + 'static, { @@ -303,7 +303,7 @@ pub async fn feed_mag<'a, EG> ( character: &CharacterEntity, mag_item_id: &ClientItemId, tool_item_id: &ClientItemId, -) -> Result<(), ItemStateError> +) -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -327,7 +327,7 @@ pub async fn buy_shop_item<'a, EG> ( shop_item: &'a (dyn ShopItem + Send + Sync), item_id: ClientItemId, amount: u32, -) -> Result +) -> Result where EG: EntityGateway + 'static, { @@ -353,7 +353,7 @@ pub async fn sell_item<'a, EG> ( character: &CharacterEntity, item_id: ClientItemId, amount: u32, -) -> Result +) -> Result where EG: EntityGateway + 'static, { @@ -373,7 +373,7 @@ pub async fn trade_items<'a, EG> ( entity_gateway: &mut EG, p1: (&AreaClient, &CharacterEntity, &Vec, Meseta), p2: (&AreaClient, &CharacterEntity, &Vec, Meseta)) - -> Result<(Vec, Vec), ItemStateError> + -> Result<(Vec, Vec), anyhow::Error> where EG: EntityGateway + 'static, { @@ -443,7 +443,7 @@ pub async fn take_meseta<'a, EG> ( entity_gateway: &mut EG, character_id: &CharacterEntityId, meseta: Meseta) - -> Result<(), ItemStateError> + -> Result<(), anyhow::Error> where EG: EntityGateway + 'static, { @@ -464,7 +464,7 @@ pub async fn enemy_drops_item<'a, EG> ( entity_gateway: &mut EG, character_id: CharacterEntityId, item_drop: ItemDrop) - -> Result + -> Result where EG: EntityGateway + 'static, { @@ -488,7 +488,7 @@ pub async fn apply_modifier<'a, EG> ( character: &CharacterEntity, item_id: ClientItemId, modifier: ItemModifier) - -> Result + -> Result where EG: EntityGateway + 'static, { diff --git a/src/ship/location.rs b/src/ship/location.rs index b3f16cc..5dc805e 100644 --- a/src/ship/location.rs +++ b/src/ship/location.rs @@ -30,41 +30,50 @@ impl LobbyId { #[derive(Error, Debug, PartialEq, Eq)] -#[error("create room")] pub enum CreateRoomError { + #[error("no open slots")] NoOpenSlots, + #[error("client already in area")] ClientInAreaAlready, + #[error("join error")] JoinError, } #[derive(Error, Debug, PartialEq, Eq)] -#[error("join room")] 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)] -#[error("join lobby")] 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)] -#[error("get area")] 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)] -#[error("client removal")] pub enum ClientRemovalError { + #[error("client removal: client not in area")] ClientNotInArea, + #[error("client removal: invalid area")] InvalidArea, } @@ -77,17 +86,20 @@ pub enum GetClientsError { } #[derive(Error, Debug, PartialEq, Eq)] -#[error("get neighbor")] pub enum GetNeighborError { + #[error("get neighbor: invalid client")] InvalidClient, + #[error("get neighbor: invalid area")] InvalidArea, } #[derive(Error, Debug, PartialEq, Eq)] -#[error("get leader")] pub enum GetLeaderError { + #[error("get leader: invalid client")] InvalidClient, + #[error("get leader: invalid area")] InvalidArea, + #[error("get leader: client not in area")] NoClientInArea, } diff --git a/src/ship/map/enemy.rs b/src/ship/map/enemy.rs index 79dc3b8..4989041 100644 --- a/src/ship/map/enemy.rs +++ b/src/ship/map/enemy.rs @@ -99,9 +99,18 @@ impl RareMonsterAppearTable { } } - pub fn roll_is_rare(&self, monster: &MonsterType) -> bool { + fn roll_is_rare(&self, monster: &MonsterType) -> bool { rand_chacha::ChaChaRng::from_entropy().gen::() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) } + + pub fn apply(&self, enemy: MapEnemy, event: ShipEvent) -> MapEnemy { + if enemy.can_be_rare() && self.roll_is_rare(&enemy.monster) { + enemy.into_rare(event) + } + else { + enemy + } + } } diff --git a/src/ship/map/maps.rs b/src/ship/map/maps.rs index 5d04291..05c7e29 100644 --- a/src/ship/map/maps.rs +++ b/src/ship/map/maps.rs @@ -8,12 +8,13 @@ use thiserror::Error; use crate::ship::ship::ShipEvent; use crate::ship::monster::MonsterType; -use crate::ship::room::{Episode, RoomMode}; +use crate::ship::room::{Episode, RoomMode, PlayerMode}; // TODO: don't use * use crate::ship::map::*; + pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec> { let mut object_data = Vec::new(); @@ -35,7 +36,7 @@ fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) -> enemy .map_or(vec![None], |monster| { let mut monsters = vec![Some(monster)]; - + match monster.monster { MonsterType::Monest => { for _ in 0..30 { @@ -172,25 +173,10 @@ fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec< } -#[derive(Error, Debug)] -#[error("")] -pub enum MapsError { - InvalidMonsterId(usize), - InvalidObjectId(usize), -} - -#[derive(Debug)] -pub struct Maps { - map_variants: Vec, - enemy_data: Vec>, - object_data: Vec>, -} - -impl Maps { - pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable, event: ShipEvent) -> Maps { - let map_variants = match (room_mode.episode(), room_mode.single_player()) { - (Episode::One, 0) => { - vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online), +pub fn default_map_variants(episode: Episode, player_mode: PlayerMode) -> Vec { + match (episode, player_mode) { + (Episode::One, PlayerMode::Multi) => { + vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online), MapVariant::new(MapArea::Forest1, MapVariantMode::Online), MapVariant::new(MapArea::Forest2, MapVariantMode::Online), MapVariant::new(MapArea::Caves1, MapVariantMode::Online), @@ -205,10 +191,10 @@ impl Maps { MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online), MapVariant::new(MapArea::VolOpt, MapVariantMode::Online), MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online), - ] - }, - (Episode::One, 1) => { - vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline), + ] + }, + (Episode::One, PlayerMode::Single) => { + vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline), MapVariant::new(MapArea::Forest1, MapVariantMode::Offline), MapVariant::new(MapArea::Forest2, MapVariantMode::Offline), MapVariant::new(MapArea::Caves1, MapVariantMode::Offline), @@ -223,10 +209,10 @@ impl Maps { MapVariant::new(MapArea::DeRolLe, MapVariantMode::Offline), MapVariant::new(MapArea::VolOpt, MapVariantMode::Offline), MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline), - ] - }, - (Episode::Two, 0) => { - vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online), + ] + }, + (Episode::Two, PlayerMode::Multi) => { + vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online), MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online), MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online), MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Online), @@ -242,10 +228,10 @@ impl Maps { MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Online), MapVariant::new(MapArea::BarbaRay, MapVariantMode::Online), MapVariant::new(MapArea::GolDragon, MapVariantMode::Online), - ] - }, - (Episode::Two, 1) => { - vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline), + ] + }, + (Episode::Two, PlayerMode::Single) => { + vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline), MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline), MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline), MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Offline), @@ -261,10 +247,10 @@ impl Maps { MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Offline), MapVariant::new(MapArea::BarbaRay, MapVariantMode::Offline), MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline), - ] - }, - (Episode::Four, _) => { - vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online), + ] + }, + (Episode::Four, PlayerMode::Multi) => { + vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online), MapVariant::new(MapArea::CraterEast, MapVariantMode::Online), MapVariant::new(MapArea::CraterWest, MapVariantMode::Online), MapVariant::new(MapArea::CraterSouth, MapVariantMode::Online), @@ -274,23 +260,44 @@ impl Maps { MapVariant::new(MapArea::SubDesert2, MapVariantMode::Online), MapVariant::new(MapArea::SubDesert3, MapVariantMode::Online), MapVariant::new(MapArea::SaintMillion, MapVariantMode::Online), - ] - }, - _ => unreachable!() - }; + ] + }, + (Episode::Four, PlayerMode::Single) => { + vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Offline), + MapVariant::new(MapArea::CraterEast, MapVariantMode::Offline), + MapVariant::new(MapArea::CraterWest, MapVariantMode::Offline), + MapVariant::new(MapArea::CraterSouth, MapVariantMode::Offline), + MapVariant::new(MapArea::CraterNorth, MapVariantMode::Offline), + MapVariant::new(MapArea::CraterInterior, MapVariantMode::Offline), + MapVariant::new(MapArea::SubDesert1, MapVariantMode::Offline), + MapVariant::new(MapArea::SubDesert2, MapVariantMode::Offline), + MapVariant::new(MapArea::SubDesert3, MapVariantMode::Offline), + MapVariant::new(MapArea::SaintMillion, MapVariantMode::Offline), + ] + }, + } +} +#[derive(Error, Debug)] +#[error("")] +pub enum MapsError { + InvalidMonsterId(usize), + InvalidObjectId(usize), +} + +#[derive(Debug)] +pub struct Maps { + map_variants: Vec, + enemy_data: Vec>, + object_data: Vec>, +} + +impl Maps { + pub fn new(map_variants: Vec, enemy_data: Vec>, object_data: Vec>) -> Maps { Maps { - enemy_data: map_variants.iter() - .flat_map(|map_variant| { - enemy_data_from_map_data(map_variant, &room_mode.episode()) - }) - .map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event)) - .collect(), - object_data: map_variants.iter() - .flat_map(|map_variant| { - objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map) - }).collect(), map_variants, + enemy_data, + object_data, } } @@ -322,7 +329,7 @@ impl Maps { { self.enemy_data = enemies .into_iter() - .map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event)) + .map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event))) .collect(); self.object_data = objects; } @@ -351,13 +358,20 @@ impl Maps { } } -fn apply_rare_enemy(enemy: Option, rare_enemy_table: &RareMonsterAppearTable, event: ShipEvent) -> Option { - enemy.map(|enemy| { - if enemy.can_be_rare() && rare_enemy_table.roll_is_rare(&enemy.monster) { - enemy.into_rare(event) - } - else { - enemy - } - }) +pub fn generate_free_roam_maps(room_mode: RoomMode, event: ShipEvent) -> Maps { + let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode()); + let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode()); + Maps { + enemy_data: map_variants.iter() + .flat_map(|map_variant| { + enemy_data_from_map_data(map_variant, &room_mode.episode()) + }) + .map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event))) + .collect(), + object_data: map_variants.iter() + .flat_map(|map_variant| { + objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map) + }).collect(), + map_variants, + } } diff --git a/src/ship/map/mod.rs b/src/ship/map/mod.rs index 6c54343..693fd46 100644 --- a/src/ship/map/mod.rs +++ b/src/ship/map/mod.rs @@ -1,8 +1,8 @@ pub mod area; pub mod enemy; -mod object; -mod variant; -mod maps; +pub mod object; +pub mod variant; +pub mod maps; // TODO: don't just forward everything to the module scope pub use area::*; diff --git a/src/ship/packet/builder/lobby.rs b/src/ship/packet/builder/lobby.rs index 666d9be..af033ca 100644 --- a/src/ship/packet/builder/lobby.rs +++ b/src/ship/packet/builder/lobby.rs @@ -1,6 +1,6 @@ use libpso::packet::ship::*; use crate::common::serverstate::ClientId; -use crate::ship::ship::{ShipError, Clients, ShipEvent}; +use crate::ship::ship::{Clients, ShipEvent}; use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError}; use crate::ship::packet::builder::{player_info}; use crate::ship::items::state::ItemState; @@ -13,7 +13,7 @@ pub async fn join_lobby(id: ClientId, clients: &Clients, item_state: &ItemState, event: ShipEvent) - -> Result { + -> Result { let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?; let playerinfo = join_all( @@ -28,9 +28,8 @@ pub async fn join_lobby(id: ClientId, }})) .await .into_iter() - .collect::, ShipError>>()?; + .collect::, anyhow::Error>>()?; - //let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); let client_block = clients.with(id, |client| Box::pin(async move { client.block as u16 })).await?; @@ -54,7 +53,7 @@ pub async fn add_to_lobby(id: ClientId, clients: &Clients, item_state: &ItemState, event: ShipEvent) - -> Result { + -> Result { let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?; let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?; clients.with(id, |client| { @@ -77,7 +76,7 @@ pub async fn add_to_lobby(id: ClientId, pub async fn remove_from_lobby(id: ClientId, client_location: &ClientLocation) - -> Result { + -> Result { let prev_area_index = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?.local_client.id(); let prev_area_leader_index = client_location .get_area_leader(client_location diff --git a/src/ship/packet/builder/room.rs b/src/ship/packet/builder/room.rs index b50c7fb..2b5c8e5 100644 --- a/src/ship/packet/builder/room.rs +++ b/src/ship/packet/builder/room.rs @@ -15,12 +15,11 @@ pub async fn join_room(id: ClientId, room_id: RoomId, room: &RoomState, event: ShipEvent) - -> Result { + -> Result { let all_clients = client_location.get_clients_in_room(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?; let players = futures::stream::iter(all_clients.iter()) .enumerate() - .fold::, _, _>(Ok([PlayerHeader::default(); 4]), |acc, (i, c)| async move { - //let header_client = clients.get(&c.client).ok_or(ShipError::ClientNotFound(id))?; + .fold::, _, _>(Ok([PlayerHeader::default(); 4]), |acc, (i, c)| async move { let header_area_client = client_location.get_local_client(id).await.map_err(|err| ShipError::ClientLocationError(err.into()))?; clients.with(c.client, |client| Box::pin(async move { acc.map(|mut a| { @@ -40,14 +39,14 @@ pub async fn join_room(id: ClientId, leader: leader.local_client.id(), one: 1, difficulty: room.mode.difficulty().into(), - battle: room.mode.battle(), + battle: room.mode.battle() as u8, event: event.into(), section: room.section_id.into(), - challenge: room.mode.challenge(), + challenge: room.mode.challenge() as u8, random_seed: room.random_seed, episode: room.mode.episode().into(), one2: 1, - single_player: room.mode.single_player(), + single_player: room.mode.player_mode().value(), unknown: 0, }) } @@ -59,7 +58,7 @@ pub async fn add_to_room(_id: ClientId, leader: &AreaClient, item_state: &ItemState, event: ShipEvent) - -> Result { + -> Result { let inventory = item_state.get_character_inventory(&client.character).await?; Ok(AddToRoom { flag: 1, diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index ddbfeeb..698de75 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -16,7 +16,7 @@ pub async fn validate_login(id: ClientId, shipgate_sender: &Option>, ship_name: &str, num_blocks: usize) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway, { diff --git a/src/ship/packet/handler/communication.rs b/src/ship/packet/handler/communication.rs index 0a80304..c0ac3e5 100644 --- a/src/ship/packet/handler/communication.rs +++ b/src/ship/packet/handler/communication.rs @@ -1,6 +1,6 @@ use libpso::packet::ship::*; use crate::common::serverstate::ClientId; -use crate::ship::ship::{SendShipPacket, ShipError, Clients}; +use crate::ship::ship::{SendShipPacket, Clients}; use crate::ship::location::{ClientLocation}; use crate::entity::gateway::EntityGateway; @@ -10,7 +10,7 @@ pub async fn player_chat(id: ClientId, msg: PlayerChat, client_location: &ClientLocation, clients: &Clients) - -> Result, ShipError> { + -> Result, anyhow::Error> { let cmsg = clients.with(id, |client| Box::pin(async move { PlayerChat::new(client.user.id.0, msg.message) })).await?; @@ -25,7 +25,7 @@ pub async fn player_chat(id: ClientId, pub async fn request_infoboard(id: ClientId, client_location: &ClientLocation, clients: &Clients) - -> Result, ShipError> { + -> Result, anyhow::Error> { let area_clients = client_location.get_client_neighbors(id).await.unwrap(); let infoboards = join_all( area_clients.iter() @@ -39,7 +39,7 @@ pub async fn request_infoboard(id: ClientId, })) .await .into_iter() - .collect::, ShipError>>()?; + .collect::, anyhow::Error>>()?; Ok(vec![(id, SendShipPacket::ViewInfoboardResponse(ViewInfoboardResponse {response: infoboards}))]) } @@ -47,7 +47,7 @@ pub async fn write_infoboard(id: ClientId, new_infoboard: WriteInfoboard, clients: &Clients, entity_gateway: &mut EG) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index 5c49a53..81f0146 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -39,13 +39,15 @@ async fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation) - -> Vec<(ClientId, SendShipPacket)> { - client_location.get_all_clients_by_client(id).await.unwrap().into_iter() - .filter(move |client| client.local_client.id() == target) - .map(move |client| { - (client.client, SendShipPacket::DirectMessage(msg.clone())) - }) - .collect() + -> Result, anyhow::Error> { + Ok(client_location.get_all_clients_by_client(id) + .await? + .into_iter() + .filter(move |client| client.local_client.id() == target) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(msg.clone())) + }) + .collect()) } pub async fn guildcard_send(id: ClientId, @@ -53,7 +55,7 @@ pub async fn guildcard_send(id: ClientId, target: u32, client_location: &ClientLocation, clients: &Clients) - -> Result, ShipError> { + -> Result, anyhow::Error> { let msg = clients.with(id, |client| Box::pin(async move { DirectMessage{ flag: target, @@ -72,7 +74,7 @@ pub async fn guildcard_send(id: ClientId, } })).await?; - Ok(send_to_client(id, target as u8, msg, client_location).await) + send_to_client(id, target as u8, msg, client_location).await } pub async fn request_item(id: ClientId, @@ -82,7 +84,7 @@ pub async fn request_item(id: ClientId, clients: &Clients, rooms: &Rooms, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + 'static, { @@ -92,7 +94,7 @@ where })).await??; if monster.dropped_item { - return Err(ShipError::MonsterAlreadyDroppedItem(id, request_item.enemy_id)) + return Err(ShipError::MonsterAlreadyDroppedItem(id, request_item.enemy_id).into()) } let clients_in_area = client_location.get_clients_in_room(room_id).await?; @@ -115,7 +117,7 @@ where z: request_item.z, item: item_drop, }; - let character_id = clients.with(id, |client| Box::pin(async move { + let character_id = clients.with(area_client.client, |client| Box::pin(async move { client.character.id })).await?; @@ -134,7 +136,7 @@ pub async fn pickup_item(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -193,7 +195,7 @@ pub async fn request_box_item(id: ClientId, clients: &Clients, rooms: &Rooms, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static { @@ -203,11 +205,10 @@ where })).await??; if box_object.dropped_item { - return Err(ShipError::BoxAlreadyDroppedItem(id, box_drop_request.object_id)) + return Err(ShipError::BoxAlreadyDroppedItem(id, box_drop_request.object_id).into()) } let clients_in_area = client_location.get_clients_in_room(room_id).await?; - let client_and_drop = rooms.with_mut(room_id, |room| Box::pin(async move { clients_in_area.into_iter() .filter_map(move |area_client| { @@ -244,7 +245,7 @@ where pub async fn send_bank_list(id: ClientId, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> { let bank = clients.with(id, |client| { let item_state = item_state.clone(); @@ -262,7 +263,7 @@ pub async fn bank_interaction(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -273,7 +274,7 @@ where let mut entity_gateway = entity_gateway.clone(); let mut item_state = item_state.clone(); Box::pin(async move { - Ok::<_, ShipError>(match bank_interaction.action { + Ok::<_, anyhow::Error>(match bank_interaction.action { BANK_ACTION_DEPOSIT => { if bank_interaction.item_id == 0xFFFFFFFF { deposit_meseta(&mut item_state, &mut entity_gateway, &client.character, bank_interaction.meseta_amount).await?; @@ -320,7 +321,7 @@ pub async fn shop_request(id: ClientId, clients: &Clients, rooms: &Rooms, shops: &ItemShops) - -> Result, ShipError> + -> Result, anyhow::Error> { //let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let room_id = client_location.get_room(id).await?; @@ -397,7 +398,7 @@ pub async fn buy_item(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -422,7 +423,7 @@ where (item, remove) }, _ => { - return Err(ShipError::ShopError) + return Err(ShipError::ShopError.into()) } }; @@ -439,7 +440,7 @@ where _ => {} } } - builder::message::create_withdrawn_inventory_item(area_client, &inventory_item) + Ok::<_, anyhow::Error>(builder::message::create_withdrawn_inventory_item(area_client, &inventory_item)?) })}).await??; let other_clients_in_area = client_location.get_client_neighbors(id).await?; @@ -465,7 +466,7 @@ pub async fn request_tek_item(id: ClientId, entity_gateway: &mut EG, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -502,7 +503,7 @@ where }); take_meseta(&mut item_state, &mut entity_gateway, &client.character.id, item::Meseta(100)).await?; - builder::message::tek_preview(ClientItemId(tek_request.item_id), &weapon) + Ok::<_, anyhow::Error>(builder::message::tek_preview(ClientItemId(tek_request.item_id), &weapon)?) })}).await??; @@ -515,7 +516,7 @@ pub async fn accept_tek_item(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { diff --git a/src/ship/packet/handler/lobby.rs b/src/ship/packet/handler/lobby.rs index f0caf23..9d7ca08 100644 --- a/src/ship/packet/handler/lobby.rs +++ b/src/ship/packet/handler/lobby.rs @@ -16,7 +16,7 @@ pub async fn block_selected(id: ClientId, pkt: MenuSelect, clients: &Clients, item_state: &ItemState) - -> Result, ShipError> { + -> Result, anyhow::Error> { clients.with_mut(id, |client| { let item_state = item_state.clone(); Box::pin(async move { @@ -34,8 +34,8 @@ pub async fn block_selected(id: ClientId, .meseta(inventory.meseta) .inventory(&inventory) .bank(&bank) - .keyboard_config(&client.character.keyboard_config.as_bytes()) - .gamepad_config(&client.character.gamepad_config.as_bytes()) + .keyboard_config(&client.settings.settings.keyboard_config) + .gamepad_config(&client.settings.settings.gamepad_config) .symbol_chat(&client.settings.settings.symbol_chats) .tech_menu(&client.character.tech_menu.as_bytes()) .option_flags(client.character.option_flags) @@ -57,7 +57,7 @@ pub async fn send_player_to_lobby(id: ClientId, clients: &Clients, item_state: &ItemState, event: ShipEvent) - -> Result, ShipError> { + -> Result, anyhow::Error> { let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).await.map_err(|_| ShipError::TooManyClients)?; let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, event).await?; let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, event).await?; @@ -77,7 +77,7 @@ pub async fn change_lobby(id: ClientId, rooms: &Rooms, entity_gateway: &mut EG, event: ShipEvent) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -134,7 +134,7 @@ where pub async fn remove_from_lobby(id: ClientId, client_location: &mut ClientLocation) - -> Result, ShipError> { + -> Result, anyhow::Error> { let area_client = client_location.get_local_client(id).await?; let neighbors = client_location.get_client_neighbors(id).await?; let leader = client_location.get_leader_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?; @@ -150,7 +150,7 @@ pub async fn get_room_tab_info(id: ClientId, pkt: MenuDetail, client_location: &mut ClientLocation, clients: &Clients) - -> Result, ShipError> { + -> Result, anyhow::Error> { let room_id = RoomId(pkt.item as usize); let clients_in_room = client_location.get_clients_in_room(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?; let room_info = if clients_in_room.is_empty() { @@ -169,7 +169,7 @@ pub async fn get_room_tab_info(id: ClientId, })).await })).await .into_iter() - .collect::, ShipError>>()? + .collect::, anyhow::Error>>()? .join("\n") }; Ok(vec![(id, SendShipPacket::SmallLeftDialog(SmallLeftDialog::new(room_info)))]) diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 8256a91..f4e4d69 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -18,7 +18,7 @@ pub async fn request_exp(id: ClientId, client_location: &ClientLocation, clients: &Clients, rooms: &Rooms) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -29,7 +29,7 @@ where let enemy_exp = rooms.with(room_id, |room| Box::pin(async move { let monster = room.maps.enemy_by_id(enemy_id)?; let monster_stats = room.monster_stats.get(&monster.monster).ok_or_else(|| ShipError::UnknownMonster(monster.monster))?; - Ok::<_, ShipError>(monster_stats.exp) + Ok::<_, anyhow::Error>(monster_stats.exp) })).await??; let exp_gain = if request_exp.last_hitter == 1 { @@ -59,7 +59,7 @@ where let (_, before_stats) = LEVEL_TABLE.get_stats_from_exp(char_class, exp); let (after_level, after_stats) = LEVEL_TABLE.get_stats_from_exp(char_class, exp + exp_gain); - let level_up_pkt = builder::message::character_leveled_up(area_client, after_level, before_stats, after_stats); + let level_up_pkt = builder::message::character_leveled_up(area_client, after_level-1, before_stats, after_stats); exp_pkts.extend(clients_in_area.into_iter() .map(move |c| { (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone())))) @@ -83,7 +83,7 @@ pub async fn player_drop_item(id: ClientId, clients: &Clients, rooms: &Rooms, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -112,7 +112,7 @@ pub async fn drop_coordinates(id: ClientId, client_location: &ClientLocation, clients: &Clients, rooms: &Rooms) - -> Result, ShipError> + -> Result, anyhow::Error> { let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; let map_area = rooms.with(room_id, |room| Box::pin(async move { @@ -137,7 +137,7 @@ pub async fn no_longer_has_item(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -150,7 +150,7 @@ where })).await?; if let Some(drop_location) = drop_location { if drop_location.item_id.0 != no_longer_has_item.item_id { - return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id)); + return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id).into()); } if no_longer_has_item.item_id == 0xFFFFFFFF { @@ -218,7 +218,7 @@ where .collect()) } else { - Err(ShipError::InvalidItem(ClientItemId(no_longer_has_item.item_id))) + Err(ShipError::InvalidItem(ClientItemId(no_longer_has_item.item_id)).into()) } } @@ -227,7 +227,7 @@ pub async fn update_player_position(id: ClientId, clients: &Clients, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { + -> Result, anyhow::Error> { if let Ok(room_id) = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() }) { let msg = message.msg.clone(); clients.with_mut(id, |client| { @@ -291,7 +291,7 @@ pub async fn update_player_position(id: ClientId, } _ => {}, } - Ok::<_, ShipError>(()) + Ok::<_, anyhow::Error>(()) })}).await??; } Ok(client_location.get_client_neighbors(id).await?.into_iter() @@ -307,7 +307,7 @@ pub async fn charge_attack(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -333,7 +333,7 @@ pub async fn player_uses_item(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -364,7 +364,7 @@ pub async fn player_used_medical_center(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -390,7 +390,7 @@ pub async fn player_feed_mag(id: ClientId, client_location: &ClientLocation, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -415,7 +415,7 @@ pub async fn player_equips_item(id: ClientId, entity_gateway: &mut EG, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -440,7 +440,7 @@ pub async fn player_unequips_item(id: ClientId, entity_gateway: &mut EG, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -458,7 +458,7 @@ pub async fn player_sorts_items(id: ClientId, entity_gateway: &mut EG, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -488,7 +488,7 @@ pub async fn player_sells_item (id: ClientId, entity_gateway: &mut EG, clients: &Clients, item_state: &mut ItemState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { diff --git a/src/ship/packet/handler/quest.rs b/src/ship/packet/handler/quest.rs index a9a5cb2..e451f9f 100644 --- a/src/ship/packet/handler/quest.rs +++ b/src/ship/packet/handler/quest.rs @@ -4,7 +4,8 @@ use libpso::packet::ship::*; use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent}; use crate::ship::room::Rooms; -use crate::ship::location::{ClientLocation, ClientLocationError}; +use crate::ship::map::enemy::RareMonsterAppearTable; +use crate::ship::location::{ClientLocation}; use crate::ship::packet::builder::quest; use libpso::util::array_to_utf8; @@ -13,7 +14,7 @@ enum QuestFileType { Dat } -fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType), ShipError> { +fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType), anyhow::Error> { let filename = array_to_utf8(*filename_bytes).map_err(|_| ShipError::InvalidQuestFilename("NOT UTF8".to_string()))?; let (filename, suffix) = { let mut s = filename.splitn(2, '.'); @@ -24,7 +25,7 @@ fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType) let datatype = match suffix { "bin" => QuestFileType::Bin, "dat" => QuestFileType::Dat, - _ => return Err(ShipError::InvalidQuestFilename(filename.to_owned())) + _ => Err(ShipError::InvalidQuestFilename(filename.to_owned()))? }; let (category, quest) = { @@ -41,8 +42,8 @@ pub async fn send_quest_category_list(id: ClientId, rql: RequestQuestList, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + -> Result, anyhow::Error> { + let room_id = client_location.get_room(id).await?; let rql = rql.clone(); rooms.with_mut(room_id, |room| Box::pin(async move { let qcl = quest::quest_category_list(&room.quests[rql.flag.clamp(0, (room.quests.len() - 1) as u32) as usize]); @@ -55,8 +56,8 @@ pub async fn select_quest_category(id: ClientId, menuselect: MenuSelect, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + -> Result, anyhow::Error> { + let room_id = client_location.get_room(id).await?; rooms.with(room_id, |room| Box::pin(async move { let (_, category_quests) = room.quests[room.quest_group.value()].iter() .nth(menuselect.item as usize) @@ -72,8 +73,8 @@ pub async fn quest_detail(id: ClientId, questdetailrequest: QuestDetailRequest, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + -> Result, anyhow::Error> { + let room_id = client_location.get_room(id).await?; rooms.with(room_id, |room| Box::pin(async move { let (_, category_quests) = room.quests[room.quest_group.value()].iter() .nth(questdetailrequest.category as usize) @@ -96,8 +97,8 @@ pub async fn player_chose_quest(id: ClientId, client_location: &ClientLocation, rooms: &Rooms, event: ShipEvent) - -> Result, ShipError> { - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + -> Result, anyhow::Error> { + let room_id = client_location.get_room(id).await?; let client_location = client_location.clone(); let questmenuselect = questmenuselect.clone(); @@ -115,14 +116,14 @@ pub async fn player_chose_quest(id: ClientId, .ok_or_else(|| ShipError::InvalidQuest(questmenuselect.quest))? .clone(); - let rare_monster_drops = room.rare_monster_table.clone(); - room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_drops, event); + let rare_monster_table = RareMonsterAppearTable::new(room.mode.episode()); + room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_table, event); room.map_areas = quest.map_areas.clone(); let bin = quest::quest_header(&questmenuselect, &quest.bin_blob, "bin"); let dat = quest::quest_header(&questmenuselect, &quest.dat_blob, "dat"); - let area_clients = client_location.get_all_clients_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + let area_clients = client_location.get_all_clients_by_client(id).await?; for client in &area_clients { clients.with_mut(client.client, |client| Box::pin(async move { client.done_loading_quest = false; @@ -141,9 +142,9 @@ pub async fn quest_file_request(id: ClientId, quest_file_request: QuestFileRequest, client_location: &ClientLocation, rooms: &mut Rooms) - -> Result, ShipError> + -> Result, anyhow::Error> { - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + let room_id = client_location.get_room(id).await?; let quest_file_request = quest_file_request.clone(); rooms.with(room_id, |room| Box::pin(async move { @@ -175,8 +176,8 @@ pub async fn quest_chunk_ack(id: ClientId, quest_chunk_ack: QuestChunkAck, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + -> Result, anyhow::Error> { + let room_id = client_location.get_room(id).await?; let quest_chunk_ack = quest_chunk_ack.clone(); rooms.with(room_id, |room| Box::pin(async move { @@ -211,11 +212,11 @@ pub async fn quest_chunk_ack(id: ClientId, pub async fn done_loading_quest(id: ClientId, clients: &Clients, client_location: &ClientLocation) - -> Result, ShipError> { + -> Result, anyhow::Error> { clients.with_mut(id, |client| Box::pin(async move { client.done_loading_quest = true; })).await?; - let area_clients = client_location.get_all_clients_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + let area_clients = client_location.get_all_clients_by_client(id).await?; let all_loaded = area_clients.iter() .map(|client| diff --git a/src/ship/packet/handler/room.rs b/src/ship/packet/handler/room.rs index 435d4b8..4cdf611 100644 --- a/src/ship/packet/handler/room.rs +++ b/src/ship/packet/handler/room.rs @@ -1,36 +1,43 @@ use std::convert::{TryFrom, Into}; use futures::stream::StreamExt; +use async_std::sync::Arc; + use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::common::serverstate::ClientId; use crate::common::leveltable::LEVEL_TABLE; -use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent}; -use crate::ship::room::Rooms; +use crate::entity::character::SectionID; +use crate::ship::drops::DropTable; +use crate::ship::ship::{SendShipPacket, Clients, ShipEvent}; +use crate::ship::room::{Rooms, Episode, Difficulty, RoomState, RoomMode}; +use crate::ship::map::Maps; use crate::ship::location::{ClientLocation, RoomId, RoomLobby, GetAreaError}; use crate::ship::packet::builder; -use crate::ship::room; use crate::ship::items::state::ItemState; +#[allow(clippy::too_many_arguments)] pub async fn create_room(id: ClientId, create_room: CreateRoom, client_location: &mut ClientLocation, clients: &Clients, item_state: &mut ItemState, rooms: &Rooms, + map_builder: Arc Maps + Send + Sync>>, + drop_table_builder: Arc DropTable + Send + Sync>>, event: ShipEvent) - -> Result, ShipError> { + -> Result, anyhow::Error> { let level = clients.with(id, |client| Box::pin(async move { LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) })).await?; - match room::Difficulty::try_from(create_room.difficulty)? { - room::Difficulty::Ultimate if level < 80 => { + match Difficulty::try_from(create_room.difficulty)? { + Difficulty::Ultimate if level < 80 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto create Ultimate rooms.".into())))]) }, - room::Difficulty::VeryHard if level < 40 => { + Difficulty::VeryHard if level < 40 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 40 \nto create Very Hard rooms.".into())))]) }, - room::Difficulty::Hard if level < 20 => { + Difficulty::Hard if level < 20 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 20 \nto create Hard rooms.".into())))]) }, _ => {}, @@ -45,9 +52,9 @@ pub async fn create_room(id: ClientId, let mut item_state = item_state.clone(); Box::pin(async move { item_state.add_character_to_room(room_id, &client.character, area_client).await; - let mut room = room::RoomState::from_create_room(&create_room, client.character.section_id, event)?; + let mut room = RoomState::from_create_room(&create_room, map_builder, drop_table_builder, client.character.section_id, event)?; room.bursting = true; - Ok::<_, ShipError>(room) + Ok::<_, anyhow::Error>(room) })}).await??; let join_room = builder::room::join_room(id, clients, client_location, room_id, &room, event).await?; @@ -69,7 +76,7 @@ pub async fn create_room(id: ClientId, pub async fn room_name_request(id: ClientId, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { + -> Result, anyhow::Error> { let area = client_location.get_area(id).await?; match area { RoomLobby::Room(room) => { @@ -90,7 +97,7 @@ pub async fn join_room(id: ClientId, item_state: &mut ItemState, rooms: &Rooms, event: ShipEvent) - -> Result, ShipError> { + -> Result, anyhow::Error> { let room_id = RoomId(pkt.item as usize); if !rooms.exists(room_id).await { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("This room no longer exists!".into())))]) @@ -103,13 +110,13 @@ pub async fn join_room(id: ClientId, })).await?; match difficulty { - room::Difficulty::Ultimate if level < 80 => { + Difficulty::Ultimate if level < 80 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto join Ultimate rooms.".into())))]) }, - room::Difficulty::VeryHard if level < 40 => { + Difficulty::VeryHard if level < 40 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 40 \nto join Very Hard rooms.".into())))]) }, - room::Difficulty::Hard if level < 20 => { + Difficulty::Hard if level < 20 => { return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 20 \nto join Hard rooms.".into())))]) }, _ => {}, @@ -171,7 +178,7 @@ pub async fn join_room(id: ClientId, pub async fn done_bursting(id: ClientId, client_location: &ClientLocation, rooms: &Rooms) - -> Result, ShipError> { + -> Result, anyhow::Error> { let room_id = client_location.get_room(id).await?; let rare_monster_list = rooms.with_mut(room_id, |room| Box::pin(async move { room.bursting = false; @@ -235,7 +242,7 @@ pub async fn request_room_list(id: ClientId, pub async fn cool_62(id: ClientId, cool_62: Like62ButCooler, client_location: &ClientLocation) - -> Result, ShipError> { + -> Result, anyhow::Error> { let target = cool_62.flag as u8; let cool_62 = cool_62.clone(); Ok(client_location diff --git a/src/ship/packet/handler/settings.rs b/src/ship/packet/handler/settings.rs index 58eea70..b40da3c 100644 --- a/src/ship/packet/handler/settings.rs +++ b/src/ship/packet/handler/settings.rs @@ -1,13 +1,13 @@ use libpso::packet::ship::*; use crate::common::serverstate::ClientId; -use crate::ship::ship::{SendShipPacket, ShipError, Clients}; +use crate::ship::ship::{SendShipPacket, Clients}; use crate::entity::gateway::EntityGateway; pub async fn update_config(id: ClientId, update_config: UpdateConfig, clients: &Clients, entity_gateway: &mut EG) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -24,7 +24,7 @@ pub async fn save_options(id: ClientId, save_options: SaveOptions, clients: &Clients, entity_gateway: &mut EG) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -41,15 +41,15 @@ pub async fn keyboard_config(id: ClientId, keyboard_config: KeyboardConfig, clients: &Clients, entity_gateway: &mut EG) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { clients.with_mut(id, |client| { let mut entity_gateway = entity_gateway.clone(); Box::pin(async move { - client.character.keyboard_config.update(&keyboard_config); - entity_gateway.save_character(&client.character).await + client.settings.settings.keyboard_config = keyboard_config.keyboard_config; + entity_gateway.save_user_settings(&client.settings).await })}).await??; Ok(Vec::new()) } @@ -58,15 +58,15 @@ pub async fn gamepad_config(id: ClientId, gamepad_config: GamepadConfig, clients: &Clients, entity_gateway: &mut EG) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { clients.with_mut(id, |client| { let mut entity_gateway = entity_gateway.clone(); Box::pin(async move { - client.character.gamepad_config.update(&gamepad_config); - entity_gateway.save_character(&client.character).await + client.settings.settings.gamepad_config = gamepad_config.gamepad_config; + entity_gateway.save_user_settings(&client.settings).await })}).await??; Ok(Vec::new()) } diff --git a/src/ship/packet/handler/trade.rs b/src/ship/packet/handler/trade.rs index f369f96..c1cb2d0 100644 --- a/src/ship/packet/handler/trade.rs +++ b/src/ship/packet/handler/trade.rs @@ -3,7 +3,7 @@ use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients}; -use crate::ship::location::{ClientLocation, ClientLocationError}; +use crate::ship::location::{ClientLocation}; use crate::ship::items::ClientItemId; use crate::ship::items::state::{ItemState, ItemStateError}; use crate::ship::items::inventory::InventoryItemDetail; @@ -57,9 +57,9 @@ async fn do_trade_action(id: ClientId, this: &mut ClientTradeState, other: &mut ClientTradeState, action: F) - -> Result, ShipError> + -> Result, anyhow::Error> where - F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> Result<(), ShipError>, + F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> Result<(), anyhow::Error>, { Ok(match action(this, other) { Ok(_) => { @@ -92,7 +92,7 @@ pub async fn trade_request(id: ClientId, clients: &Clients, item_state: &mut ItemState, trades: &mut TradeState) - -> Result, ShipError> + -> Result, anyhow::Error> { let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet match trade_request.trade { @@ -293,12 +293,12 @@ async fn inner_items_to_trade(id: ClientId, clients: &Clients, item_state: &mut ItemState, trades: &mut TradeState) - -> Result, ShipError> + -> Result, anyhow::Error> { let pkts = trades .with(&id, |mut this, other| async move { if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) { - return Err(ShipError::from(TradeError::MismatchedStatus)) + return Err(anyhow::Error::from(ShipError::from(TradeError::MismatchedStatus))) } let other_client = other.client(); let (this_inventory, other_inventory) = clients.with(this.client(), |client| { @@ -311,7 +311,7 @@ async fn inner_items_to_trade(id: ClientId, Box::pin(async move { item_state.get_character_inventory(&client.character).await })}).await??; - Ok::<_, ShipError>((this, other_inventory)) + Ok::<_, anyhow::Error>((this, other_inventory)) })}).await??; if items_to_trade.count as usize != (this.items.len() + usize::from(this.meseta != 0)) { @@ -383,7 +383,7 @@ async fn inner_items_to_trade(id: ClientId, } } }) - .collect::, ShipError>>()?; + .collect::, anyhow::Error>>()?; this.status = TradeStatus::ItemsChecked; if this.status == TradeStatus::ItemsChecked && other.status == TradeStatus::ItemsChecked { @@ -418,7 +418,7 @@ pub async fn items_to_trade(id: ClientId, clients: &Clients, item_state: &mut ItemState, trades: &mut TradeState) - -> Result, ShipError> + -> Result, anyhow::Error> { let t = inner_items_to_trade(id, items_to_trade_pkt, client_location, clients, item_state, trades).await; match t { @@ -443,7 +443,7 @@ async fn trade_confirmed_inner(id: ClientId, clients: &Clients, item_state: &mut ItemState, trades: &mut TradeState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { @@ -460,14 +460,14 @@ where .with(&id, |mut this, other| { async move { if status_is_not(&this.status, &[TradeStatus::ItemsChecked]) || status_is_not(&other.status, &[TradeStatus::ItemsChecked, TradeStatus::TradeComplete]) { - return Err(ShipError::TradeError(TradeError::MismatchedStatus)) + return Err(anyhow::Error::from(ShipError::TradeError(TradeError::MismatchedStatus))) } this.status = TradeStatus::TradeComplete; if this.status == TradeStatus::TradeComplete && other.status == TradeStatus::TradeComplete { let this_local_client = client_location.get_local_client(this.client()).await?; let other_local_client = client_location.get_local_client(other.client()).await?; - let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?; + let room_id = client_location.get_room(id).await?; Ok(TradeReady::BothPlayers(room_id, (this_local_client, /*this_client, */this.clone()), @@ -584,7 +584,7 @@ pub async fn trade_confirmed(id: ClientId, clients: &Clients, item_state: &mut ItemState, trades: &mut TradeState) - -> Result, ShipError> + -> Result, anyhow::Error> where EG: EntityGateway + Clone + 'static, { diff --git a/src/ship/room.rs b/src/ship/room.rs index ed41b8d..87e5585 100644 --- a/src/ship/room.rs +++ b/src/ship/room.rs @@ -13,12 +13,10 @@ use crate::ship::drops::DropTable; use crate::entity::character::SectionID; use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use crate::ship::map::area::MapAreaLookup; -use crate::ship::map::enemy::RareMonsterAppearTable; use crate::ship::quests; use crate::ship::ship::{ShipError, ShipEvent}; use crate::ship::location::{MAX_ROOMS, RoomId}; - #[derive(Clone)] pub struct Rooms([Arc>>; MAX_ROOMS]); @@ -29,7 +27,7 @@ impl Default for Rooms { } impl Rooms { - pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), ShipError> { + pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> { *self.0 .get(room_id.0) .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))? @@ -58,7 +56,7 @@ impl Rooms { } } - pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result + pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result where T: Send, F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a @@ -72,11 +70,11 @@ impl Rooms { Ok(func(room).await) } else { - Err(ShipError::InvalidRoom(room_id.0 as u32)) + Err(ShipError::InvalidRoom(room_id.0 as u32).into()) } } - pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result + pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result where T: Send, F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a @@ -91,7 +89,7 @@ impl Rooms { Ok(func(room).await) } else { - Err(ShipError::InvalidRoom(room_id.0 as u32)) + Err(ShipError::InvalidRoom(room_id.0 as u32).into()) } } @@ -135,6 +133,21 @@ pub enum Episode { Four, } +#[derive(Debug, Copy, Clone)] +pub enum PlayerMode{ + Single, + Multi, +} + +impl PlayerMode { + pub fn value(&self) -> u8 { + match self { + PlayerMode::Single => 1, + PlayerMode::Multi => 0, + } + } +} + impl TryFrom for Episode { type Error = RoomCreationError; @@ -245,24 +258,18 @@ impl RoomMode { } } - pub fn battle(&self) -> u8 { - match self { - RoomMode::Battle {..} => 1, - _ => 0, - } + pub fn battle(&self) -> bool { + matches!(self, RoomMode::Battle {..}) } - pub fn challenge(&self) -> u8 { - match self { - RoomMode::Challenge {..} => 1, - _ => 0, - } + pub fn challenge(&self) -> bool { + matches!(self, RoomMode::Challenge {..}) } - pub fn single_player(&self) -> u8 { + pub fn player_mode(&self) -> PlayerMode { match self { - RoomMode::Single {..} => 1, - _ => 0, + RoomMode::Single {..} => PlayerMode::Single, + _ => PlayerMode::Multi, } } } @@ -295,13 +302,12 @@ pub struct RoomState { pub name: String, pub password: [u16; 16], pub maps: Maps, - pub drop_table: Box>, + pub drop_table: Box, pub section_id: SectionID, pub random_seed: u32, pub bursting: bool, pub monster_stats: Box>, pub map_areas: MapAreaLookup, - pub rare_monster_table: Box, pub quest_group: QuestCategoryType, pub quests: Vec, // items on ground @@ -343,7 +349,12 @@ impl RoomState { self.quest_group = QuestCategoryType::from(group); } - pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID, event: ShipEvent) -> Result { + pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, + map_builder: Arc Maps + Send + Sync>>, + drop_table_builder: Arc DropTable + Send + Sync>>, + section_id: SectionID, + event: ShipEvent) + -> Result { if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::() > 1 { return Err(RoomCreationError::InvalidMode) } @@ -372,7 +383,6 @@ impl RoomState { } }; - let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode()); // push the usual set of quests for the selected mode let mut qpath = PathBuf::from("data/quests/bb"); @@ -401,18 +411,15 @@ impl RoomState { room_quests.push(quest_list); } - - Ok(RoomState { monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?), mode: room_mode, random_seed: rand::thread_rng().gen(), - rare_monster_table: Box::new(rare_monster_table.clone()), name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(), password: create_room.password, - maps: Maps::new(room_mode, &rare_monster_table, event), + maps: map_builder(room_mode, event), section_id, - drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)), + drop_table: Box::new(drop_table_builder(room_mode.episode(), room_mode.difficulty(), section_id)), bursting: false, map_areas: MapAreaLookup::new(&room_mode.episode()), quest_group: QuestCategoryType::Standard, diff --git a/src/ship/ship.rs b/src/ship/ship.rs index a71cc14..1d1bb47 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -4,7 +4,6 @@ use std::collections::HashMap; use async_std::channel; use async_std::sync::{Arc, Mutex, RwLock}; - use rand::Rng; use thiserror::Error; @@ -13,24 +12,19 @@ use libpso::packet::login::{RedirectClient, Login, LoginResponse, ShipList}; use libpso::packet::messages::*; use libpso::{PacketParseError, PSOPacket}; use libpso::crypto::bb::PSOBBCipher; - use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID}; - use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage}; - use crate::login::character::SHIP_MENU_ID; - use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::character::SectionID; - use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId}; - +use crate::ship::drops::DropTable; use crate::ship::items; use crate::ship::room; -use crate::ship::map::{MapsError, MapAreaError}; +use crate::ship::map::{Maps, MapsError, MapAreaError, generate_free_roam_maps}; use crate::ship::packet::handler; use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop}; use crate::ship::trade::TradeState; @@ -162,11 +156,13 @@ pub enum ShipError { SendError(#[from] async_std::channel::SendError), } +/* impl> From for ShipError { fn from(other: I) -> ShipError { ShipError::ClientLocationError(other.into()) } } +*/ #[derive(Debug)] @@ -375,6 +371,8 @@ pub struct ShipServerStateBuilder { port: Option, auth_token: Option, event: Option, + map_builder: Option Maps + Send + Sync>>, + drop_table_builder: Option DropTable + Send + Sync>>, num_blocks: usize, } @@ -387,6 +385,8 @@ impl Default for ShipServerStateBuilder port: None, auth_token: None, event: None, + map_builder: None, + drop_table_builder: None, num_blocks: 2, } } @@ -429,6 +429,18 @@ impl ShipServerStateBuilder { self } + #[must_use] + pub fn map_builder(mut self, map_builder: Box Maps + Send + Sync>) -> ShipServerStateBuilder { + self.map_builder = Some(map_builder); + self + } + + #[must_use] + pub fn drop_table_builder(mut self, drop_table_builder: Box DropTable + Send + Sync>) -> ShipServerStateBuilder { + self.drop_table_builder = Some(drop_table_builder); + self + } + #[must_use] pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder { self.num_blocks = num_blocks; @@ -447,6 +459,8 @@ impl ShipServerStateBuilder { shops: ItemShops::default(), blocks: Blocks(blocks), event: self.event.unwrap_or(ShipEvent::None), + map_builder: Arc::new(self.map_builder.unwrap_or(Box::new(generate_free_roam_maps))), + drop_table_builder: Arc::new(self.drop_table_builder.unwrap_or(Box::new(DropTable::new))), auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())), ship_list: Arc::new(RwLock::new(Vec::new())), @@ -467,13 +481,13 @@ pub struct Block { pub struct Blocks(pub Vec); impl Blocks { - async fn get_from_client(&mut self, id: ClientId, clients: &Clients) -> Result<&mut Block, ShipError> { + async fn get_from_client(&mut self, id: ClientId, clients: &Clients) -> Result<&mut Block, anyhow::Error> { let block = clients.with(id, |client| Box::pin(async move { client.block })).await?; self.0 .get_mut(block) - .ok_or_else(|| ShipError::InvalidBlock(block)) + .ok_or_else(|| ShipError::InvalidBlock(block).into()) } } @@ -495,6 +509,8 @@ pub struct ShipServerState { ship_list: Arc>>, shipgate_sender: Option>, trades: TradeState, + map_builder: Arc Maps + Send + Sync>>, + drop_table_builder: Arc DropTable + Send + Sync>>, } impl ShipServerState { @@ -665,12 +681,12 @@ impl ServerState for ShipServerState { let block = self.blocks.get_from_client(id, &self.clients).await?; match menuselect.menu { SHIP_MENU_ID => { - let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().into_iter().flatten(); + let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().flatten(); let select_ship = handler::ship::selected_ship(id, menuselect, &self.ship_list).await?; leave_lobby.chain(select_ship).collect() } BLOCK_MENU_ID => { - let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().into_iter().flatten(); + let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).await.into_iter().flatten(); let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter(); leave_lobby.chain(select_block).collect() } @@ -721,7 +737,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::CreateRoom(create_room) => { let block = self.blocks.get_from_client(id, &self.clients).await?; - handler::room::create_room(id, create_room, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await? + handler::room::create_room(id, create_room, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.map_builder.clone(), self.drop_table_builder.clone(), self.event).await? }, RecvShipPacket::RoomNameRequest(_req) => { let block = self.blocks.get_from_client(id, &self.clients).await?; @@ -834,6 +850,8 @@ impl ServerState for ShipServerState { self.item_state.remove_character_from_room(&client.character).await } + block.client_location.remove_client_from_area(id).await?; + Ok(neighbors.into_iter().map(|n| { (n.client, pkt.clone()) }).collect()) diff --git a/tests/common.rs b/tests/common.rs index d2ea567..1a8a6a0 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -13,6 +13,7 @@ use libpso::packet::login::{Login, Session}; use libpso::{utf8_to_array, utf8_to_utf16_array}; +//TODO: remove kb_conf_preset pub async fn new_user_character(entity_gateway: &mut EG, username: &str, password: &str, kb_conf_preset: usize) -> (UserAccountEntity, CharacterEntity) { let new_user = NewUserAccountEntity { email: format!("{}@pso.com", username), @@ -26,7 +27,7 @@ pub async fn new_user_character(entity_gateway: &mut let user = entity_gateway.create_user(new_user).await.unwrap(); let new_settings = NewUserSettingsEntity::new(user.id); let _settings = entity_gateway.create_user_settings(new_settings).await.unwrap(); - let new_character = NewCharacterEntity::new(user.id, kb_conf_preset); + let new_character = NewCharacterEntity::new(user.id); let character = entity_gateway.create_character(new_character).await.unwrap(); entity_gateway.set_character_meseta(&character.id, Meseta(0)).await.unwrap(); entity_gateway.set_bank_meseta(&character.id, &BankName("".into()), Meseta(0)).await.unwrap(); diff --git a/tests/test_character.rs b/tests/test_character.rs index 29812e9..ca16eac 100644 --- a/tests/test_character.rs +++ b/tests/test_character.rs @@ -31,26 +31,6 @@ async fn test_save_options() { assert!(char.option_flags == 12345); } -#[async_std::test] -async fn test_default3_keyboard_mappings() { - /* - check if keyboard is set to default3 when specified. this will only occur for things like creating characters from the web page. - normal client behaviour will simply use default1 when creating a character. - gamepad only has 1 default config - */ - let mut entity_gateway = InMemoryGateway::default(); - let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 3).await; - assert!(char1.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG3); -} - -#[async_std::test] -async fn test_invalid_keyboard_preset_value() { - // check if keyboard_config self-corrects to DEFAULT1 if an invalid value (>4) is given - let mut entity_gateway = InMemoryGateway::default(); - let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 10).await; - assert!(char1.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG1); -} - #[async_std::test] async fn test_change_keyboard_mappings() { let mut entity_gateway = InMemoryGateway::default(); @@ -63,7 +43,8 @@ async fn test_change_keyboard_mappings() { log_in_char(&mut ship, ClientId(1), "a1", "a").await; join_lobby(&mut ship, ClientId(1)).await; - assert!(char1.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG2); + let settings = entity_gateway.get_user_settings_by_user(&user1).await.unwrap(); + assert!(settings.settings.keyboard_config == DEFAULT_KEYBOARD_CONFIG1); // update from default2 to default4 // the client simply sends the full 364 bytes... @@ -95,8 +76,6 @@ async fn test_change_keyboard_mappings() { ], })).await.unwrap(); - let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap(); - let char = characters[0].as_ref().unwrap(); - - assert!(char.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG4); + let settings = entity_gateway.get_user_settings_by_user(&user1).await.unwrap(); + assert!(settings.settings.keyboard_config == DEFAULT_KEYBOARD_CONFIG4); } diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index 4584571..0317e9d 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -4,6 +4,10 @@ use elseware::common::leveltable::CharacterLevelTable; use elseware::ship::ship::{ShipServerState, SendShipPacket, RecvShipPacket}; use elseware::ship::monster::MonsterType; use elseware::ship::location::RoomId; +use elseware::ship::map::variant::{MapVariant, MapVariantMode}; +use elseware::ship::map::maps::Maps; +use elseware::ship::map::area::MapArea; +use elseware::ship::map::enemy::MapEnemy; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -19,36 +23,30 @@ async fn test_character_gains_exp() { let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; let mut ship = Box::new(ShipServerState::builder() - .gateway(entity_gateway.clone()) - .build()); + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))], + Vec::new(), + ) + })) + .build()); log_in_char(&mut ship, ClientId(1), "a1", "a").await; join_lobby(&mut ship, ClientId(1)).await; create_room(&mut ship, ClientId(1), "room", "").await; - let (enemy_id, exp) = { - //let room = ship.blocks.0[0].rooms.get(RoomId(0)).as_ref().unwrap(); - ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move { - let (enemy_id, map_enemy) = (0..).filter_map(|i| { - room.maps.enemy_by_id(i).map(|enemy| { - (i, enemy) - }).ok() - }).next().unwrap(); - let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap(); - (enemy_id, map_enemy_stats.exp) - })).await.unwrap() - }; - ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp { - client: enemy_id as u8, + client: 0, target: 16, - enemy_id: enemy_id as u16, + enemy_id: 0, client_id: 0, unused: 0, last_hitter: 1, })))).await.unwrap(); ship.clients.with(ClientId(1), |client| Box::pin(async move { - assert!(exp == client.character.exp); + assert!(13 == client.character.exp); })).await.unwrap(); } @@ -61,30 +59,29 @@ async fn test_character_levels_up() { entity_gateway.save_character(&char1).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() - .gateway(entity_gateway.clone()) - .build()); + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))], + Vec::new(), + ) + })) + .build()); log_in_char(&mut ship, ClientId(1), "a1", "a").await; join_lobby(&mut ship, ClientId(1)).await; create_room(&mut ship, ClientId(1), "room", "").await; - let enemy_id = ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move { - (0..).filter_map(|i| { - room.maps.enemy_by_id(i).map(|_| { - i - }).ok() - }).next().unwrap() - })).await.unwrap(); - let levelup_pkt = ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp { - client: enemy_id as u8, + client: 0 as u8, target: 16, - enemy_id: enemy_id as u16, + enemy_id: 0 as u16, client_id: 0, unused: 0, last_hitter: 1, })))).await.unwrap(); - assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 2, ..})}))); + assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 1, ..})}))); let leveltable = CharacterLevelTable::default(); ship.clients.with(ClientId(1), |client| Box::pin(async move { @@ -99,42 +96,32 @@ async fn test_character_levels_up_multiple_times() { let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; let mut ship = Box::new(ShipServerState::builder() - .gateway(entity_gateway.clone()) - .build()); + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::DarkFalz2, MapArea::DarkFalz))], + Vec::new(), + ) + })) + .build()); log_in_char(&mut ship, ClientId(1), "a1", "a").await; join_lobby(&mut ship, ClientId(1)).await; create_room(&mut ship, ClientId(1), "room", "").await; - let (enemy_id, exp) = { - ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move { - let (enemy_id, map_enemy) = (0..).filter_map(|i| { - room.maps.enemy_by_id(i).ok().and_then(|enemy| { - if enemy.monster == MonsterType::DarkFalz2 { - Some((i, enemy)) - } - else { - None - } - }) - }).next().unwrap(); - let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap(); - (enemy_id, map_enemy_stats.exp) - })).await.unwrap() - }; - let levelup_pkt = ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp { - client: enemy_id as u8, + client: 0 as u8, target: 16, - enemy_id: enemy_id as u16, + enemy_id: 0 as u16, client_id: 0, unused: 0, last_hitter: 1, })))).await.unwrap(); - assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 8, ..})}))); + assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 7, ..})}))); ship.clients.with(ClientId(1), |client| Box::pin(async move { - assert!(exp == client.character.exp); + assert!(3000 == client.character.exp); })).await.unwrap(); } @@ -146,8 +133,15 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() { let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; let mut ship = Box::new(ShipServerState::builder() - .gateway(entity_gateway.clone()) - .build()); + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))], + Vec::new(), + ) + })) + .build()); log_in_char(&mut ship, ClientId(1), "a1", "a").await; log_in_char(&mut ship, ClientId(2), "a2", "a").await; @@ -157,43 +151,28 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() { create_room(&mut ship, ClientId(1), "room", "").await; join_room(&mut ship, ClientId(2), 0).await; - let (enemy_id, exp) = ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move { - let (enemy_id, map_enemy) = (0..).filter_map(|i| { - room.maps.enemy_by_id(i).ok().and_then(|enemy| { - if enemy.monster == MonsterType::DarkFalz2 { - Some((i, enemy)) - } - else { - None - } - }) - }).next().unwrap(); - let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap(); - (enemy_id, map_enemy_stats.exp) - })).await.unwrap(); - ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp { - client: enemy_id as u8, + client: 0, target: 16, - enemy_id: enemy_id as u16, + enemy_id: 0, client_id: 0, unused: 0, last_hitter: 1, })))).await.unwrap(); ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp { - client: enemy_id as u8, + client: 0, target: 16, - enemy_id: enemy_id as u16, + enemy_id: 0, client_id: 0, unused: 0, last_hitter: 0, })))).await.unwrap(); ship.clients.with(ClientId(1), |client| Box::pin(async move { - assert!(client.character.exp == exp); + assert_eq!(client.character.exp, 13); })).await.unwrap(); ship.clients.with(ClientId(2), |client| Box::pin(async move { - assert!(client.character.exp == (exp as f32 * 0.8) as u32); + assert_eq!(client.character.exp, 10); })).await.unwrap(); } diff --git a/tests/test_item_drop.rs b/tests/test_item_drop.rs new file mode 100644 index 0000000..02b01b1 --- /dev/null +++ b/tests/test_item_drop.rs @@ -0,0 +1,210 @@ +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::character::SectionID; +use elseware::common::leveltable::CharacterLevelTable; +use elseware::ship::ship::{ShipServerState, SendShipPacket, RecvShipPacket}; +use elseware::ship::room::{Episode, Difficulty}; +use elseware::ship::monster::MonsterType; +use elseware::ship::location::RoomId; +use elseware::ship::drops::{DropTable, MonsterDropStats, MonsterDropType}; +use elseware::ship::drops::rare_drop_table::{RareDropTable, RareDropRate, RareDropItem}; +use elseware::ship::map::{Maps, MapVariant, MapArea, MapVariantMode, MapEnemy}; +use elseware::entity::item::weapon::WeaponType; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; + +#[path = "common.rs"] +mod common; +use common::*; + +#[async_std::test] +async fn test_enemy_drops_item() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))], + Vec::new(), + ) + })) + .drop_table_builder(Box::new(|episode, difficulty, section_id| { + DropTable::builder() + .monster_stat(MonsterType::Hildebear, MonsterDropStats { + dar: 100, + drop_type: MonsterDropType::Weapon, + min_meseta: 0, + max_meseta: 0, + }) + .rare_table(RareDropTable::builder() + .rate(MonsterType::Hildebear, RareDropRate { + rate: 1.0, + item: RareDropItem::Weapon(WeaponType::DarkFlow) + }) + .build(episode, difficulty, section_id)) + .build(episode, difficulty, section_id) + })) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + let pkt = ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::RequestItem(RequestItem { + client: 0, + target: 0, + map_area: 2, + pt_index: 0, + enemy_id: 0, + x: 0.0, + z: 0.0, + y: 0.0, + })))).await.unwrap(); + + match &pkt[0].1 { + SendShipPacket::Message(Message{msg: GameMessage::ItemDrop(item_drop)}) => { + assert_eq!(item_drop.item_id, 0x810001); + assert_eq!(item_drop.item_bytes[1], 0x9D); + }, + _ => panic!(), + } +} + +#[async_std::test] +async fn test_enemy_drops_item_for_two_players() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))], + Vec::new(), + ) + })) + .drop_table_builder(Box::new(|episode, difficulty, section_id| { + DropTable::builder() + .monster_stat(MonsterType::Hildebear, MonsterDropStats { + dar: 100, + drop_type: MonsterDropType::Weapon, + min_meseta: 0, + max_meseta: 0, + }) + .rare_table(RareDropTable::builder() + .rate(MonsterType::Hildebear, RareDropRate { + rate: 1.0, + item: RareDropItem::Weapon(WeaponType::DarkFlow) + }) + .build(episode, difficulty, section_id)) + .build(episode, difficulty, section_id) + })) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let pkt = ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::RequestItem(RequestItem { + client: 0, + target: 0, + map_area: 2, + pt_index: 0, + enemy_id: 0, + x: 0.0, + z: 0.0, + y: 0.0, + })))).await.unwrap(); + + assert_eq!(pkt[0].0, ClientId(1)); + match &pkt[0].1 { + SendShipPacket::Message(Message{msg: GameMessage::ItemDrop(item_drop)}) => { + assert_eq!(item_drop.item_id, 0x810001); + assert_eq!(item_drop.item_bytes[1], 0x9D); + }, + _ => panic!(), + } + assert_eq!(pkt[1].0, ClientId(2)); + match &pkt[1].1 { + SendShipPacket::Message(Message{msg: GameMessage::ItemDrop(item_drop)}) => { + assert_eq!(item_drop.item_id, 0x810002); + assert_eq!(item_drop.item_bytes[1], 0x9D); + }, + _ => panic!(), + } +} + +#[async_std::test] +async fn test_enemy_drops_item_for_two_players_and_pick_up() { + let mut entity_gateway = InMemoryGateway::default(); + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .map_builder(Box::new(|_room_mode, _event| { + Maps::new( + vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)], + vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))], + Vec::new(), + ) + })) + .drop_table_builder(Box::new(|episode, difficulty, section_id| { + DropTable::builder() + .monster_stat(MonsterType::Hildebear, MonsterDropStats { + dar: 100, + drop_type: MonsterDropType::Weapon, + min_meseta: 0, + max_meseta: 0, + }) + .rare_table(RareDropTable::builder() + .rate(MonsterType::Hildebear, RareDropRate { + rate: 1.0, + item: RareDropItem::Weapon(WeaponType::DarkFlow) + }) + .build(episode, difficulty, section_id)) + .build(episode, difficulty, section_id) + })) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::RequestItem(RequestItem { + client: 0, + target: 0, + map_area: 2, + pt_index: 0, + enemy_id: 0, + x: 0.0, + z: 0.0, + y: 0.0, + })))).await.unwrap(); + + ship.handle(ClientId(2), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0x810002, + map_area: 0, + unknown: [0; 3] + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0x810001, + map_area: 0, + unknown: [0; 3] + })))).await.unwrap(); + +} diff --git a/tests/test_item_use.rs b/tests/test_item_use.rs index 2289822..f9a9b67 100644 --- a/tests/test_item_use.rs +++ b/tests/test_item_use.rs @@ -2,6 +2,7 @@ use elseware::common::serverstate::{ClientId, ServerState}; use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::entity::item; use elseware::ship::ship::{ShipServerState, RecvShipPacket}; +use elseware::entity::character::TechLevel; //use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType}; use libpso::packet::ship::*; @@ -304,6 +305,92 @@ async fn test_jackolantern() { } } + +#[async_std::test] +async fn test_use_barta_1() { + let mut entity_gateway = InMemoryGateway::default(); + + let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; + + /* + let mut p1_inv = Vec::new(); + for tool in vec![item::tool::ToolType::PowerMaterial, item::tool::ToolType::].into_iter() { + let mut item = Vec::new(); + for _ in 0..5usize { + item.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + }).await.unwrap()); + } + p1_inv.push(item::InventoryItemEntity::Stacked(item)); +}*/ + let inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::TechniqueDisk( + item::tech::TechniqueDisk { + tech: item::tech::Technique::Foie, + level: 3, + } + ) + } + ).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::TechniqueDisk( + item::tech::TechniqueDisk { + tech: item::tech::Technique::Barta, + level: 4, + } + ) + } + ).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::TechniqueDisk( + item::tech::TechniqueDisk { + tech: item::tech::Technique::Zonde, + level: 5, + } + ) + } + ).await.unwrap() + ]; + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap(); + + ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10002, + })))).await.unwrap(); + + let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap(); + let char = characters[0].as_ref().unwrap(); + + assert_eq!(char.techs.techs.len(), 2); + assert_eq!(char.techs.techs.get(&item::tech::Technique::Foie).unwrap(), &TechLevel(3)); + assert_eq!(char.techs.techs.get(&item::tech::Technique::Zonde).unwrap(), &TechLevel(5)); + assert!(char.techs.techs.get(&item::tech::Technique::Barta).is_none()); +} + // TODO: tests for ALL ITEMS WOW /* diff --git a/tests/test_rooms.rs b/tests/test_rooms.rs index 55635c6..45a5210 100644 --- a/tests/test_rooms.rs +++ b/tests/test_rooms.rs @@ -97,6 +97,7 @@ async fn test_item_ids_reset_when_rejoining_rooms() { } } +/* #[async_std::test] async fn test_load_rare_monster_default_appear_rates() { let mut entity_gateway = InMemoryGateway::default(); @@ -116,6 +117,7 @@ async fn test_load_rare_monster_default_appear_rates() { } })).await.unwrap(); } +*/ #[async_std::test] async fn test_set_valid_quest_group() { diff --git a/tests/test_shops.rs b/tests/test_shops.rs index 32244d0..cf230c9 100644 --- a/tests/test_shops.rs +++ b/tests/test_shops.rs @@ -1143,7 +1143,8 @@ async fn test_player_cant_sell_if_meseta_would_go_over_max() { amount: 1, })))).await.err().unwrap(); //assert_eq!(ack, ShipError::ItemStateError(ItemStateError::FullOfMeseta)); - assert!(matches!(ack.downcast::().unwrap(), ShipError::ItemStateError(ItemStateError::FullOfMeseta))); + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::FullOfMeseta)); + //assert!(matches!(ack.downcast::().unwrap(), ShipError::ItemStateError(ItemStateError::FullOfMeseta))); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); assert_eq!(c1_meseta.0, 999995); diff --git a/tests/test_trade.rs b/tests/test_trade.rs index 61bff99..4155a46 100644 --- a/tests/test_trade.rs +++ b/tests/test_trade.rs @@ -3155,7 +3155,7 @@ async fn test_client_tries_to_start_two_trades() { trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) })))).await.err().unwrap(); - assert!(matches!(ack.downcast::().unwrap(), ShipError::TradeError(TradeError::ClientAlreadyInTrade))); + assert!(matches!(ack.downcast::().unwrap(), TradeError::ClientAlreadyInTrade)); } #[async_std::test] @@ -3187,14 +3187,14 @@ async fn test_client_tries_trading_with_client_already_trading() { target: 0, trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) })))).await.err().unwrap(); - assert!(matches!(ack.downcast::().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade))); + assert!(matches!(ack.downcast::().unwrap(), TradeError::OtherAlreadyInTrade)); let ack = ship.handle(ClientId(3), RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { client: 2, target: 0, trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 1) })))).await.err().unwrap(); - assert!(matches!(ack.downcast::().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade))); + assert!(matches!(ack.downcast::().unwrap(), TradeError::OtherAlreadyInTrade)); } #[async_std::test]