Browse Source

Merge pull request 'bunch of small fixes' (#127) from make_actually_work into master

Reviewed-on: #127
pull/129/head
jake 2 years ago
parent
commit
f3bfa658cd
  1. 6
      Cargo.lock
  2. 2
      Cargo.toml
  3. 6
      src/bin/main.rs
  4. 31
      src/common/mainloop/client.rs
  5. 96
      src/entity/character.rs
  6. 9
      src/entity/gateway/entitygateway.rs
  7. 17
      src/entity/gateway/inmemory.rs
  8. 9
      src/entity/gateway/postgres/migrations/V0004__meseta.sql
  9. 4
      src/entity/gateway/postgres/migrations/V0005__trade.sql
  10. 3
      src/entity/gateway/postgres/migrations/V0007__player_keyconfig.sql
  11. 5
      src/entity/gateway/postgres/migrations/V0008__playtime_not_null.sql
  12. 3
      src/entity/gateway/postgres/migrations/V0009__no_player_keyconfig.sql
  13. 2
      src/entity/gateway/postgres/migrations/V0010__char_create_timestamp.sql
  14. 18
      src/entity/gateway/postgres/models.rs
  15. 65
      src/entity/gateway/postgres/postgres.rs
  16. 2
      src/entity/item/mag.rs
  17. 4
      src/lib.rs
  18. 16
      src/login/character.rs
  19. 2
      src/login/login.rs
  20. 6
      src/patch/patch.rs
  21. 6
      src/ship/client.rs
  22. 88
      src/ship/drops/mod.rs
  23. 70
      src/ship/drops/rare_drop_table.rs
  24. 90
      src/ship/items/actions.rs
  25. 76
      src/ship/items/apply_item.rs
  26. 20
      src/ship/items/bank.rs
  27. 6
      src/ship/items/floor.rs
  28. 42
      src/ship/items/inventory.rs
  29. 32
      src/ship/items/state.rs
  30. 40
      src/ship/items/tasks.rs
  31. 26
      src/ship/location.rs
  32. 11
      src/ship/map/enemy.rs
  33. 102
      src/ship/map/maps.rs
  34. 6
      src/ship/map/mod.rs
  35. 11
      src/ship/packet/builder/lobby.rs
  36. 13
      src/ship/packet/builder/room.rs
  37. 2
      src/ship/packet/handler/auth.rs
  38. 10
      src/ship/packet/handler/communication.rs
  39. 45
      src/ship/packet/handler/direct_message.rs
  40. 16
      src/ship/packet/handler/lobby.rs
  41. 36
      src/ship/packet/handler/message.rs
  42. 41
      src/ship/packet/handler/quest.rs
  43. 41
      src/ship/packet/handler/room.rs
  44. 18
      src/ship/packet/handler/settings.rs
  45. 26
      src/ship/packet/handler/trade.rs
  46. 65
      src/ship/room.rs
  47. 44
      src/ship/ship.rs
  48. 3
      tests/common.rs
  49. 29
      tests/test_character.rs
  50. 117
      tests/test_exp_gain.rs
  51. 210
      tests/test_item_drop.rs
  52. 87
      tests/test_item_use.rs
  53. 2
      tests/test_rooms.rs
  54. 3
      tests/test_shops.rs
  55. 6
      tests/test_trade.rs

6
Cargo.lock

@ -48,9 +48,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.57"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -1040,6 +1040,7 @@ checksum = "739e9d7726dc32173fed2d69d17eef3c54682169e4e20ff1d0a45dcd37063cef"
[[package]] [[package]]
name = "libpso" name = "libpso"
version = "0.1.0" version = "0.1.0"
source = "git+http://git.sharnoth.com/jake/libpso#5051514fb1d3b39a7eb6ff97b624a9ceebd93e40"
dependencies = [ dependencies = [
"chrono", "chrono",
"psopacket", "psopacket",
@ -1398,6 +1399,7 @@ dependencies = [
[[package]] [[package]]
name = "psopacket" name = "psopacket"
version = "1.0.0" version = "1.0.0"
source = "git+http://git.sharnoth.com/jake/libpso#5051514fb1d3b39a7eb6ff97b624a9ceebd93e40"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

2
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"] } sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
strum = "0.19.5" strum = "0.19.5"
strum_macros = "0.19" strum_macros = "0.19"
anyhow = { version = "1.0.47", features = ["backtrace"] }
anyhow = { version = "1.0.68", features = ["backtrace"] }

6
src/bin/main.rs

@ -52,7 +52,7 @@ fn main() {
for i in 0..5 { for i in 0..5 {
let fake_user = NewUserAccountEntity { 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) }, username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) },
password: bcrypt::hash("qwer", 5).unwrap(), password: bcrypt::hash("qwer", 5).unwrap(),
guildcard: i + 1, guildcard: i + 1,
@ -64,12 +64,12 @@ fn main() {
}; };
let fake_user = entity_gateway.create_user(fake_user).await.unwrap(); let fake_user = entity_gateway.create_user(fake_user).await.unwrap();
entity_gateway.create_user_settings(NewUserSettingsEntity::new(fake_user.id)).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); character.name = format!("Test Char {}", i*2);
let character = entity_gateway.create_character(character).await.unwrap(); let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).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(); 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.slot = 2;
character.name = "ItemRefactor".into(); character.name = "ItemRefactor".into();
character.exp = 80000000; character.exp = 80000000;

31
src/common/mainloop/client.rs

@ -1,10 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Write;
use async_std::channel; use async_std::channel;
use async_std::io::prelude::{ReadExt, WriteExt}; use async_std::io::prelude::{ReadExt, WriteExt};
use async_std::sync::{Arc, RwLock}; use async_std::sync::{Arc, RwLock};
use futures::future::Future; use futures::future::Future;
use log::{trace, info, warn};
use log::{trace, info, warn, error};
use libpso::crypto::{PSOCipher, NullCipher, CipherError}; use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use libpso::PacketParseError; use libpso::PacketParseError;
@ -132,7 +133,7 @@ where
match pkt_receiver.recv_pkts::<R>().await { match pkt_receiver.recv_pkts::<R>().await {
Ok(pkts) => { Ok(pkts) => {
for pkt in pkts { for pkt in pkts {
info!("[recv from {:?}] {:#?}", client_id, pkt);
trace!("[recv from {:?}] {:#?}", client_id, pkt);
match state.handle(client_id, pkt).await { match state.handle(client_id, pkt).await {
Ok(response) => { Ok(response) => {
for resp in response { for resp in response {
@ -147,7 +148,27 @@ where
} }
}, },
Err(err) => { 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; break;
} }
_ => { _ => {
warn!("[client {:?} recv error] {:?}", client_id, err);
error!("[client {:?} recv error] {:?}", client_id, err);
} }
} }
} }
@ -206,7 +227,7 @@ where
Ok(pkt) => { Ok(pkt) => {
info!("[send to {:?}] {:#?}", client_id, pkt); info!("[send to {:?}] {:#?}", client_id, pkt);
if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await { 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) => { Err(err) => {

96
src/entity/character.rs

@ -2,8 +2,8 @@ use std::convert::{From, Into};
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Serialize, Deserialize}; 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::item::tech::Technique;
use crate::entity::account::UserAccountId; 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); pub struct TechLevel(pub u8);
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -167,16 +167,14 @@ pub struct CharacterTechniques {
impl CharacterTechniques { impl CharacterTechniques {
pub fn set_tech(&mut self, tech: Technique, level: TechLevel) { 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] { pub fn as_bytes(&self) -> [u8; 20] {
self.techs.iter() self.techs.iter()
.fold([0xFF; 20], |mut techlist, (tech, level)| { .fold([0xFF; 20], |mut techlist, (tech, level)| {
let index = tech.as_value(); let index = tech.as_value();
techlist[index as usize] = level.0;
techlist[index as usize] = level.0 - 1;
techlist techlist
}) })
} }
@ -264,82 +262,6 @@ pub struct CharacterMaterials {
pub tp: u32, 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)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)]
pub struct CharacterEntityId(pub u32); pub struct CharacterEntityId(pub u32);
@ -363,12 +285,10 @@ pub struct NewCharacterEntity {
pub tech_menu: CharacterTechMenu, pub tech_menu: CharacterTechMenu,
pub option_flags: u32, pub option_flags: u32,
pub keyboard_config: CharacterKeyboardConfig,
pub gamepad_config: CharacterGamepadConfig,
} }
impl NewCharacterEntity { impl NewCharacterEntity {
pub fn new(user: UserAccountId, keyboard_config_preset: usize,) -> NewCharacterEntity {
pub fn new(user: UserAccountId) -> NewCharacterEntity {
NewCharacterEntity { NewCharacterEntity {
user_id: user, user_id: user,
slot: 0, slot: 0,
@ -384,8 +304,6 @@ impl NewCharacterEntity {
materials: CharacterMaterials::default(), materials: CharacterMaterials::default(),
tech_menu: CharacterTechMenu::default(), tech_menu: CharacterTechMenu::default(),
option_flags: 0, 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 tech_menu: CharacterTechMenu,
pub option_flags: u32, pub option_flags: u32,
pub keyboard_config: CharacterKeyboardConfig,
pub gamepad_config: CharacterGamepadConfig,
pub playtime: u32, pub playtime: u32,
} }

9
src/entity/gateway/entitygateway.rs

@ -1,4 +1,3 @@
use std::convert::From;
use thiserror::Error; use thiserror::Error;
use futures::Future; use futures::Future;
@ -10,9 +9,10 @@ use crate::entity::item::*;
// TODO: better granularity? // TODO: better granularity?
//#[derive(Error, Debug)] //#[derive(Error, Debug)]
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("")]
pub enum GatewayError { pub enum GatewayError {
#[error("unknown error")]
Error, Error,
#[error("postgres error {0}")]
PgError(#[from] sqlx::Error) PgError(#[from] sqlx::Error)
} }
@ -21,12 +21,11 @@ pub enum GatewayError {
pub trait EntityGateway: Send + Sync { pub trait EntityGateway: Send + Sync {
type Transaction: EntityGatewayTransaction + Clone; type Transaction: EntityGatewayTransaction + Clone;
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result<R, E>
async fn with_transaction<'a, F, Fut, R>(&'a mut self, _func: F) -> Result<R, anyhow::Error>
where where
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
Fut: Future<Output = Result<(Self::Transaction, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction) -> Fut + Send, F: FnOnce(Self::Transaction) -> Fut + Send,
R: Send, R: Send,
E: From<GatewayError>,
Self: Sized Self: Sized
{ {
unimplemented!(); unimplemented!();

17
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> { async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
copy_if_needed(&mut *self.working_gateway.characters.lock().await, copy_if_needed(&mut *self.working_gateway.characters.lock().await,
&*self.original_gateway.characters.lock().await, &*self.original_gateway.characters.lock().await,
@ -304,12 +308,11 @@ fn apply_modifiers(items: &BTreeMap<ItemEntityId, ItemEntity>,
impl EntityGateway for InMemoryGateway { impl EntityGateway for InMemoryGateway {
type Transaction = InMemoryGatewayTransaction; type Transaction = InMemoryGatewayTransaction;
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
async fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> Result<R, anyhow::Error>
where where
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
Fut: Future<Output = Result<(Self::Transaction, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction) -> Fut + Send, F: FnOnce(Self::Transaction) -> Fut + Send,
R: Send, R: Send,
E: From<GatewayError>,
{ {
let users = self.users.lock().await.clone(); let users = self.users.lock().await.clone();
let user_settings = self.user_settings.lock().await.clone(); let user_settings = self.user_settings.lock().await.clone();
@ -419,6 +422,12 @@ impl EntityGateway for InMemoryGateway {
.ok_or(GatewayError::Error) .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<CharacterEntity>; 4], GatewayError> { async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
let characters = self.characters.lock().await; let characters = self.characters.lock().await;
const NONE: Option<CharacterEntity> = None; const NONE: Option<CharacterEntity> = None;
@ -453,8 +462,6 @@ impl EntityGateway for InMemoryGateway {
materials: character.materials, materials: character.materials,
tech_menu: character.tech_menu, tech_menu: character.tech_menu,
option_flags: character.option_flags, option_flags: character.option_flags,
keyboard_config: character.keyboard_config,
gamepad_config: character.gamepad_config,
playtime: 0, playtime: 0,
}; };
characters.insert(new_character.id, new_character.clone()); characters.insert(new_character.id, new_character.clone());

9
src/entity/gateway/postgres/migrations/V0004__meseta.sql

@ -1,10 +1,10 @@
create table character_meseta ( 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 ( create table bank_meseta (
pchar integer references character (id) not null,
pchar integer references player_character (id) not null,
bank varchar(128) not null, bank varchar(128) not null,
meseta integer not null, meseta integer not null,
unique (pchar, bank) unique (pchar, bank)
@ -12,4 +12,5 @@ create table bank_meseta (
alter table player_character alter table player_character
drop column meseta, bank_meseta;
drop column meseta,
drop column bank_meseta;

4
src/entity/gateway/postgres/migrations/V0005__trade.sql

@ -1,5 +1,5 @@
create table trades ( create table trades (
id serial primary key not null, 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
); );

3
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;

5
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;

3
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;

2
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;

18
src/entity/gateway/postgres/models.rs

@ -49,8 +49,8 @@ pub struct PgUserSettings {
id: i32, id: i32,
user_account: i32, user_account: i32,
blocked_users: Vec<u8>, //[u32; 0x1E], blocked_users: Vec<u8>, //[u32; 0x1E],
keyboard_config: Vec<u8>, //[u8; 0x16C],
gamepad_config: Vec<u8>, //[u8; 0x38],
key_config: Vec<u8>, //[u8; 0x16C],
joystick_config: Vec<u8>, //[u8; 0x38],
option_flags: i32, option_flags: i32,
shortcuts: Vec<u8>, //[u8; 0xA40], shortcuts: Vec<u8>, //[u8; 0xA40],
symbol_chats: Vec<u8>, //[u8; 0x4E0], symbol_chats: Vec<u8>, //[u8; 0x4E0],
@ -64,8 +64,8 @@ impl From<PgUserSettings> for UserSettingsEntity {
user_id: UserAccountId(other.user_account as u32), user_id: UserAccountId(other.user_account as u32),
settings: settings::UserSettings { 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()), 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, option_flags: other.option_flags as u32,
shortcuts: vec_to_array(other.shortcuts), shortcuts: vec_to_array(other.shortcuts),
symbol_chats: vec_to_array(other.symbol_chats), symbol_chats: vec_to_array(other.symbol_chats),
@ -217,8 +217,6 @@ pub struct PgCharacter {
tp: i16, tp: i16,
tech_menu: Vec<u8>, tech_menu: Vec<u8>,
keyboard_config: Vec<u8>,
gamepad_config: Vec<u8>,
playtime: i32, playtime: i32,
} }
@ -246,7 +244,7 @@ impl From<PgCharacter> for CharacterEntity {
prop_y: other.prop_y, prop_y: other.prop_y,
}, },
techs: CharacterTechniques { 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 { config: CharacterConfig {
raw_data: vec_to_array(other.config) raw_data: vec_to_array(other.config)
@ -270,12 +268,6 @@ impl From<PgCharacter> for CharacterEntity {
tech_menu: CharacterTechMenu { tech_menu: CharacterTechMenu {
tech_menu: vec_to_array(other.tech_menu) 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, playtime: other.playtime as u32,
} }
} }

65
src/entity/gateway/postgres/postgres.rs

@ -2,7 +2,7 @@
#![allow(clippy::explicit_auto_deref)] #![allow(clippy::explicit_auto_deref)]
use std::convert::{From, TryFrom, Into}; use std::convert::{From, TryFrom, Into};
use futures::{Future, TryStreamExt};
use futures::Future;
use async_std::stream::StreamExt; use async_std::stream::StreamExt;
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
use libpso::character::guildcard; use libpso::character::guildcard;
@ -67,7 +67,7 @@ impl<'t> PostgresGateway<'t> {
let pool = async_std::task::block_on(async move { let pool = async_std::task::block_on(async move {
PgPoolOptions::new() PgPoolOptions::new()
.max_connections(5) .max_connections(5)
.connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap()
.connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap()
}); });
PostgresGateway { 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.symbol_chats.to_vec())
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.id.0) .bind(settings.id.0)
.fetch_one(conn).await?;
.execute(conn).await?;
Ok(()) Ok(())
} }
async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError>
{ {
let q = r#"insert into player_character 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 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 *;"#; returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q) let character = sqlx::query_as::<_, PgCharacter>(q)
.bind(char.user_id.0) .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.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec()) .bind(char.tech_menu.tech_menu.to_vec())
.bind(char.option_flags as i32) .bind(char.option_flags as i32)
.bind(0)
.fetch_one(conn).await?; .fetch_one(conn).await?;
Ok(character.into()) 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<CharacterEntity>; 4], GatewayError> async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 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) .bind(user.id.0)
.fetch(conn); .fetch(conn);
const NONE: Option<CharacterEntity> = 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> async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -> Result<(), GatewayError>
@ -316,7 +326,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
.bind(char.materials.tp as i16) // $27 .bind(char.materials.tp as i16) // $27
.bind(char.tech_menu.tech_menu.to_vec()) // $28 .bind(char.tech_menu.tech_menu.to_vec()) // $28
.bind(char.option_flags as i32) // $29 .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 .bind(char.id.0 as i32) // $31
.execute(conn).await?; .execute(conn).await?;
Ok(()) 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> 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(char_id.0)
.bind(meseta.0 as i32) .bind(meseta.0 as i32)
.execute(conn) .execute(conn)
@ -542,7 +552,7 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
{ {
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
struct PgMeseta(i32); 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) .bind(char_id.0)
.fetch_one(conn) .fetch_one(conn)
.await?; .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> 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(char_id.0)
.bind(meseta.0 as i32)
.bind(bank.0.clone()) .bind(bank.0.clone())
.bind(meseta.0 as i32)
.execute(conn) .execute(conn)
.await?; .await?;
Ok(()) Ok(())
@ -564,7 +574,7 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
{ {
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
struct PgMeseta(i32); 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(char_id.0)
.bind(bank.0.clone()) .bind(bank.0.clone())
.fetch_one(conn) .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> 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(char_id.0)
.bind(playtime) .bind(playtime)
.fetch_one(conn)
.execute(conn)
.await?; .await?;
Ok(()) Ok(())
} }
@ -596,18 +606,17 @@ async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &Charact
impl<'t> EntityGateway for PostgresGateway<'t> { impl<'t> EntityGateway for PostgresGateway<'t> {
type Transaction = PostgresTransaction<'t>; type Transaction = PostgresTransaction<'t>;
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
async fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> Result<R, anyhow::Error>
where where
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
Fut: Future<Output = Result<(Self::Transaction, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction) -> Fut + Send, F: FnOnce(Self::Transaction) -> Fut + Send,
R: Send, R: Send,
E: From<GatewayError>,
{ {
let transaction = PostgresTransaction { 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) Ok(result)
} }

2
src/entity/item/mag.rs

@ -521,7 +521,7 @@ pub enum MagCellError {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum MagModifier { pub enum MagModifier {
FeedMag{
FeedMag {
food: ItemEntityId, food: ItemEntityId,
}, },
BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags

4
src/lib.rs

@ -1,10 +1,12 @@
#![allow(clippy::type_complexity)]
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![feature(inline_const)] #![feature(inline_const)]
#![feature(drain_filter)] #![feature(drain_filter)]
#![feature(try_blocks)] #![feature(try_blocks)]
#![feature(once_cell)] #![feature(once_cell)]
#![feature(pin_macro)]
#![feature(test)] #![feature(test)]
#![feature(error_generic_member_access)]
#![feature(provide_any)]
extern crate test; extern crate test;

16
src/login/character.rs

@ -38,13 +38,18 @@ pub const CHARACTER_PORT: u16 = 12001;
pub const SHIP_MENU_ID: u32 = 1; pub const SHIP_MENU_ID: u32 = 1;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[error("")]
pub enum CharacterError { pub enum CharacterError {
#[error("invalid menu selection {0} {1}")]
InvalidMenuSelection(u32, u32), InvalidMenuSelection(u32, u32),
#[error("client not found {0}")]
ClientNotFound(ClientId), ClientNotFound(ClientId),
CouldNotLoadSettings,
#[error("could not load settings {0}")]
CouldNotLoadSettings(GatewayError),
#[error("could not load characters")]
CouldNotLoadCharacters, CouldNotLoadCharacters,
#[error("could not load guildcard")]
CouldNotLoadGuildcard, CouldNotLoadGuildcard,
#[error("gateway error {0}")]
GatewayError(#[from] GatewayError), GatewayError(#[from] GatewayError),
} }
@ -206,6 +211,7 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
let character = entity_gateway.create_character(character).await?; let character = entity_gateway.create_character(character).await?;
entity_gateway.set_character_meseta(&character.id, Meseta(300)).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 { let new_weapon = match character.char_class {
CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber, CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber,
@ -260,6 +266,8 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
character_id: character.id, character_id: character.id,
}).await?; }).await?;
entity_gateway.change_mag_owner(&mag.id, &character).await?;
let mut monomates = Vec::new(); let mut monomates = Vec::new();
for _ in 0..4usize { for _ in 0..4usize {
let monomate = entity_gateway.create_item( let monomate = entity_gateway.create_item(
@ -385,7 +393,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
Ok(settings) => settings, Ok(settings) => settings,
Err(_) => { Err(_) => {
let user_settings = NewUserSettingsEntity::new(user.id); 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<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
fn new_character_from_preview(user: &UserAccountEntity, preview: &CharacterPreview) -> NewCharacterEntity { 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.slot = preview.slot;
character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into(); character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into();
character.section_id = preview.character.section_id.into(); character.section_id = preview.character.section_id.into();

2
src/login/login.rs

@ -21,8 +21,8 @@ pub const LOGIN_PORT: u16 = 12000;
pub const COMMUNICATION_PORT: u16 = 12123; pub const COMMUNICATION_PORT: u16 = 12123;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[error("")]
pub enum LoginError { pub enum LoginError {
#[error("dberror")]
DbError DbError
} }

6
src/patch/patch.rs

@ -395,18 +395,18 @@ pub struct PatchConfig {
pub fn load_config() -> PatchConfig { pub fn load_config() -> PatchConfig {
let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) { 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, Ok(ini_file) => ini_file,
}; };
let mut s = String::new(); let mut s = String::new();
if let Err(err) = (&ini_file).read_to_string(&mut s) { 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()) { let config: PatchConfig = match from_str(s.as_str()) {
Ok(config) => config, 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 config
} }

6
src/ship/client.rs

@ -36,7 +36,7 @@ impl Clients {
.into_inner()) .into_inner())
} }
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, ShipError>
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
where where
T: Send, T: Send,
F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a, F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a,
@ -53,7 +53,7 @@ impl Clients {
Ok(func(&client).await) Ok(func(&client).await)
} }
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, ShipError>
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, anyhow::Error>
where where
T: Send, T: Send,
F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a, F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a,
@ -85,7 +85,7 @@ impl Clients {
Ok(func(client_states).await) Ok(func(client_states).await)
} }
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, ShipError>
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
where where
T: Send, T: Send,
F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a, F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a,

88
src/ship/drops/mod.rs

@ -6,7 +6,7 @@
mod drop_table; mod drop_table;
mod rare_drop_table;
pub mod rare_drop_table;
mod generic_weapon; mod generic_weapon;
mod generic_armor; mod generic_armor;
mod generic_shield; mod generic_shield;
@ -112,7 +112,7 @@ pub struct ItemDrop {
} }
pub struct DropTable<R: Rng + SeedableRng> {
pub struct DropTable {
monster_stats: HashMap<MonsterType, MonsterDropStats>, monster_stats: HashMap<MonsterType, MonsterDropStats>,
rare_table: RareDropTable, rare_table: RareDropTable,
weapon_table: GenericWeaponTable, weapon_table: GenericWeaponTable,
@ -121,11 +121,11 @@ pub struct DropTable<R: Rng + SeedableRng> {
unit_table: GenericUnitTable, unit_table: GenericUnitTable,
tool_table: ToolTable, tool_table: ToolTable,
box_table: BoxDropTable, box_table: BoxDropTable,
rng: R,
rng: rand_chacha::ChaCha20Rng,
} }
impl<R: Rng + SeedableRng> DropTable<R> {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
impl DropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
DropTable { DropTable {
@ -137,7 +137,21 @@ impl<R: Rng + SeedableRng> DropTable<R> {
unit_table: GenericUnitTable::new(episode, difficulty, section_id), unit_table: GenericUnitTable::new(episode, difficulty, section_id),
tool_table: ToolTable::new(episode, difficulty, section_id), tool_table: ToolTable::new(episode, difficulty, section_id),
box_table: BoxDropTable::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<R: Rng + SeedableRng> DropTable<R> {
} }
pub struct DropTableBuilder {
monster_stats: Option<HashMap<MonsterType, MonsterDropStats>>,
rare_table: Option<RareDropTable>,
weapon_table: Option<GenericWeaponTable>,
armor_table: Option<GenericArmorTable>,
shield_table: Option<GenericShieldTable>,
unit_table: Option<GenericUnitTable>,
tool_table: Option<ToolTable>,
box_table: Option<BoxDropTable>,
rng: Option<rand_chacha::ChaCha20Rng>,
}
// 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<MonsterType, MonsterDropStats>) -> 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<String, MonsterDropStats> = 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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -203,6 +277,6 @@ mod test {
let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum, let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill] SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]
.into_iter().choose(&mut rng).unwrap(); .into_iter().choose(&mut rng).unwrap();
DropTable::<rand_chacha::ChaCha20Rng>::new(episode, difficulty, section_id);
DropTable::new(episode, difficulty, section_id);
} }
} }

70
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,11 +71,10 @@ pub struct RareDropTable {
shield_stats: GenericShieldTable, shield_stats: GenericShieldTable,
} }
impl RareDropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
fn load_default_monster_rates(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> HashMap<MonsterType, Vec<RareDropRate>> {
let cfg: HashMap<String, Vec<RareDropConfigEntity>> = load_data_file(episode, difficulty, section_id, "rare_rate.toml"); let cfg: HashMap<String, Vec<RareDropConfigEntity>> = load_data_file(episode, difficulty, section_id, "rare_rate.toml");
let rates = cfg.into_iter()
cfg.into_iter()
.map(|(monster, drops)| { .map(|(monster, drops)| {
let monster = monster.parse().unwrap(); let monster = monster.parse().unwrap();
let drops = drops.into_iter().map(|drop| { let drops = drops.into_iter().map(|drop| {
@ -85,16 +84,28 @@ impl RareDropTable {
} }
}).collect(); }).collect();
(monster, drops) (monster, drops)
}).collect();
}).collect()
}
impl RareDropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
RareDropTable { RareDropTable {
rates,
rates: load_default_monster_rates(episode, difficulty, section_id),
attribute_table: AttributeTable::new(episode, difficulty, section_id), attribute_table: AttributeTable::new(episode, difficulty, section_id),
armor_stats: GenericArmorTable::new(episode, difficulty, section_id), armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
shield_stats: GenericShieldTable::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<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType { pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
match item { match item {
RareDropItem::Weapon(weapon) => { RareDropItem::Weapon(weapon) => {
@ -155,3 +166,46 @@ impl RareDropTable {
}) })
} }
} }
pub struct RareDropTableBuilder {
rates: Option<HashMap<MonsterType, Vec<RareDropRate>>>,
attribute_table: Option<AttributeTable>,
armor_stats: Option<GenericArmorTable>,
shield_stats: Option<GenericShieldTable>,
}
// TODO: add the rest of these later I just need these ones right now
impl RareDropTableBuilder {
pub fn rates(mut self, rates: HashMap<MonsterType, Vec<RareDropRate>>) -> 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)),
}
}
}

90
src/ship/items/actions.rs

@ -35,7 +35,7 @@ pub(super) fn take_item_from_floor<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
item_id: ClientItemId item_id: ClientItemId
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where where
EG: EntityGateway + Send, EG: EntityGateway + Send,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -54,7 +54,7 @@ where
pub(super) fn add_floor_item_to_inventory<EG, TR>( pub(super) fn add_floor_item_to_inventory<EG, TR>(
character: &CharacterEntity character: &CharacterEntity
) -> impl Fn((ItemStateProxy, TR), FloorItem) ) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<Result<((ItemStateProxy, TR), TriggerCreateItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), TriggerCreateItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'static,
@ -103,7 +103,7 @@ pub(super) fn take_item_from_inventory<EG, TR>(
item_id: ClientItemId, item_id: ClientItemId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -111,7 +111,7 @@ where
move |(mut item_state, mut transaction), _| { move |(mut item_state, mut transaction), _| {
Box::pin(async move { Box::pin(async move {
let mut inventory = item_state.inventory(&character_id).await?; 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?; transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await; item_state.set_inventory(inventory).await;
@ -127,7 +127,7 @@ pub(super) fn add_inventory_item_to_shared_floor<EG, TR>(
map_area: MapArea, map_area: MapArea,
drop_position: (f32, f32, f32), drop_position: (f32, f32, f32),
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -160,7 +160,7 @@ pub(super) fn take_meseta_from_inventory<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -181,7 +181,7 @@ pub(super) fn add_meseta_to_inventory<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
amount: u32 amount: u32
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -204,7 +204,7 @@ pub(super) fn add_meseta_to_shared_floor<EG, TR>(
map_area: MapArea, map_area: MapArea,
drop_position: (f32, f32) drop_position: (f32, f32)
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -233,7 +233,7 @@ pub(super) fn take_meseta_from_bank<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -243,6 +243,7 @@ where
let mut bank = item_state.bank(&character_id).await?; let mut bank = item_state.bank(&character_id).await?;
bank.remove_meseta(amount)?; bank.remove_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ())) Ok(((item_state, transaction), ()))
}) })
@ -253,7 +254,7 @@ pub(super) fn add_meseta_from_bank_to_inventory<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -274,7 +275,7 @@ pub(super) fn add_meseta_to_bank<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -284,6 +285,7 @@ where
let mut bank = item_state.bank(&character_id).await?; let mut bank = item_state.bank(&character_id).await?;
bank.add_meseta(amount)?; bank.add_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ())) Ok(((item_state, transaction), ()))
}) })
@ -296,7 +298,7 @@ pub(super) fn take_item_from_bank<EG, TR>(
item_id: ClientItemId, item_id: ClientItemId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), BankItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), BankItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -316,7 +318,7 @@ where
pub(super) fn add_bank_item_to_inventory<EG, TR>( pub(super) fn add_bank_item_to_inventory<EG, TR>(
character: &CharacterEntity, character: &CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), BankItem) ) -> impl Fn((ItemStateProxy, TR), BankItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -367,7 +369,7 @@ where
pub(super) fn add_inventory_item_to_bank<EG, TR>( pub(super) fn add_inventory_item_to_bank<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -403,7 +405,7 @@ pub(super) fn equip_inventory_item<EG, TR>(
item_id: ClientItemId, item_id: ClientItemId,
equip_slot: u8, equip_slot: u8,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -425,7 +427,7 @@ pub(super) fn unequip_inventory_item<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
item_id: ClientItemId, item_id: ClientItemId,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -448,7 +450,7 @@ pub(super) fn sort_inventory_items<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
item_ids: Vec<ClientItemId>, item_ids: Vec<ClientItemId>,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -470,7 +472,7 @@ where
pub(super) fn use_consumed_item<EG, TR>( pub(super) fn use_consumed_item<EG, TR>(
character: &CharacterEntity, character: &CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), anyhow::Error>>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -497,7 +499,7 @@ pub(super) fn feed_mag_item<EG, TR>(
character: CharacterEntity, character: CharacterEntity,
mag_item_id: ClientItemId, mag_item_id: ClientItemId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -551,7 +553,7 @@ pub(super) fn add_bought_item_to_inventory<'a, EG, TR>(
item_id: ClientItemId, item_id: ClientItemId,
amount: u32, amount: u32,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Send + 'a>>
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Send + 'a>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -613,7 +615,7 @@ where
pub(super) fn sell_inventory_item<EG, TR>( pub(super) fn sell_inventory_item<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -643,7 +645,7 @@ async fn iterate_inner<'a, EG, TR, I, O, T, F, FR>(
mut input: Vec<I>, mut input: Vec<I>,
func: F, func: F,
arg: T, arg: T,
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
) -> Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>
where where
'a: 'async_recursion, 'a: 'async_recursion,
EG: EntityGateway, EG: EntityGateway,
@ -653,7 +655,7 @@ where
T: Clone + Send + Sync, T: Clone + Send + Sync,
F: Fn(I) -> FR + Send + Sync + Clone + 'static, F: Fn(I) -> FR + Send + Sync + Clone + 'static,
FR: Fn((ItemStateProxy, TR), T) FR: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
{ {
let item = match input.pop() { let item = match input.pop() {
Some(item) => item, Some(item) => item,
@ -673,7 +675,7 @@ pub(super) fn iterate<EG, TR, I, O, T, F, FR>(
input: Vec<I>, input: Vec<I>,
func: F, func: F,
) -> impl Fn((ItemStateProxy, TR), T) ) -> impl Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -682,7 +684,7 @@ where
T: Send + Clone + 'static + std::fmt::Debug, T: Send + Clone + 'static + std::fmt::Debug,
F: Fn(I) -> FR + Send + Sync + Clone + 'static, F: Fn(I) -> FR + Send + Sync + Clone + 'static,
FR: Fn((ItemStateProxy, TR), T) FR: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
T: Clone + Send + Sync, T: Clone + Send + Sync,
{ {
move |(item_state, transaction), arg| { move |(item_state, transaction), arg| {
@ -701,7 +703,7 @@ async fn foreach_inner<'a, EG, TR, O, T, F, I>(
state: (ItemStateProxy, TR), state: (ItemStateProxy, TR),
mut input: I, mut input: I,
func: Arc<F>, func: Arc<F>,
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
) -> Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>
where where
'a: 'async_recursion, 'a: 'async_recursion,
EG: EntityGateway, EG: EntityGateway,
@ -709,7 +711,7 @@ where
O: Send, O: Send,
T: Send, T: Send,
F: Fn((ItemStateProxy, TR), T) F: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
I: Iterator<Item = T> + Send + Sync + 'static, I: Iterator<Item = T> + Send + Sync + 'static,
{ {
let item = match input.next() { let item = match input.next() {
@ -728,14 +730,14 @@ where
pub(super) fn foreach<EG, TR, O, T, F, I>( pub(super) fn foreach<EG, TR, O, T, F, I>(
func: F func: F
) -> impl Fn((ItemStateProxy, TR), I) ) -> impl Fn((ItemStateProxy, TR), I)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
O: Send, O: Send,
T: Send + Clone + 'static + std::fmt::Debug, T: Send + Clone + 'static + std::fmt::Debug,
F: Fn((ItemStateProxy, TR), T) F: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync + 'static,
-> BoxFuture<Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync + 'static,
T: Send + Sync, T: Send + Sync,
I: IntoIterator<Item = T> + Send + Sync + 'static, I: IntoIterator<Item = T> + Send + Sync + 'static,
I::IntoIter: Send + Sync, I::IntoIter: Send + Sync,
@ -754,7 +756,7 @@ where
pub(super) fn insert<'a, EG, TR, T>( pub(super) fn insert<'a, EG, TR, T>(
element: T element: T
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), T), ItemStateError>> + Send + 'a>>
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), T), anyhow::Error>> + Send + 'a>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -772,12 +774,12 @@ pub(super) fn fork<EG, TR, F1, F2, T, O1, O2>(
func1: F1, func1: F1,
func2: F2, func2: F2,
) -> impl Fn((ItemStateProxy, TR), T) ) -> impl Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), (O1, O2)), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), (O1, O2)), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
F1: Fn((ItemStateProxy, TR), T) -> BoxFuture<Result<((ItemStateProxy, TR), O1), ItemStateError>> + Send + Sync + 'static,
F2: Fn((ItemStateProxy, TR), T) -> BoxFuture<Result<((ItemStateProxy, TR), O2), ItemStateError>> + Send + Sync + 'static,
F1: Fn((ItemStateProxy, TR), T) -> BoxFuture<Result<((ItemStateProxy, TR), O1), anyhow::Error>> + Send + Sync + 'static,
F2: Fn((ItemStateProxy, TR), T) -> BoxFuture<Result<((ItemStateProxy, TR), O2), anyhow::Error>> + Send + Sync + 'static,
T: Send + Sync + Clone + 'static, T: Send + Sync + Clone + 'static,
O1: Send, O1: Send,
O2: Send, O2: Send,
@ -799,7 +801,7 @@ where
pub(super) fn add_item_to_inventory<EG, TR>( pub(super) fn add_item_to_inventory<EG, TR>(
character: CharacterEntity, character: CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -829,7 +831,7 @@ pub(super) fn record_trade<EG, TR>(
character_to: CharacterEntityId, character_to: CharacterEntityId,
character_from: CharacterEntityId, character_from: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), Vec<InventoryItem>) ) -> impl Fn((ItemStateProxy, TR), Vec<InventoryItem>)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<InventoryItem>), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<InventoryItem>), anyhow::Error>> + Clone
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -855,7 +857,7 @@ where
pub(super) fn assign_new_item_id<EG, TR>( pub(super) fn assign_new_item_id<EG, TR>(
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -873,7 +875,7 @@ pub(super) fn convert_item_drop_to_floor_item<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
item_drop: ItemDrop, item_drop: ItemDrop,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -974,7 +976,7 @@ where
pub(super) fn add_item_to_local_floor<EG, TR>( pub(super) fn add_item_to_local_floor<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem) ) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -993,7 +995,7 @@ where
pub(super) fn apply_modifier_to_inventory_item<EG, TR>( pub(super) fn apply_modifier_to_inventory_item<EG, TR>(
modifier: ItemModifier, modifier: ItemModifier,
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -1006,7 +1008,7 @@ where
weapon.apply_modifier(&modifier); weapon.apply_modifier(&modifier);
transaction.gateway().add_weapon_modifier(entity_id, modifier).await?; transaction.gateway().add_weapon_modifier(entity_id, modifier).await?;
}, },
_ => return Err(ItemStateError::InvalidModifier)
_ => return Err(ItemStateError::InvalidModifier.into())
} }
Ok(((item_state, transaction), inventory_item)) Ok(((item_state, transaction), inventory_item))
@ -1016,7 +1018,7 @@ where
pub(super) fn as_individual_item<EG, TR>( pub(super) fn as_individual_item<EG, TR>(
) -> impl Fn((ItemStateProxy, TR), InventoryItem) ) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), IndividualItemDetail), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), IndividualItemDetail), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -1025,7 +1027,7 @@ where
Box::pin(async move { Box::pin(async move {
let item = match inventory_item.item { let item = match inventory_item.item {
InventoryItemDetail::Individual(individual_item) => individual_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)) Ok(((item_state, transaction), item))
@ -1038,7 +1040,7 @@ pub(super) fn apply_item_action_packets<EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
area_client: AreaClient, area_client: AreaClient,
) -> impl Fn((ItemStateProxy, TR), ApplyItemAction) ) -> impl Fn((ItemStateProxy, TR), ApplyItemAction)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<SendShipPacket>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<SendShipPacket>), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -1095,7 +1097,7 @@ where
pub(super) fn apply_item_action_character<EG, TR>( pub(super) fn apply_item_action_character<EG, TR>(
character: &CharacterEntity character: &CharacterEntity
) -> impl Fn((ItemStateProxy, TR), Vec<ApplyItemAction>) ) -> impl Fn((ItemStateProxy, TR), Vec<ApplyItemAction>)
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static, TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,

76
src/ship/items/apply_item.rs

@ -1,14 +1,16 @@
use std::convert::TryInto; use std::convert::TryInto;
use futures::future::join_all; use futures::future::join_all;
use thiserror::Error; use thiserror::Error;
use anyhow::Context;
use rand::SeedableRng; use rand::SeedableRng;
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::gateway::{EntityGateway, GatewayError}; 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::mag::{MagCell, MagCellError};
use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::tech::TechniqueDisk;
use crate::entity::item::{ItemDetail, ItemEntityId}; 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}; use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
@ -18,14 +20,12 @@ pub enum ApplyItemError {
NoCharacter, NoCharacter,
#[error("item not equipped")] #[error("item not equipped")]
ItemNotEquipped, ItemNotEquipped,
#[error("invalid item")]
#[error("could not use item invalid item")]
InvalidItem, InvalidItem,
#[error("invalid tool")]
InvalidTool,
#[error("gateway error {0}")] #[error("gateway error {0}")]
GatewayError(#[from] GatewayError), GatewayError(#[from] GatewayError),
#[error("itemstate error {0}")]
ItemStateError(Box<ItemStateError>),
#[error("magcell error {0}")] #[error("magcell error {0}")]
MagCellError(#[from] MagCellError), MagCellError(#[from] MagCellError),
} }
@ -38,49 +38,43 @@ pub enum ApplyItemAction {
//RemoveItem, //RemoveItem,
} }
impl From<ItemStateError> for ApplyItemError {
fn from(other: ItemStateError) -> ApplyItemError {
ApplyItemError::ItemStateError(Box::new(other))
}
}
async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.power += 1; character.materials.power += 1;
entity_gateway.save_character(character).await?; entity_gateway.save_character(character).await?;
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
} }
async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.mind += 1; character.materials.mind += 1;
entity_gateway.save_character(character).await.unwrap(); entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
} }
async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.evade += 1; character.materials.evade += 1;
entity_gateway.save_character(character).await.unwrap(); entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
} }
async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.def += 1; character.materials.def += 1;
entity_gateway.save_character(character).await.unwrap(); entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
} }
async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.luck += 1; character.materials.luck += 1;
entity_gateway.save_character(character).await.unwrap(); entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
} }
async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.hp += 1; character.materials.hp += 1;
entity_gateway.save_character(character).await.unwrap(); entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
} }
async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.tp += 1; character.materials.tp += 1;
entity_gateway.save_character(character).await.unwrap(); entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))]) Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
@ -113,7 +107,7 @@ async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy,
character: &CharacterEntity, character: &CharacterEntity,
cell_entity_id: ItemEntityId, cell_entity_id: ItemEntityId,
mag_cell_type: MagCell) mag_cell_type: MagCell)
-> Result<Vec<ApplyItemAction>, ApplyItemError>
-> Result<Vec<ApplyItemAction>, anyhow::Error>
where where
EG: EntityGateway + ?Sized, EG: EntityGateway + ?Sized,
{ {
@ -229,7 +223,7 @@ pub async fn liberta_kit<EG: EntityGateway>(entity_gateway: &mut EG, used_cell:
*/ */
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, ApplyItemError>
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, anyhow::Error>
{ {
let mag_rate = WeightedIndex::new(&[13, 13, 13, 13, 12, 12, 12, 12]).unwrap(); 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()) { 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, character: &mut CharacterEntity,
entity_id: ItemEntityId, entity_id: ItemEntityId,
tool: ToolType) tool: ToolType)
-> Result<Vec<ApplyItemAction>, ApplyItemError>
-> Result<Vec<ApplyItemAction>, anyhow::Error>
where where
EG: EntityGateway + ?Sized, EG: EntityGateway + ?Sized,
{ {
@ -270,6 +264,13 @@ where
ToolType::Monofluid => Ok(Vec::new()), ToolType::Monofluid => Ok(Vec::new()),
ToolType::Difluid => Ok(Vec::new()), ToolType::Difluid => Ok(Vec::new()),
ToolType::Trifluid => 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::HuntersReport => Ok(Vec::new()),
ToolType::CellOfMag502 ToolType::CellOfMag502
| ToolType::CellOfMag213 | ToolType::CellOfMag213
@ -299,17 +300,36 @@ where
} }
ToolType::JackOLantern => jack_o_lantern(), ToolType::JackOLantern => jack_o_lantern(),
// TODO: rest of these // 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<Vec<ApplyItemAction>, 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, pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy,
entity_gateway: &mut EG, entity_gateway: &mut EG,
character: &mut CharacterEntity, character: &mut CharacterEntity,
item: InventoryItem item: InventoryItem
) -> Result<Vec<ApplyItemAction>, ApplyItemError>
) -> Result<Vec<ApplyItemAction>, anyhow::Error>
where where
EG: EntityGateway + ?Sized + Clone + 'static EG: EntityGateway + ?Sized + Clone + 'static
{ {
@ -317,7 +337,11 @@ where
InventoryItemDetail::Individual(individual_item) => { InventoryItemDetail::Individual(individual_item) => {
match individual_item.item { match individual_item.item {
ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await, 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) => { InventoryItemDetail::Stacked(stacked_item) => {

20
src/ship/items/bank.rs

@ -64,10 +64,10 @@ pub struct BankItem {
} }
impl BankItem { impl BankItem {
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where where
F: FnMut(T, ItemEntityId) -> Fut, F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{ {
match &self.item { match &self.item {
BankItemDetail::Individual(individual_item) => { BankItemDetail::Individual(individual_item) => {
@ -125,27 +125,27 @@ impl BankState {
self.item_id_counter = base_item_id + self.bank.0.len() as u32; 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 { if self.meseta.0 + amount > 999999 {
return Err(ItemStateError::FullOfMeseta)
return Err(ItemStateError::FullOfMeseta.into())
} }
self.meseta.0 += amount; self.meseta.0 += amount;
Ok(()) 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 { if amount > self.meseta.0 {
return Err(ItemStateError::InvalidMesetaRemoval(amount))
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
} }
self.meseta.0 -= amount; self.meseta.0 -= amount;
Ok(()) Ok(())
} }
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, BankError> {
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, anyhow::Error> {
match item.item { match item.item {
InventoryItemDetail::Individual(iitem) => { InventoryItemDetail::Individual(iitem) => {
if self.bank.0.len() >= 30 { if self.bank.0.len() >= 30 {
Err(BankError::BankFull)
Err(BankError::BankFull.into())
} }
else { else {
self.bank.0.push(BankItem { self.bank.0.push(BankItem {
@ -166,7 +166,7 @@ impl BankState {
match existing_stack { match existing_stack {
Some(existing_stack) => { Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(BankError::StackFull)
Err(BankError::StackFull.into())
} }
else { else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
@ -175,7 +175,7 @@ impl BankState {
}, },
None => { None => {
if self.bank.0.len() >= 30 { if self.bank.0.len() >= 30 {
Err(BankError::BankFull)
Err(BankError::BankFull.into())
} }
else { else {
self.bank.0.push(BankItem { self.bank.0.push(BankItem {

6
src/ship/items/floor.rs

@ -33,7 +33,7 @@ pub struct FloorItem {
} }
impl FloorItem { impl FloorItem {
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where where
F: FnMut(T, ItemEntityId) -> Fut, F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>, Fut: Future<Output=Result<T, ItemStateError>>,
@ -53,10 +53,10 @@ impl FloorItem {
Ok(param) Ok(param)
} }
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where where
F: FnMut(T, ItemEntityId, Mag) -> Fut, F: FnMut(T, ItemEntityId, Mag) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{ {
if let FloorItemDetail::Individual(individual_item) = &self.item { if let FloorItemDetail::Individual(individual_item) = &self.item {
if let ItemDetail::Mag(mag) = &individual_item.item { if let ItemDetail::Mag(mag) = &individual_item.item {

42
src/ship/items/inventory.rs

@ -60,7 +60,7 @@ impl InventoryItemDetail {
} }
// TODO: this should probably go somewhere a bit more fundamental like ItemDetail // TODO: this should probably go somewhere a bit more fundamental like ItemDetail
pub fn sell_price(&self) -> Result<u32, ItemStateError> {
pub fn sell_price(&self) -> Result<u32, anyhow::Error> {
match self { match self {
InventoryItemDetail::Individual(individual_item) => { InventoryItemDetail::Individual(individual_item) => {
match &individual_item.item { match &individual_item.item {
@ -102,7 +102,7 @@ impl InventoryItemDetail {
Ok((ToolShopItem::from(d).price() / 8) as u32) Ok((ToolShopItem::from(d).price() / 8) as u32)
}, },
ItemDetail::Mag(_m) => { ItemDetail::Mag(_m) => {
Err(ItemStateError::ItemNotSellable)
Err(ItemStateError::ItemNotSellable.into())
}, },
ItemDetail::ESWeapon(_e) => { ItemDetail::ESWeapon(_e) => {
Ok(10u32) Ok(10u32)
@ -126,10 +126,10 @@ pub struct InventoryItem {
} }
impl InventoryItem { impl InventoryItem {
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where where
F: FnMut(T, ItemEntityId) -> Fut, F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{ {
match &self.item { match &self.item {
InventoryItemDetail::Individual(individual_item) => { InventoryItemDetail::Individual(individual_item) => {
@ -145,10 +145,10 @@ impl InventoryItem {
Ok(param) Ok(param)
} }
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where where
F: FnMut(T, ItemEntityId, Mag) -> Fut, F: FnMut(T, ItemEntityId, Mag) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{ {
if let InventoryItemDetail::Individual(individual_item) = &self.item { if let InventoryItemDetail::Individual(individual_item) = &self.item {
if let ItemDetail::Mag(mag) = &individual_item.item { if let ItemDetail::Mag(mag) = &individual_item.item {
@ -205,11 +205,11 @@ impl InventoryState {
self.inventory.0.len() self.inventory.0.len()
} }
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, InventoryError> {
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, anyhow::Error> {
match item.item { match item.item {
FloorItemDetail::Individual(iitem) => { FloorItemDetail::Individual(iitem) => {
if self.inventory.0.len() >= 30 { if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
} }
else { else {
self.inventory.0.push(InventoryItem { self.inventory.0.push(InventoryItem {
@ -229,7 +229,7 @@ impl InventoryState {
match existing_stack { match existing_stack {
Some(existing_stack) => { Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(InventoryError::StackFull)
Err(InventoryError::StackFull.into())
} }
else { else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
@ -238,7 +238,7 @@ impl InventoryState {
}, },
None => { None => {
if self.inventory.0.len() >= 30 { if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
} }
else { else {
self.inventory.0.push(InventoryItem { self.inventory.0.push(InventoryItem {
@ -253,7 +253,7 @@ impl InventoryState {
}, },
FloorItemDetail::Meseta(meseta) => { FloorItemDetail::Meseta(meseta) => {
if self.meseta == Meseta(999999) { if self.meseta == Meseta(999999) {
Err(InventoryError::MesetaFull)
Err(InventoryError::MesetaFull.into())
} }
else { else {
self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999); 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 { match &item.item {
InventoryItemDetail::Individual(_) => { InventoryItemDetail::Individual(_) => {
if self.inventory.0.len() >= 30 { if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
} }
else { else {
self.inventory.0.push(item); self.inventory.0.push(item);
@ -290,7 +290,7 @@ impl InventoryState {
match existing_stack { match existing_stack {
Some(existing_stack) => { Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(InventoryError::StackFull)
Err(InventoryError::StackFull.into())
} }
else { else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
@ -307,7 +307,7 @@ impl InventoryState {
}, },
None => { None => {
if self.inventory.0.len() >= 30 { if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
} }
else { else {
self.inventory.0.push(item); self.inventory.0.push(item);
@ -370,25 +370,25 @@ impl InventoryState {
.find(|i| i.item_id == *item_id) .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 { 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); self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
Ok(()) 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 { if self.meseta.0 + amount > 999999 {
return Err(ItemStateError::FullOfMeseta)
return Err(ItemStateError::FullOfMeseta.into())
} }
self.meseta.0 += amount; self.meseta.0 += amount;
Ok(()) 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 { if amount > self.meseta.0 {
return Err(ItemStateError::InvalidMesetaRemoval(amount))
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
} }
self.meseta.0 -= amount; self.meseta.0 -= amount;
Ok(()) Ok(())

32
src/ship/items/state.rs

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use async_std::sync::{Arc, RwLock, Mutex}; use async_std::sync::{Arc, RwLock, Mutex};
use futures::future::join_all; use futures::future::join_all;
use anyhow::Context;
use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::character::{CharacterEntity, CharacterEntityId};
@ -21,6 +22,8 @@ pub enum ItemStateError {
NoCharacter(CharacterEntityId), NoCharacter(CharacterEntityId),
#[error("room {0} not found")] #[error("room {0} not found")]
NoRoom(RoomId), NoRoom(RoomId),
#[error("inventory item {0} not found")]
NoInventoryItem(ClientItemId),
#[error("floor item {0} not found")] #[error("floor item {0} not found")]
NoFloorItem(ClientItemId), NoFloorItem(ClientItemId),
#[error("expected {0} to be a tool")] #[error("expected {0} to be a tool")]
@ -55,7 +58,7 @@ pub enum ItemStateError {
ItemNotSellable, ItemNotSellable,
#[error("could not modify item")] #[error("could not modify item")]
InvalidModifier, InvalidModifier,
#[error("wrong item type ")]
#[error("wrong item type {0}")]
WrongItemType(ClientItemId), WrongItemType(ClientItemId),
} }
@ -150,7 +153,7 @@ impl Default for ItemState {
} }
impl ItemState { impl ItemState {
pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result<InventoryState, ItemStateError> {
pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result<InventoryState, anyhow::Error> {
Ok(self.character_inventory Ok(self.character_inventory
.read() .read()
.await .await
@ -161,7 +164,7 @@ impl ItemState {
.clone()) .clone())
} }
pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result<BankState, ItemStateError> {
pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result<BankState, anyhow::Error> {
Ok(self.character_bank Ok(self.character_bank
.read() .read()
.await .await
@ -174,20 +177,20 @@ impl ItemState {
} }
impl ItemState { impl ItemState {
async fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
async fn new_item_id(&mut self) -> Result<ClientItemId, anyhow::Error> {
*self.room_item_id_counter *self.room_item_id_counter
.write() .write()
.await += 1; .await += 1;
Ok(ClientItemId(*self.room_item_id_counter.read().await)) Ok(ClientItemId(*self.room_item_id_counter.read().await))
} }
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> {
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> {
let inventory = entity_gateway.get_character_inventory(&character.id).await?; let inventory = entity_gateway.get_character_inventory(&character.id).await?;
let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?; let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?;
let equipped = entity_gateway.get_character_equips(&character.id).await?; let equipped = entity_gateway.get_character_equips(&character.id).await?;
let inventory_items = inventory.items.into_iter() let inventory_items = inventory.items.into_iter()
.map(|item| -> Result<InventoryItem, ItemStateError> {
.map(|item| -> Result<InventoryItem, anyhow::Error> {
Ok(match item { Ok(match item {
InventoryItemEntity::Individual(item) => { InventoryItemEntity::Individual(item) => {
InventoryItem { InventoryItem {
@ -214,7 +217,7 @@ impl ItemState {
}, },
}) })
}) })
.collect::<Result<Vec<_>, ItemStateError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; let character_meseta = entity_gateway.get_character_meseta(&character.id).await?;
let inventory_state = InventoryState { let inventory_state = InventoryState {
@ -259,7 +262,7 @@ impl ItemState {
.collect::<Vec<_>>()) .collect::<Vec<_>>())
.await .await
.into_iter() .into_iter()
.collect::<Result<Vec<_>, ItemStateError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; 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); 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 let local_floors = self.character_floor
.read() .read()
.await; .await;
@ -369,6 +372,7 @@ impl ItemState {
.map(|item| (item.clone(), FloorType::Shared)) .map(|item| (item.clone(), FloorType::Shared))
}) })
.ok_or_else(|| ItemStateError::NoFloorItem(*item_id)) .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<K, V>(master: &Arc<RwLock<HashMap<K, RwLock<V>>>>, async fn get_or_clone<K, V>(master: &Arc<RwLock<HashMap<K, RwLock<V>>>>,
proxy: &Arc<Mutex<HashMap<K, V>>>, proxy: &Arc<Mutex<HashMap<K, V>>>,
key: K, key: K,
err: fn(K) -> ItemStateError) -> Result<V, ItemStateError>
err: fn(K) -> ItemStateError) -> Result<V, anyhow::Error>
where where
K: Eq + std::hash::Hash + Copy, K: Eq + std::hash::Hash + Copy,
V: Clone V: Clone
@ -451,7 +455,7 @@ impl ItemStateProxy {
} }
} }
pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, ItemStateError> {
pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, anyhow::Error> {
get_or_clone(&self.item_state.character_inventory, get_or_clone(&self.item_state.character_inventory,
&self.proxied_state.character_inventory, &self.proxied_state.character_inventory,
*character_id, *character_id,
@ -462,7 +466,7 @@ impl ItemStateProxy {
self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory); self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory);
} }
pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, ItemStateError> {
pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, anyhow::Error> {
get_or_clone(&self.item_state.character_bank, get_or_clone(&self.item_state.character_bank,
&self.proxied_state.character_bank, &self.proxied_state.character_bank,
*character_id, *character_id,
@ -473,7 +477,7 @@ impl ItemStateProxy {
self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank); self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank);
} }
pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result<FloorState, ItemStateError> {
pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result<FloorState, anyhow::Error> {
let room_id = *self.item_state.character_room.read().await.get(character_id).unwrap(); let room_id = *self.item_state.character_room.read().await.get(character_id).unwrap();
Ok(FloorState { Ok(FloorState {
character_id: *character_id, character_id: *character_id,
@ -488,7 +492,7 @@ impl ItemStateProxy {
self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared); self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared);
} }
pub async fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
pub async fn new_item_id(&mut self) -> Result<ClientItemId, anyhow::Error> {
self.item_state.new_item_id().await self.item_state.new_item_id().await
} }
} }

40
src/ship/items/tasks.rs

@ -5,7 +5,7 @@ use crate::ship::ship::SendShipPacket;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction}; 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::itemstateaction::{ItemStateAction, ItemAction};
use crate::ship::items::inventory::InventoryItem; use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::floor::FloorItem; use crate::ship::items::floor::FloorItem;
@ -22,7 +22,7 @@ pub async fn pick_up_item<EG>(
entity_gateway: &mut EG, entity_gateway: &mut EG,
character: &CharacterEntity, character: &CharacterEntity,
item_id: &ClientItemId) item_id: &ClientItemId)
-> Result<actions::TriggerCreateItem, ItemStateError>
-> Result<actions::TriggerCreateItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
EG::Transaction: Clone, EG::Transaction: Clone,
@ -46,7 +46,7 @@ pub async fn drop_item<EG>(
item_id: &ClientItemId, item_id: &ClientItemId,
map_area: MapArea, map_area: MapArea,
drop_position: (f32, f32, f32)) drop_position: (f32, f32, f32))
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -70,7 +70,7 @@ pub async fn drop_partial_item<'a, EG>(
map_area: MapArea, map_area: MapArea,
drop_position: (f32, f32), drop_position: (f32, f32),
amount: u32) amount: u32)
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -95,7 +95,7 @@ pub async fn drop_meseta<'a, EG>(
map_area: MapArea, map_area: MapArea,
drop_position: (f32, f32), drop_position: (f32, f32),
amount: u32) amount: u32)
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -117,7 +117,7 @@ pub async fn withdraw_meseta<'a, EG>(
entity_gateway: &mut EG, entity_gateway: &mut EG,
character: &CharacterEntity, character: &CharacterEntity,
amount: u32) amount: u32)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -139,7 +139,7 @@ pub async fn deposit_meseta<'a, EG>(
entity_gateway: &mut EG, entity_gateway: &mut EG,
character: &CharacterEntity, character: &CharacterEntity,
amount: u32) amount: u32)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -162,7 +162,7 @@ pub async fn withdraw_item<'a, EG>(
character: &CharacterEntity, character: &CharacterEntity,
item_id: &ClientItemId, item_id: &ClientItemId,
amount: u32) amount: u32)
-> Result<InventoryItem, ItemStateError>
-> Result<InventoryItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -187,7 +187,7 @@ pub async fn deposit_item<'a, EG> (
character: &CharacterEntity, character: &CharacterEntity,
item_id: &ClientItemId, item_id: &ClientItemId,
amount: u32) amount: u32)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -209,7 +209,7 @@ pub async fn equip_item<'a, EG> (
character: &CharacterEntity, character: &CharacterEntity,
item_id: &ClientItemId, item_id: &ClientItemId,
equip_slot: u8, equip_slot: u8,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -230,7 +230,7 @@ pub async fn unequip_item<'a, EG> (
entity_gateway: &mut EG, entity_gateway: &mut EG,
character: &CharacterEntity, character: &CharacterEntity,
item_id: &ClientItemId, item_id: &ClientItemId,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -251,7 +251,7 @@ pub async fn sort_inventory<'a, EG> (
entity_gateway: &mut EG, entity_gateway: &mut EG,
character: &CharacterEntity, character: &CharacterEntity,
item_ids: Vec<ClientItemId>, item_ids: Vec<ClientItemId>,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -274,7 +274,7 @@ pub async fn use_item<'a, EG> (
area_client: AreaClient, area_client: AreaClient,
item_id: &ClientItemId, item_id: &ClientItemId,
amount: u32, amount: u32,
) -> Result<Vec<SendShipPacket>, ItemStateError>
) -> Result<Vec<SendShipPacket>, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -303,7 +303,7 @@ pub async fn feed_mag<'a, EG> (
character: &CharacterEntity, character: &CharacterEntity,
mag_item_id: &ClientItemId, mag_item_id: &ClientItemId,
tool_item_id: &ClientItemId, tool_item_id: &ClientItemId,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -327,7 +327,7 @@ pub async fn buy_shop_item<'a, EG> (
shop_item: &'a (dyn ShopItem + Send + Sync), shop_item: &'a (dyn ShopItem + Send + Sync),
item_id: ClientItemId, item_id: ClientItemId,
amount: u32, amount: u32,
) -> Result<InventoryItem, ItemStateError>
) -> Result<InventoryItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -353,7 +353,7 @@ pub async fn sell_item<'a, EG> (
character: &CharacterEntity, character: &CharacterEntity,
item_id: ClientItemId, item_id: ClientItemId,
amount: u32, amount: u32,
) -> Result<InventoryItem, ItemStateError>
) -> Result<InventoryItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -373,7 +373,7 @@ pub async fn trade_items<'a, EG> (
entity_gateway: &mut EG, entity_gateway: &mut EG,
p1: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta), p1: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta),
p2: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta)) p2: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta))
-> Result<(Vec<InventoryItem>, Vec<InventoryItem>), ItemStateError>
-> Result<(Vec<InventoryItem>, Vec<InventoryItem>), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -443,7 +443,7 @@ pub async fn take_meseta<'a, EG> (
entity_gateway: &mut EG, entity_gateway: &mut EG,
character_id: &CharacterEntityId, character_id: &CharacterEntityId,
meseta: Meseta) meseta: Meseta)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -464,7 +464,7 @@ pub async fn enemy_drops_item<'a, EG> (
entity_gateway: &mut EG, entity_gateway: &mut EG,
character_id: CharacterEntityId, character_id: CharacterEntityId,
item_drop: ItemDrop) item_drop: ItemDrop)
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -488,7 +488,7 @@ pub async fn apply_modifier<'a, EG> (
character: &CharacterEntity, character: &CharacterEntity,
item_id: ClientItemId, item_id: ClientItemId,
modifier: ItemModifier) modifier: ItemModifier)
-> Result<IndividualItemDetail, ItemStateError>
-> Result<IndividualItemDetail, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {

26
src/ship/location.rs

@ -30,41 +30,50 @@ impl LobbyId {
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("create room")]
pub enum CreateRoomError { pub enum CreateRoomError {
#[error("no open slots")]
NoOpenSlots, NoOpenSlots,
#[error("client already in area")]
ClientInAreaAlready, ClientInAreaAlready,
#[error("join error")]
JoinError, JoinError,
} }
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("join room")]
pub enum JoinRoomError { pub enum JoinRoomError {
#[error("room does not exist")]
RoomDoesNotExist, RoomDoesNotExist,
#[error("room is full")]
RoomFull, RoomFull,
#[error("client already in area")]
ClientInAreaAlready, ClientInAreaAlready,
} }
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("join lobby")]
pub enum JoinLobbyError { pub enum JoinLobbyError {
#[error("lobby does not exist")]
LobbyDoesNotExist, LobbyDoesNotExist,
#[error("lobby is full")]
LobbyFull, LobbyFull,
#[error("client already in area")]
ClientInAreaAlready, ClientInAreaAlready,
} }
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("get area")]
pub enum GetAreaError { pub enum GetAreaError {
#[error("not in a room")]
NotInRoom, NotInRoom,
#[error("not in a lobby")]
NotInLobby, NotInLobby,
#[error("get area: invalid client")]
InvalidClient, InvalidClient,
} }
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("client removal")]
pub enum ClientRemovalError { pub enum ClientRemovalError {
#[error("client removal: client not in area")]
ClientNotInArea, ClientNotInArea,
#[error("client removal: invalid area")]
InvalidArea, InvalidArea,
} }
@ -77,17 +86,20 @@ pub enum GetClientsError {
} }
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("get neighbor")]
pub enum GetNeighborError { pub enum GetNeighborError {
#[error("get neighbor: invalid client")]
InvalidClient, InvalidClient,
#[error("get neighbor: invalid area")]
InvalidArea, InvalidArea,
} }
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
#[error("get leader")]
pub enum GetLeaderError { pub enum GetLeaderError {
#[error("get leader: invalid client")]
InvalidClient, InvalidClient,
#[error("get leader: invalid area")]
InvalidArea, InvalidArea,
#[error("get leader: client not in area")]
NoClientInArea, NoClientInArea,
} }

11
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::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *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
}
}
} }

102
src/ship/map/maps.rs

@ -8,12 +8,13 @@ use thiserror::Error;
use crate::ship::ship::ShipEvent; use crate::ship::ship::ShipEvent;
use crate::ship::monster::MonsterType; use crate::ship::monster::MonsterType;
use crate::ship::room::{Episode, RoomMode};
use crate::ship::room::{Episode, RoomMode, PlayerMode};
// TODO: don't use * // TODO: don't use *
use crate::ship::map::*; use crate::ship::map::*;
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> { pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
let mut object_data = Vec::new(); let mut object_data = Vec::new();
@ -172,24 +173,9 @@ 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<MapVariant>,
enemy_data: Vec<Option<MapEnemy>>,
object_data: Vec<Option<MapObject>>,
}
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) => {
pub fn default_map_variants(episode: Episode, player_mode: PlayerMode) -> Vec<MapVariant> {
match (episode, player_mode) {
(Episode::One, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online), vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
MapVariant::new(MapArea::Forest1, MapVariantMode::Online), MapVariant::new(MapArea::Forest1, MapVariantMode::Online),
MapVariant::new(MapArea::Forest2, MapVariantMode::Online), MapVariant::new(MapArea::Forest2, MapVariantMode::Online),
@ -207,7 +193,7 @@ impl Maps {
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online), MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online),
] ]
}, },
(Episode::One, 1) => {
(Episode::One, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline), vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
MapVariant::new(MapArea::Forest1, MapVariantMode::Offline), MapVariant::new(MapArea::Forest1, MapVariantMode::Offline),
MapVariant::new(MapArea::Forest2, MapVariantMode::Offline), MapVariant::new(MapArea::Forest2, MapVariantMode::Offline),
@ -225,7 +211,7 @@ impl Maps {
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline), MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline),
] ]
}, },
(Episode::Two, 0) => {
(Episode::Two, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online), vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online), MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online),
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online), MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online),
@ -244,7 +230,7 @@ impl Maps {
MapVariant::new(MapArea::GolDragon, MapVariantMode::Online), MapVariant::new(MapArea::GolDragon, MapVariantMode::Online),
] ]
}, },
(Episode::Two, 1) => {
(Episode::Two, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline), vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline), MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline),
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline), MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline),
@ -263,7 +249,7 @@ impl Maps {
MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline), MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline),
] ]
}, },
(Episode::Four, _) => {
(Episode::Four, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online), vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
MapVariant::new(MapArea::CraterEast, MapVariantMode::Online), MapVariant::new(MapArea::CraterEast, MapVariantMode::Online),
MapVariant::new(MapArea::CraterWest, MapVariantMode::Online), MapVariant::new(MapArea::CraterWest, MapVariantMode::Online),
@ -276,21 +262,42 @@ impl Maps {
MapVariant::new(MapArea::SaintMillion, 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<MapVariant>,
enemy_data: Vec<Option<MapEnemy>>,
object_data: Vec<Option<MapObject>>,
}
impl Maps {
pub fn new(map_variants: Vec<MapVariant>, enemy_data: Vec<Option<MapEnemy>>, object_data: Vec<Option<MapObject>>) -> Maps {
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, map_variants,
enemy_data,
object_data,
} }
} }
@ -322,7 +329,7 @@ impl Maps {
{ {
self.enemy_data = enemies self.enemy_data = enemies
.into_iter() .into_iter()
.map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
.map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event)))
.collect(); .collect();
self.object_data = objects; self.object_data = objects;
} }
@ -351,13 +358,20 @@ impl Maps {
} }
} }
fn apply_rare_enemy(enemy: Option<MapEnemy>, rare_enemy_table: &RareMonsterAppearTable, event: ShipEvent) -> Option<MapEnemy> {
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,
}
} }

6
src/ship/map/mod.rs

@ -1,8 +1,8 @@
pub mod area; pub mod area;
pub mod enemy; 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 // TODO: don't just forward everything to the module scope
pub use area::*; pub use area::*;

11
src/ship/packet/builder/lobby.rs

@ -1,6 +1,6 @@
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::common::serverstate::ClientId; 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::location::{ClientLocation, LobbyId, ClientLocationError};
use crate::ship::packet::builder::{player_info}; use crate::ship::packet::builder::{player_info};
use crate::ship::items::state::ItemState; use crate::ship::items::state::ItemState;
@ -13,7 +13,7 @@ pub async fn join_lobby(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &ItemState, item_state: &ItemState,
event: ShipEvent) event: ShipEvent)
-> Result<JoinLobby, ShipError> {
-> Result<JoinLobby, anyhow::Error> {
let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?; let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
let playerinfo = join_all( let playerinfo = join_all(
@ -28,9 +28,8 @@ pub async fn join_lobby(id: ClientId,
}})) }}))
.await .await
.into_iter() .into_iter()
.collect::<Result<Vec<_>, ShipError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
//let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap();
let client_block = clients.with(id, |client| Box::pin(async move { let client_block = clients.with(id, |client| Box::pin(async move {
client.block as u16 client.block as u16
})).await?; })).await?;
@ -54,7 +53,7 @@ pub async fn add_to_lobby(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &ItemState, item_state: &ItemState,
event: ShipEvent) event: ShipEvent)
-> Result<AddToLobby, ShipError> {
-> Result<AddToLobby, anyhow::Error> {
let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?; 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() })?; let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
clients.with(id, |client| { clients.with(id, |client| {
@ -77,7 +76,7 @@ pub async fn add_to_lobby(id: ClientId,
pub async fn remove_from_lobby(id: ClientId, pub async fn remove_from_lobby(id: ClientId,
client_location: &ClientLocation) client_location: &ClientLocation)
-> Result<LeaveLobby, ShipError> {
-> Result<LeaveLobby, anyhow::Error> {
let prev_area_index = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?.local_client.id(); 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 let prev_area_leader_index = client_location
.get_area_leader(client_location .get_area_leader(client_location

13
src/ship/packet/builder/room.rs

@ -15,12 +15,11 @@ pub async fn join_room(id: ClientId,
room_id: RoomId, room_id: RoomId,
room: &RoomState, room: &RoomState,
event: ShipEvent) event: ShipEvent)
-> Result<JoinRoom, ShipError> {
-> Result<JoinRoom, anyhow::Error> {
let all_clients = client_location.get_clients_in_room(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?; 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()) let players = futures::stream::iter(all_clients.iter())
.enumerate() .enumerate()
.fold::<Result<_, ShipError>, _, _>(Ok([PlayerHeader::default(); 4]), |acc, (i, c)| async move {
//let header_client = clients.get(&c.client).ok_or(ShipError::ClientNotFound(id))?;
.fold::<Result<_, anyhow::Error>, _, _>(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()))?; 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 { clients.with(c.client, |client| Box::pin(async move {
acc.map(|mut a| { acc.map(|mut a| {
@ -40,14 +39,14 @@ pub async fn join_room(id: ClientId,
leader: leader.local_client.id(), leader: leader.local_client.id(),
one: 1, one: 1,
difficulty: room.mode.difficulty().into(), difficulty: room.mode.difficulty().into(),
battle: room.mode.battle(),
battle: room.mode.battle() as u8,
event: event.into(), event: event.into(),
section: room.section_id.into(), section: room.section_id.into(),
challenge: room.mode.challenge(),
challenge: room.mode.challenge() as u8,
random_seed: room.random_seed, random_seed: room.random_seed,
episode: room.mode.episode().into(), episode: room.mode.episode().into(),
one2: 1, one2: 1,
single_player: room.mode.single_player(),
single_player: room.mode.player_mode().value(),
unknown: 0, unknown: 0,
}) })
} }
@ -59,7 +58,7 @@ pub async fn add_to_room(_id: ClientId,
leader: &AreaClient, leader: &AreaClient,
item_state: &ItemState, item_state: &ItemState,
event: ShipEvent) event: ShipEvent)
-> Result<AddToRoom, ShipError> {
-> Result<AddToRoom, anyhow::Error> {
let inventory = item_state.get_character_inventory(&client.character).await?; let inventory = item_state.get_character_inventory(&client.character).await?;
Ok(AddToRoom { Ok(AddToRoom {
flag: 1, flag: 1,

2
src/ship/packet/handler/auth.rs

@ -16,7 +16,7 @@ pub async fn validate_login<EG>(id: ClientId,
shipgate_sender: &Option<async_std::channel::Sender<ShipMessage>>, shipgate_sender: &Option<async_std::channel::Sender<ShipMessage>>,
ship_name: &str, ship_name: &str,
num_blocks: usize) num_blocks: usize)
-> Result<Vec<SendShipPacket>, ShipError>
-> Result<Vec<SendShipPacket>, anyhow::Error>
where where
EG: EntityGateway, EG: EntityGateway,
{ {

10
src/ship/packet/handler/communication.rs

@ -1,6 +1,6 @@
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::common::serverstate::ClientId; 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::ship::location::{ClientLocation};
use crate::entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
@ -10,7 +10,7 @@ pub async fn player_chat(id: ClientId,
msg: PlayerChat, msg: PlayerChat,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients) clients: &Clients)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let cmsg = clients.with(id, |client| Box::pin(async move { let cmsg = clients.with(id, |client| Box::pin(async move {
PlayerChat::new(client.user.id.0, msg.message) PlayerChat::new(client.user.id.0, msg.message)
})).await?; })).await?;
@ -25,7 +25,7 @@ pub async fn player_chat(id: ClientId,
pub async fn request_infoboard(id: ClientId, pub async fn request_infoboard(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients) clients: &Clients)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let area_clients = client_location.get_client_neighbors(id).await.unwrap(); let area_clients = client_location.get_client_neighbors(id).await.unwrap();
let infoboards = join_all( let infoboards = join_all(
area_clients.iter() area_clients.iter()
@ -39,7 +39,7 @@ pub async fn request_infoboard(id: ClientId,
})) }))
.await .await
.into_iter() .into_iter()
.collect::<Result<Vec<_>, ShipError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
Ok(vec![(id, SendShipPacket::ViewInfoboardResponse(ViewInfoboardResponse {response: infoboards}))]) Ok(vec![(id, SendShipPacket::ViewInfoboardResponse(ViewInfoboardResponse {response: infoboards}))])
} }
@ -47,7 +47,7 @@ pub async fn write_infoboard<EG>(id: ClientId,
new_infoboard: WriteInfoboard, new_infoboard: WriteInfoboard,
clients: &Clients, clients: &Clients,
entity_gateway: &mut EG) entity_gateway: &mut EG)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {

45
src/ship/packet/handler/direct_message.rs

@ -39,13 +39,15 @@ async fn send_to_client(id: ClientId,
target: u8, target: u8,
msg: DirectMessage, msg: DirectMessage,
client_location: &ClientLocation) client_location: &ClientLocation)
-> Vec<(ClientId, SendShipPacket)> {
client_location.get_all_clients_by_client(id).await.unwrap().into_iter()
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
Ok(client_location.get_all_clients_by_client(id)
.await?
.into_iter()
.filter(move |client| client.local_client.id() == target) .filter(move |client| client.local_client.id() == target)
.map(move |client| { .map(move |client| {
(client.client, SendShipPacket::DirectMessage(msg.clone())) (client.client, SendShipPacket::DirectMessage(msg.clone()))
}) })
.collect()
.collect())
} }
pub async fn guildcard_send(id: ClientId, pub async fn guildcard_send(id: ClientId,
@ -53,7 +55,7 @@ pub async fn guildcard_send(id: ClientId,
target: u32, target: u32,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients) clients: &Clients)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let msg = clients.with(id, |client| Box::pin(async move { let msg = clients.with(id, |client| Box::pin(async move {
DirectMessage{ DirectMessage{
flag: target, flag: target,
@ -72,7 +74,7 @@ pub async fn guildcard_send(id: ClientId,
} }
})).await?; })).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<EG>(id: ClientId, pub async fn request_item<EG>(id: ClientId,
@ -82,7 +84,7 @@ pub async fn request_item<EG>(id: ClientId,
clients: &Clients, clients: &Clients,
rooms: &Rooms, rooms: &Rooms,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -92,7 +94,7 @@ where
})).await??; })).await??;
if monster.dropped_item { 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?; let clients_in_area = client_location.get_clients_in_room(room_id).await?;
@ -115,7 +117,7 @@ where
z: request_item.z, z: request_item.z,
item: item_drop, 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 client.character.id
})).await?; })).await?;
@ -134,7 +136,7 @@ pub async fn pickup_item<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -193,7 +195,7 @@ pub async fn request_box_item<EG>(id: ClientId,
clients: &Clients, clients: &Clients,
rooms: &Rooms, rooms: &Rooms,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static EG: EntityGateway + Clone + 'static
{ {
@ -203,11 +205,10 @@ where
})).await??; })).await??;
if box_object.dropped_item { 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 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 { let client_and_drop = rooms.with_mut(room_id, |room| Box::pin(async move {
clients_in_area.into_iter() clients_in_area.into_iter()
.filter_map(move |area_client| { .filter_map(move |area_client| {
@ -244,7 +245,7 @@ where
pub async fn send_bank_list(id: ClientId, pub async fn send_bank_list(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{ {
let bank = clients.with(id, |client| { let bank = clients.with(id, |client| {
let item_state = item_state.clone(); let item_state = item_state.clone();
@ -262,7 +263,7 @@ pub async fn bank_interaction<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -273,7 +274,7 @@ where
let mut entity_gateway = entity_gateway.clone(); let mut entity_gateway = entity_gateway.clone();
let mut item_state = item_state.clone(); let mut item_state = item_state.clone();
Box::pin(async move { Box::pin(async move {
Ok::<_, ShipError>(match bank_interaction.action {
Ok::<_, anyhow::Error>(match bank_interaction.action {
BANK_ACTION_DEPOSIT => { BANK_ACTION_DEPOSIT => {
if bank_interaction.item_id == 0xFFFFFFFF { if bank_interaction.item_id == 0xFFFFFFFF {
deposit_meseta(&mut item_state, &mut entity_gateway, &client.character, bank_interaction.meseta_amount).await?; 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, clients: &Clients,
rooms: &Rooms, rooms: &Rooms,
shops: &ItemShops) shops: &ItemShops)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{ {
//let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; //let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
@ -397,7 +398,7 @@ pub async fn buy_item<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -422,7 +423,7 @@ where
(item, remove) (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??; })}).await??;
let other_clients_in_area = client_location.get_client_neighbors(id).await?; let other_clients_in_area = client_location.get_client_neighbors(id).await?;
@ -465,7 +466,7 @@ pub async fn request_tek_item<EG>(id: ClientId,
entity_gateway: &mut EG, entity_gateway: &mut EG,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -502,7 +503,7 @@ where
}); });
take_meseta(&mut item_state, &mut entity_gateway, &client.character.id, item::Meseta(100)).await?; 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??; })}).await??;
@ -515,7 +516,7 @@ pub async fn accept_tek_item<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {

16
src/ship/packet/handler/lobby.rs

@ -16,7 +16,7 @@ pub async fn block_selected(id: ClientId,
pkt: MenuSelect, pkt: MenuSelect,
clients: &Clients, clients: &Clients,
item_state: &ItemState) item_state: &ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
clients.with_mut(id, |client| { clients.with_mut(id, |client| {
let item_state = item_state.clone(); let item_state = item_state.clone();
Box::pin(async move { Box::pin(async move {
@ -34,8 +34,8 @@ pub async fn block_selected(id: ClientId,
.meseta(inventory.meseta) .meseta(inventory.meseta)
.inventory(&inventory) .inventory(&inventory)
.bank(&bank) .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) .symbol_chat(&client.settings.settings.symbol_chats)
.tech_menu(&client.character.tech_menu.as_bytes()) .tech_menu(&client.character.tech_menu.as_bytes())
.option_flags(client.character.option_flags) .option_flags(client.character.option_flags)
@ -57,7 +57,7 @@ pub async fn send_player_to_lobby(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &ItemState, item_state: &ItemState,
event: ShipEvent) event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).await.map_err(|_| ShipError::TooManyClients)?; 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 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?; 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<EG>(id: ClientId,
rooms: &Rooms, rooms: &Rooms,
entity_gateway: &mut EG, entity_gateway: &mut EG,
event: ShipEvent) event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -134,7 +134,7 @@ where
pub async fn remove_from_lobby(id: ClientId, pub async fn remove_from_lobby(id: ClientId,
client_location: &mut ClientLocation) client_location: &mut ClientLocation)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let area_client = client_location.get_local_client(id).await?; let area_client = client_location.get_local_client(id).await?;
let neighbors = client_location.get_client_neighbors(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() })?; 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, pkt: MenuDetail,
client_location: &mut ClientLocation, client_location: &mut ClientLocation,
clients: &Clients) clients: &Clients)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = RoomId(pkt.item as usize); 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 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() { let room_info = if clients_in_room.is_empty() {
@ -169,7 +169,7 @@ pub async fn get_room_tab_info(id: ClientId,
})).await })).await
})).await })).await
.into_iter() .into_iter()
.collect::<Result<Vec<_>, ShipError>>()?
.collect::<Result<Vec<_>, anyhow::Error>>()?
.join("\n") .join("\n")
}; };
Ok(vec![(id, SendShipPacket::SmallLeftDialog(SmallLeftDialog::new(room_info)))]) Ok(vec![(id, SendShipPacket::SmallLeftDialog(SmallLeftDialog::new(room_info)))])

36
src/ship/packet/handler/message.rs

@ -18,7 +18,7 @@ pub async fn request_exp<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -29,7 +29,7 @@ where
let enemy_exp = rooms.with(room_id, |room| Box::pin(async move { let enemy_exp = rooms.with(room_id, |room| Box::pin(async move {
let monster = room.maps.enemy_by_id(enemy_id)?; 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))?; 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??; })).await??;
let exp_gain = if request_exp.last_hitter == 1 { 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 (_, 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 (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() exp_pkts.extend(clients_in_area.into_iter()
.map(move |c| { .map(move |c| {
(c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone())))) (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone()))))
@ -83,7 +83,7 @@ pub async fn player_drop_item<EG>(id: ClientId,
clients: &Clients, clients: &Clients,
rooms: &Rooms, rooms: &Rooms,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -112,7 +112,7 @@ pub async fn drop_coordinates(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, 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.map_err(|err| -> ClientLocationError { err.into() })?;
let map_area = rooms.with(room_id, |room| Box::pin(async move { let map_area = rooms.with(room_id, |room| Box::pin(async move {
@ -137,7 +137,7 @@ pub async fn no_longer_has_item<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -150,7 +150,7 @@ where
})).await?; })).await?;
if let Some(drop_location) = drop_location { if let Some(drop_location) = drop_location {
if drop_location.item_id.0 != no_longer_has_item.item_id { 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 { if no_longer_has_item.item_id == 0xFFFFFFFF {
@ -218,7 +218,7 @@ where
.collect()) .collect())
} }
else { 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, clients: &Clients,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
if let Ok(room_id) = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() }) { if let Ok(room_id) = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() }) {
let msg = message.msg.clone(); let msg = message.msg.clone();
clients.with_mut(id, |client| { clients.with_mut(id, |client| {
@ -291,7 +291,7 @@ pub async fn update_player_position(id: ClientId,
} }
_ => {}, _ => {},
} }
Ok::<_, ShipError>(())
Ok::<_, anyhow::Error>(())
})}).await??; })}).await??;
} }
Ok(client_location.get_client_neighbors(id).await?.into_iter() Ok(client_location.get_client_neighbors(id).await?.into_iter()
@ -307,7 +307,7 @@ pub async fn charge_attack<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -333,7 +333,7 @@ pub async fn player_uses_item<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -364,7 +364,7 @@ pub async fn player_used_medical_center<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -390,7 +390,7 @@ pub async fn player_feed_mag<EG>(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -415,7 +415,7 @@ pub async fn player_equips_item<EG>(id: ClientId,
entity_gateway: &mut EG, entity_gateway: &mut EG,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -440,7 +440,7 @@ pub async fn player_unequips_item<EG>(id: ClientId,
entity_gateway: &mut EG, entity_gateway: &mut EG,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -458,7 +458,7 @@ pub async fn player_sorts_items<EG>(id: ClientId,
entity_gateway: &mut EG, entity_gateway: &mut EG,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -488,7 +488,7 @@ pub async fn player_sells_item<EG> (id: ClientId,
entity_gateway: &mut EG, entity_gateway: &mut EG,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState) item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {

41
src/ship/packet/handler/quest.rs

@ -4,7 +4,8 @@ use libpso::packet::ship::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent}; use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent};
use crate::ship::room::Rooms; 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 crate::ship::packet::builder::quest;
use libpso::util::array_to_utf8; use libpso::util::array_to_utf8;
@ -13,7 +14,7 @@ enum QuestFileType {
Dat 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 = array_to_utf8(*filename_bytes).map_err(|_| ShipError::InvalidQuestFilename("NOT UTF8".to_string()))?;
let (filename, suffix) = { let (filename, suffix) = {
let mut s = filename.splitn(2, '.'); 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 { let datatype = match suffix {
"bin" => QuestFileType::Bin, "bin" => QuestFileType::Bin,
"dat" => QuestFileType::Dat, "dat" => QuestFileType::Dat,
_ => return Err(ShipError::InvalidQuestFilename(filename.to_owned()))
_ => Err(ShipError::InvalidQuestFilename(filename.to_owned()))?
}; };
let (category, quest) = { let (category, quest) = {
@ -41,8 +42,8 @@ pub async fn send_quest_category_list(id: ClientId,
rql: RequestQuestList, rql: RequestQuestList,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?;
let rql = rql.clone(); let rql = rql.clone();
rooms.with_mut(room_id, |room| Box::pin(async move { 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]); 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, menuselect: MenuSelect,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?;
rooms.with(room_id, |room| Box::pin(async move { rooms.with(room_id, |room| Box::pin(async move {
let (_, category_quests) = room.quests[room.quest_group.value()].iter() let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(menuselect.item as usize) .nth(menuselect.item as usize)
@ -72,8 +73,8 @@ pub async fn quest_detail(id: ClientId,
questdetailrequest: QuestDetailRequest, questdetailrequest: QuestDetailRequest,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?;
rooms.with(room_id, |room| Box::pin(async move { rooms.with(room_id, |room| Box::pin(async move {
let (_, category_quests) = room.quests[room.quest_group.value()].iter() let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(questdetailrequest.category as usize) .nth(questdetailrequest.category as usize)
@ -96,8 +97,8 @@ pub async fn player_chose_quest(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms, rooms: &Rooms,
event: ShipEvent) event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?;
let client_location = client_location.clone(); let client_location = client_location.clone();
let questmenuselect = questmenuselect.clone(); let questmenuselect = questmenuselect.clone();
@ -115,14 +116,14 @@ pub async fn player_chose_quest(id: ClientId,
.ok_or_else(|| ShipError::InvalidQuest(questmenuselect.quest))? .ok_or_else(|| ShipError::InvalidQuest(questmenuselect.quest))?
.clone(); .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(); room.map_areas = quest.map_areas.clone();
let bin = quest::quest_header(&questmenuselect, &quest.bin_blob, "bin"); let bin = quest::quest_header(&questmenuselect, &quest.bin_blob, "bin");
let dat = quest::quest_header(&questmenuselect, &quest.dat_blob, "dat"); 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 { for client in &area_clients {
clients.with_mut(client.client, |client| Box::pin(async move { clients.with_mut(client.client, |client| Box::pin(async move {
client.done_loading_quest = false; client.done_loading_quest = false;
@ -141,9 +142,9 @@ pub async fn quest_file_request(id: ClientId,
quest_file_request: QuestFileRequest, quest_file_request: QuestFileRequest,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &mut Rooms) rooms: &mut Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, 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(); let quest_file_request = quest_file_request.clone();
rooms.with(room_id, |room| Box::pin(async move { 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, quest_chunk_ack: QuestChunkAck,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?;
let quest_chunk_ack = quest_chunk_ack.clone(); let quest_chunk_ack = quest_chunk_ack.clone();
rooms.with(room_id, |room| Box::pin(async move { 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, pub async fn done_loading_quest(id: ClientId,
clients: &Clients, clients: &Clients,
client_location: &ClientLocation) client_location: &ClientLocation)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
clients.with_mut(id, |client| Box::pin(async move { clients.with_mut(id, |client| Box::pin(async move {
client.done_loading_quest = true; client.done_loading_quest = true;
})).await?; })).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() let all_loaded = area_clients.iter()
.map(|client| .map(|client|

41
src/ship/packet/handler/room.rs

@ -1,36 +1,43 @@
use std::convert::{TryFrom, Into}; use std::convert::{TryFrom, Into};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use async_std::sync::Arc;
use libpso::packet::ship::*; use libpso::packet::ship::*;
use libpso::packet::messages::*; use libpso::packet::messages::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::common::leveltable::LEVEL_TABLE; 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::location::{ClientLocation, RoomId, RoomLobby, GetAreaError};
use crate::ship::packet::builder; use crate::ship::packet::builder;
use crate::ship::room;
use crate::ship::items::state::ItemState; use crate::ship::items::state::ItemState;
#[allow(clippy::too_many_arguments)]
pub async fn create_room(id: ClientId, pub async fn create_room(id: ClientId,
create_room: CreateRoom, create_room: CreateRoom,
client_location: &mut ClientLocation, client_location: &mut ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState, item_state: &mut ItemState,
rooms: &Rooms, rooms: &Rooms,
map_builder: Arc<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>,
event: ShipEvent) event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let level = clients.with(id, |client| Box::pin(async move { let level = clients.with(id, |client| Box::pin(async move {
LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp)
})).await?; })).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())))]) 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())))]) 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())))]) 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(); let mut item_state = item_state.clone();
Box::pin(async move { Box::pin(async move {
item_state.add_character_to_room(room_id, &client.character, area_client).await; 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; room.bursting = true;
Ok::<_, ShipError>(room)
Ok::<_, anyhow::Error>(room)
})}).await??; })}).await??;
let join_room = builder::room::join_room(id, clients, client_location, room_id, &room, event).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, pub async fn room_name_request(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let area = client_location.get_area(id).await?; let area = client_location.get_area(id).await?;
match area { match area {
RoomLobby::Room(room) => { RoomLobby::Room(room) => {
@ -90,7 +97,7 @@ pub async fn join_room(id: ClientId,
item_state: &mut ItemState, item_state: &mut ItemState,
rooms: &Rooms, rooms: &Rooms,
event: ShipEvent) event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = RoomId(pkt.item as usize); let room_id = RoomId(pkt.item as usize);
if !rooms.exists(room_id).await { if !rooms.exists(room_id).await {
return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("This room no longer exists!".into())))]) 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?; })).await?;
match difficulty { 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())))]) 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())))]) 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())))]) 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, pub async fn done_bursting(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
rooms: &Rooms) rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
let rare_monster_list = rooms.with_mut(room_id, |room| Box::pin(async move { let rare_monster_list = rooms.with_mut(room_id, |room| Box::pin(async move {
room.bursting = false; room.bursting = false;
@ -235,7 +242,7 @@ pub async fn request_room_list(id: ClientId,
pub async fn cool_62(id: ClientId, pub async fn cool_62(id: ClientId,
cool_62: Like62ButCooler, cool_62: Like62ButCooler,
client_location: &ClientLocation) client_location: &ClientLocation)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let target = cool_62.flag as u8; let target = cool_62.flag as u8;
let cool_62 = cool_62.clone(); let cool_62 = cool_62.clone();
Ok(client_location Ok(client_location

18
src/ship/packet/handler/settings.rs

@ -1,13 +1,13 @@
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients};
use crate::ship::ship::{SendShipPacket, Clients};
use crate::entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
pub async fn update_config<EG>(id: ClientId, pub async fn update_config<EG>(id: ClientId,
update_config: UpdateConfig, update_config: UpdateConfig,
clients: &Clients, clients: &Clients,
entity_gateway: &mut EG) entity_gateway: &mut EG)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -24,7 +24,7 @@ pub async fn save_options<EG>(id: ClientId,
save_options: SaveOptions, save_options: SaveOptions,
clients: &Clients, clients: &Clients,
entity_gateway: &mut EG) entity_gateway: &mut EG)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -41,15 +41,15 @@ pub async fn keyboard_config<EG>(id: ClientId,
keyboard_config: KeyboardConfig, keyboard_config: KeyboardConfig,
clients: &Clients, clients: &Clients,
entity_gateway: &mut EG) entity_gateway: &mut EG)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
clients.with_mut(id, |client| { clients.with_mut(id, |client| {
let mut entity_gateway = entity_gateway.clone(); let mut entity_gateway = entity_gateway.clone();
Box::pin(async move { 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??; })}).await??;
Ok(Vec::new()) Ok(Vec::new())
} }
@ -58,15 +58,15 @@ pub async fn gamepad_config<EG>(id: ClientId,
gamepad_config: GamepadConfig, gamepad_config: GamepadConfig,
clients: &Clients, clients: &Clients,
entity_gateway: &mut EG) entity_gateway: &mut EG)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
clients.with_mut(id, |client| { clients.with_mut(id, |client| {
let mut entity_gateway = entity_gateway.clone(); let mut entity_gateway = entity_gateway.clone();
Box::pin(async move { 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??; })}).await??;
Ok(Vec::new()) Ok(Vec::new())
} }

26
src/ship/packet/handler/trade.rs

@ -3,7 +3,7 @@ use libpso::packet::ship::*;
use libpso::packet::messages::*; use libpso::packet::messages::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients}; 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::ClientItemId;
use crate::ship::items::state::{ItemState, ItemStateError}; use crate::ship::items::state::{ItemState, ItemStateError};
use crate::ship::items::inventory::InventoryItemDetail; use crate::ship::items::inventory::InventoryItemDetail;
@ -57,9 +57,9 @@ async fn do_trade_action<F>(id: ClientId,
this: &mut ClientTradeState, this: &mut ClientTradeState,
other: &mut ClientTradeState, other: &mut ClientTradeState,
action: F) action: F)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> Result<(), ShipError>,
F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> Result<(), anyhow::Error>,
{ {
Ok(match action(this, other) { Ok(match action(this, other) {
Ok(_) => { Ok(_) => {
@ -92,7 +92,7 @@ pub async fn trade_request(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState, item_state: &mut ItemState,
trades: &mut TradeState) trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{ {
let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet
match trade_request.trade { match trade_request.trade {
@ -293,12 +293,12 @@ async fn inner_items_to_trade(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState, item_state: &mut ItemState,
trades: &mut TradeState) trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{ {
let pkts = trades let pkts = trades
.with(&id, |mut this, other| async move { .with(&id, |mut this, other| async move {
if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) { 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 other_client = other.client();
let (this_inventory, other_inventory) = clients.with(this.client(), |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 { Box::pin(async move {
item_state.get_character_inventory(&client.character).await item_state.get_character_inventory(&client.character).await
})}).await??; })}).await??;
Ok::<_, ShipError>((this, other_inventory))
Ok::<_, anyhow::Error>((this, other_inventory))
})}).await??; })}).await??;
if items_to_trade.count as usize != (this.items.len() + usize::from(this.meseta != 0)) { 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::<Result<Vec<_>, ShipError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
this.status = TradeStatus::ItemsChecked; this.status = TradeStatus::ItemsChecked;
if this.status == TradeStatus::ItemsChecked && other.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, clients: &Clients,
item_state: &mut ItemState, item_state: &mut ItemState,
trades: &mut TradeState) trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{ {
let t = inner_items_to_trade(id, items_to_trade_pkt, client_location, clients, item_state, trades).await; let t = inner_items_to_trade(id, items_to_trade_pkt, client_location, clients, item_state, trades).await;
match t { match t {
@ -443,7 +443,7 @@ async fn trade_confirmed_inner<EG>(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState, item_state: &mut ItemState,
trades: &mut TradeState) trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {
@ -460,14 +460,14 @@ where
.with(&id, |mut this, other| { .with(&id, |mut this, other| {
async move { async move {
if status_is_not(&this.status, &[TradeStatus::ItemsChecked]) || status_is_not(&other.status, &[TradeStatus::ItemsChecked, TradeStatus::TradeComplete]) { 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; this.status = TradeStatus::TradeComplete;
if this.status == TradeStatus::TradeComplete && other.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 this_local_client = client_location.get_local_client(this.client()).await?;
let other_local_client = client_location.get_local_client(other.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, Ok(TradeReady::BothPlayers(room_id,
(this_local_client, /*this_client, */this.clone()), (this_local_client, /*this_client, */this.clone()),
@ -584,7 +584,7 @@ pub async fn trade_confirmed<EG>(id: ClientId,
clients: &Clients, clients: &Clients,
item_state: &mut ItemState, item_state: &mut ItemState,
trades: &mut TradeState) trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where where
EG: EntityGateway + Clone + 'static, EG: EntityGateway + Clone + 'static,
{ {

65
src/ship/room.rs

@ -13,12 +13,10 @@ use crate::ship::drops::DropTable;
use crate::entity::character::SectionID; use crate::entity::character::SectionID;
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
use crate::ship::map::area::MapAreaLookup; use crate::ship::map::area::MapAreaLookup;
use crate::ship::map::enemy::RareMonsterAppearTable;
use crate::ship::quests; use crate::ship::quests;
use crate::ship::ship::{ShipError, ShipEvent}; use crate::ship::ship::{ShipError, ShipEvent};
use crate::ship::location::{MAX_ROOMS, RoomId}; use crate::ship::location::{MAX_ROOMS, RoomId};
#[derive(Clone)] #[derive(Clone)]
pub struct Rooms([Arc<RwLock<Option<RoomState>>>; MAX_ROOMS]); pub struct Rooms([Arc<RwLock<Option<RoomState>>>; MAX_ROOMS]);
@ -29,7 +27,7 @@ impl Default for Rooms {
} }
impl 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 *self.0
.get(room_id.0) .get(room_id.0)
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))? .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<T, ShipError>
pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where where
T: Send, T: Send,
F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a
@ -72,11 +70,11 @@ impl Rooms {
Ok(func(room).await) Ok(func(room).await)
} }
else { 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<T, ShipError>
pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where where
T: Send, T: Send,
F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a
@ -91,7 +89,7 @@ impl Rooms {
Ok(func(room).await) Ok(func(room).await)
} }
else { 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, Four,
} }
#[derive(Debug, Copy, Clone)]
pub enum PlayerMode{
Single,
Multi,
}
impl PlayerMode {
pub fn value(&self) -> u8 {
match self {
PlayerMode::Single => 1,
PlayerMode::Multi => 0,
}
}
}
impl TryFrom<u8> for Episode { impl TryFrom<u8> for Episode {
type Error = RoomCreationError; 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 { match self {
RoomMode::Single {..} => 1,
_ => 0,
RoomMode::Single {..} => PlayerMode::Single,
_ => PlayerMode::Multi,
} }
} }
} }
@ -295,13 +302,12 @@ pub struct RoomState {
pub name: String, pub name: String,
pub password: [u16; 16], pub password: [u16; 16],
pub maps: Maps, pub maps: Maps,
pub drop_table: Box<DropTable<rand_chacha::ChaCha20Rng>>,
pub drop_table: Box<DropTable>,
pub section_id: SectionID, pub section_id: SectionID,
pub random_seed: u32, pub random_seed: u32,
pub bursting: bool, pub bursting: bool,
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>, pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
pub map_areas: MapAreaLookup, pub map_areas: MapAreaLookup,
pub rare_monster_table: Box<RareMonsterAppearTable>,
pub quest_group: QuestCategoryType, pub quest_group: QuestCategoryType,
pub quests: Vec<quests::QuestList>, pub quests: Vec<quests::QuestList>,
// items on ground // items on ground
@ -343,7 +349,12 @@ impl RoomState {
self.quest_group = QuestCategoryType::from(group); self.quest_group = QuestCategoryType::from(group);
} }
pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID, event: ShipEvent) -> Result<RoomState, RoomCreationError> {
pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom,
map_builder: Arc<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>,
section_id: SectionID,
event: ShipEvent)
-> Result<RoomState, RoomCreationError> {
if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::<u8>() > 1 { if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::<u8>() > 1 {
return Err(RoomCreationError::InvalidMode) 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 // push the usual set of quests for the selected mode
let mut qpath = PathBuf::from("data/quests/bb"); let mut qpath = PathBuf::from("data/quests/bb");
@ -401,18 +411,15 @@ impl RoomState {
room_quests.push(quest_list); room_quests.push(quest_list);
} }
Ok(RoomState { Ok(RoomState {
monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?), monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?),
mode: room_mode, mode: room_mode,
random_seed: rand::thread_rng().gen(), 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(), name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(),
password: create_room.password, password: create_room.password,
maps: Maps::new(room_mode, &rare_monster_table, event),
maps: map_builder(room_mode, event),
section_id, 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, bursting: false,
map_areas: MapAreaLookup::new(&room_mode.episode()), map_areas: MapAreaLookup::new(&room_mode.episode()),
quest_group: QuestCategoryType::Standard, quest_group: QuestCategoryType::Standard,

44
src/ship/ship.rs

@ -4,7 +4,6 @@ use std::collections::HashMap;
use async_std::channel; use async_std::channel;
use async_std::sync::{Arc, Mutex, RwLock}; use async_std::sync::{Arc, Mutex, RwLock};
use rand::Rng; use rand::Rng;
use thiserror::Error; use thiserror::Error;
@ -13,24 +12,19 @@ use libpso::packet::login::{RedirectClient, Login, LoginResponse, ShipList};
use libpso::packet::messages::*; use libpso::packet::messages::*;
use libpso::{PacketParseError, PSOPacket}; use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher; use libpso::crypto::bb::PSOBBCipher;
use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID}; use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID};
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage}; use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, LoginMessage, ShipMessage};
use crate::login::character::SHIP_MENU_ID; use crate::login::character::SHIP_MENU_ID;
use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::SectionID; use crate::entity::character::SectionID;
use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId}; use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId};
use crate::ship::drops::DropTable;
use crate::ship::items; use crate::ship::items;
use crate::ship::room; 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::packet::handler;
use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop}; use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop};
use crate::ship::trade::TradeState; use crate::ship::trade::TradeState;
@ -162,11 +156,13 @@ pub enum ShipError {
SendError(#[from] async_std::channel::SendError<ShipMessage>), SendError(#[from] async_std::channel::SendError<ShipMessage>),
} }
/*
impl<I: Into<ClientLocationError>> From<I> for ShipError { impl<I: Into<ClientLocationError>> From<I> for ShipError {
fn from(other: I) -> ShipError { fn from(other: I) -> ShipError {
ShipError::ClientLocationError(other.into()) ShipError::ClientLocationError(other.into())
} }
} }
*/
#[derive(Debug)] #[derive(Debug)]
@ -375,6 +371,8 @@ pub struct ShipServerStateBuilder<EG: EntityGateway + Clone + 'static> {
port: Option<u16>, port: Option<u16>,
auth_token: Option<AuthToken>, auth_token: Option<AuthToken>,
event: Option<ShipEvent>, event: Option<ShipEvent>,
map_builder: Option<Box<dyn Fn(room::RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Option<Box<dyn Fn(room::Episode, room::Difficulty, SectionID) -> DropTable + Send + Sync>>,
num_blocks: usize, num_blocks: usize,
} }
@ -387,6 +385,8 @@ impl<EG: EntityGateway + Clone + 'static> Default for ShipServerStateBuilder<EG>
port: None, port: None,
auth_token: None, auth_token: None,
event: None, event: None,
map_builder: None,
drop_table_builder: None,
num_blocks: 2, num_blocks: 2,
} }
} }
@ -429,6 +429,18 @@ impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
self self
} }
#[must_use]
pub fn map_builder(mut self, map_builder: Box<dyn Fn(room::RoomMode, ShipEvent) -> Maps + Send + Sync>) -> ShipServerStateBuilder<EG> {
self.map_builder = Some(map_builder);
self
}
#[must_use]
pub fn drop_table_builder(mut self, drop_table_builder: Box<dyn Fn(room::Episode, room::Difficulty, SectionID) -> DropTable + Send + Sync>) -> ShipServerStateBuilder<EG> {
self.drop_table_builder = Some(drop_table_builder);
self
}
#[must_use] #[must_use]
pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder<EG> { pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder<EG> {
self.num_blocks = num_blocks; self.num_blocks = num_blocks;
@ -447,6 +459,8 @@ impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
shops: ItemShops::default(), shops: ItemShops::default(),
blocks: Blocks(blocks), blocks: Blocks(blocks),
event: self.event.unwrap_or(ShipEvent::None), 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())), auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
ship_list: Arc::new(RwLock::new(Vec::new())), ship_list: Arc::new(RwLock::new(Vec::new())),
@ -467,13 +481,13 @@ pub struct Block {
pub struct Blocks(pub Vec<Block>); pub struct Blocks(pub Vec<Block>);
impl Blocks { 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 { let block = clients.with(id, |client| Box::pin(async move {
client.block client.block
})).await?; })).await?;
self.0 self.0
.get_mut(block) .get_mut(block)
.ok_or_else(|| ShipError::InvalidBlock(block))
.ok_or_else(|| ShipError::InvalidBlock(block).into())
} }
} }
@ -495,6 +509,8 @@ pub struct ShipServerState<EG: EntityGateway + Clone + 'static> {
ship_list: Arc<RwLock<Vec<Ship>>>, ship_list: Arc<RwLock<Vec<Ship>>>,
shipgate_sender: Option<channel::Sender<ShipMessage>>, shipgate_sender: Option<channel::Sender<ShipMessage>>,
trades: TradeState, trades: TradeState,
map_builder: Arc<Box<dyn Fn(room::RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(room::Episode, room::Difficulty, SectionID) -> DropTable + Send + Sync>>,
} }
impl<EG: EntityGateway + Clone + 'static> ShipServerState<EG> { impl<EG: EntityGateway + Clone + 'static> ShipServerState<EG> {
@ -665,12 +681,12 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
let block = self.blocks.get_from_client(id, &self.clients).await?; let block = self.blocks.get_from_client(id, &self.clients).await?;
match menuselect.menu { match menuselect.menu {
SHIP_MENU_ID => { 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?; let select_ship = handler::ship::selected_ship(id, menuselect, &self.ship_list).await?;
leave_lobby.chain(select_ship).collect() leave_lobby.chain(select_ship).collect()
} }
BLOCK_MENU_ID => { 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(); let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter();
leave_lobby.chain(select_block).collect() leave_lobby.chain(select_block).collect()
} }
@ -721,7 +737,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
}, },
RecvShipPacket::CreateRoom(create_room) => { RecvShipPacket::CreateRoom(create_room) => {
let block = self.blocks.get_from_client(id, &self.clients).await?; 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) => { RecvShipPacket::RoomNameRequest(_req) => {
let block = self.blocks.get_from_client(id, &self.clients).await?; let block = self.blocks.get_from_client(id, &self.clients).await?;
@ -834,6 +850,8 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
self.item_state.remove_character_from_room(&client.character).await 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| { Ok(neighbors.into_iter().map(|n| {
(n.client, pkt.clone()) (n.client, pkt.clone())
}).collect()) }).collect())

3
tests/common.rs

@ -13,6 +13,7 @@ use libpso::packet::login::{Login, Session};
use libpso::{utf8_to_array, utf8_to_utf16_array}; use libpso::{utf8_to_array, utf8_to_utf16_array};
//TODO: remove kb_conf_preset
pub async fn new_user_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, username: &str, password: &str, kb_conf_preset: usize) -> (UserAccountEntity, CharacterEntity) { pub async fn new_user_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, username: &str, password: &str, kb_conf_preset: usize) -> (UserAccountEntity, CharacterEntity) {
let new_user = NewUserAccountEntity { let new_user = NewUserAccountEntity {
email: format!("{}@pso.com", username), email: format!("{}@pso.com", username),
@ -26,7 +27,7 @@ pub async fn new_user_character<EG: EntityGateway + Clone>(entity_gateway: &mut
let user = entity_gateway.create_user(new_user).await.unwrap(); let user = entity_gateway.create_user(new_user).await.unwrap();
let new_settings = NewUserSettingsEntity::new(user.id); let new_settings = NewUserSettingsEntity::new(user.id);
let _settings = entity_gateway.create_user_settings(new_settings).await.unwrap(); 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(); let character = entity_gateway.create_character(new_character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, Meseta(0)).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(); entity_gateway.set_bank_meseta(&character.id, &BankName("".into()), Meseta(0)).await.unwrap();

29
tests/test_character.rs

@ -31,26 +31,6 @@ async fn test_save_options() {
assert!(char.option_flags == 12345); 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_std::test]
async fn test_change_keyboard_mappings() { async fn test_change_keyboard_mappings() {
let mut entity_gateway = InMemoryGateway::default(); 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; log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).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 // update from default2 to default4
// the client simply sends the full 364 bytes... // the client simply sends the full 364 bytes...
@ -95,8 +76,6 @@ async fn test_change_keyboard_mappings() {
], ],
})).await.unwrap(); })).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);
} }

117
tests/test_exp_gain.rs

@ -4,6 +4,10 @@ use elseware::common::leveltable::CharacterLevelTable;
use elseware::ship::ship::{ShipServerState, SendShipPacket, RecvShipPacket}; use elseware::ship::ship::{ShipServerState, SendShipPacket, RecvShipPacket};
use elseware::ship::monster::MonsterType; use elseware::ship::monster::MonsterType;
use elseware::ship::location::RoomId; 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::ship::*;
use libpso::packet::messages::*; use libpso::packet::messages::*;
@ -20,35 +24,29 @@ async fn test_character_gains_exp() {
let mut ship = Box::new(ShipServerState::builder() let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone()) .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()); .build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await; log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await; join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").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 { ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0,
target: 16, target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0,
client_id: 0, client_id: 0,
unused: 0, unused: 0,
last_hitter: 1, last_hitter: 1,
})))).await.unwrap(); })))).await.unwrap();
ship.clients.with(ClientId(1), |client| Box::pin(async move { ship.clients.with(ClientId(1), |client| Box::pin(async move {
assert!(exp == client.character.exp);
assert!(13 == client.character.exp);
})).await.unwrap(); })).await.unwrap();
} }
@ -62,29 +60,28 @@ async fn test_character_levels_up() {
let mut ship = Box::new(ShipServerState::builder() let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone()) .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()); .build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await; log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await; join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").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 { 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, target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0 as u16,
client_id: 0, client_id: 0,
unused: 0, unused: 0,
last_hitter: 1, last_hitter: 1,
})))).await.unwrap(); })))).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(); let leveltable = CharacterLevelTable::default();
ship.clients.with(ClientId(1), |client| Box::pin(async move { ship.clients.with(ClientId(1), |client| Box::pin(async move {
@ -100,41 +97,31 @@ async fn test_character_levels_up_multiple_times() {
let mut ship = Box::new(ShipServerState::builder() let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone()) .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()); .build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await; log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await; join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").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 { 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, target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0 as u16,
client_id: 0, client_id: 0,
unused: 0, unused: 0,
last_hitter: 1, last_hitter: 1,
})))).await.unwrap(); })))).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 { ship.clients.with(ClientId(1), |client| Box::pin(async move {
assert!(exp == client.character.exp);
assert!(3000 == client.character.exp);
})).await.unwrap(); })).await.unwrap();
} }
@ -147,6 +134,13 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() {
let mut ship = Box::new(ShipServerState::builder() let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone()) .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()); .build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await; log_in_char(&mut ship, ClientId(1), "a1", "a").await;
log_in_char(&mut ship, ClientId(2), "a2", "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; create_room(&mut ship, ClientId(1), "room", "").await;
join_room(&mut ship, ClientId(2), 0).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 { ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0,
target: 16, target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0,
client_id: 0, client_id: 0,
unused: 0, unused: 0,
last_hitter: 1, last_hitter: 1,
})))).await.unwrap(); })))).await.unwrap();
ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp { ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0,
target: 16, target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0,
client_id: 0, client_id: 0,
unused: 0, unused: 0,
last_hitter: 0, last_hitter: 0,
})))).await.unwrap(); })))).await.unwrap();
ship.clients.with(ClientId(1), |client| Box::pin(async move { ship.clients.with(ClientId(1), |client| Box::pin(async move {
assert!(client.character.exp == exp);
assert_eq!(client.character.exp, 13);
})).await.unwrap(); })).await.unwrap();
ship.clients.with(ClientId(2), |client| Box::pin(async move { 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(); })).await.unwrap();
} }

210
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();
}

87
tests/test_item_use.rs

@ -2,6 +2,7 @@ use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::entity::item; use elseware::entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket}; use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::entity::character::TechLevel;
//use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType}; //use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType};
use libpso::packet::ship::*; 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 // TODO: tests for ALL ITEMS WOW
/* /*

2
tests/test_rooms.rs

@ -97,6 +97,7 @@ async fn test_item_ids_reset_when_rejoining_rooms() {
} }
} }
/*
#[async_std::test] #[async_std::test]
async fn test_load_rare_monster_default_appear_rates() { async fn test_load_rare_monster_default_appear_rates() {
let mut entity_gateway = InMemoryGateway::default(); let mut entity_gateway = InMemoryGateway::default();
@ -116,6 +117,7 @@ async fn test_load_rare_monster_default_appear_rates() {
} }
})).await.unwrap(); })).await.unwrap();
} }
*/
#[async_std::test] #[async_std::test]
async fn test_set_valid_quest_group() { async fn test_set_valid_quest_group() {

3
tests/test_shops.rs

@ -1143,7 +1143,8 @@ async fn test_player_cant_sell_if_meseta_would_go_over_max() {
amount: 1, amount: 1,
})))).await.err().unwrap(); })))).await.err().unwrap();
//assert_eq!(ack, ShipError::ItemStateError(ItemStateError::FullOfMeseta)); //assert_eq!(ack, ShipError::ItemStateError(ItemStateError::FullOfMeseta));
assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::ItemStateError(ItemStateError::FullOfMeseta)));
assert!(matches!(ack.downcast::<ItemStateError>().unwrap(), ItemStateError::FullOfMeseta));
//assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::ItemStateError(ItemStateError::FullOfMeseta)));
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert_eq!(c1_meseta.0, 999995); assert_eq!(c1_meseta.0, 999995);

6
tests/test_trade.rs

@ -3155,7 +3155,7 @@ async fn test_client_tries_to_start_two_trades() {
trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0)
})))).await.err().unwrap(); })))).await.err().unwrap();
assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::TradeError(TradeError::ClientAlreadyInTrade)));
assert!(matches!(ack.downcast::<TradeError>().unwrap(), TradeError::ClientAlreadyInTrade));
} }
#[async_std::test] #[async_std::test]
@ -3187,14 +3187,14 @@ async fn test_client_tries_trading_with_client_already_trading() {
target: 0, target: 0,
trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0)
})))).await.err().unwrap(); })))).await.err().unwrap();
assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade)));
assert!(matches!(ack.downcast::<TradeError>().unwrap(), TradeError::OtherAlreadyInTrade));
let ack = ship.handle(ClientId(3), RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { let ack = ship.handle(ClientId(3), RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest {
client: 2, client: 2,
target: 0, target: 0,
trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 1) trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 1)
})))).await.err().unwrap(); })))).await.err().unwrap();
assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade)));
assert!(matches!(ack.downcast::<TradeError>().unwrap(), TradeError::OtherAlreadyInTrade));
} }
#[async_std::test] #[async_std::test]

Loading…
Cancel
Save