Browse Source

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

Reviewed-on: #127
pull/129/head
jake 1 year 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. 67
      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. 90
      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. 136
      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. 53
      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. 133
      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]]
name = "anyhow"
version = "1.0.57"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
dependencies = [
"backtrace",
]
@ -1040,6 +1040,7 @@ checksum = "739e9d7726dc32173fed2d69d17eef3c54682169e4e20ff1d0a45dcd37063cef"
[[package]]
name = "libpso"
version = "0.1.0"
source = "git+http://git.sharnoth.com/jake/libpso#5051514fb1d3b39a7eb6ff97b624a9ceebd93e40"
dependencies = [
"chrono",
"psopacket",
@ -1398,6 +1399,7 @@ dependencies = [
[[package]]
name = "psopacket"
version = "1.0.0"
source = "git+http://git.sharnoth.com/jake/libpso#5051514fb1d3b39a7eb6ff97b624a9ceebd93e40"
dependencies = [
"proc-macro2",
"quote",

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

31
src/common/mainloop/client.rs

@ -1,10 +1,11 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Write;
use async_std::channel;
use async_std::io::prelude::{ReadExt, WriteExt};
use async_std::sync::{Arc, RwLock};
use futures::future::Future;
use log::{trace, info, warn};
use log::{trace, info, warn, error};
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use libpso::PacketParseError;
@ -132,7 +133,7 @@ where
match pkt_receiver.recv_pkts::<R>().await {
Ok(pkts) => {
for pkt in pkts {
info!("[recv from {:?}] {:#?}", client_id, pkt);
trace!("[recv from {:?}] {:#?}", client_id, pkt);
match state.handle(client_id, pkt).await {
Ok(response) => {
for resp in response {
@ -147,7 +148,27 @@ where
}
},
Err(err) => {
warn!("[client recv {:?}] error {:?} ", client_id, err);
error!("[client recv {:?}] error {:?} ", client_id, err);
let mut f = std::fs::File::options().create(true).append(true).open("errors.txt").unwrap();
f.write_all(format!("[{client_id:?}] {err:?}").as_bytes()).unwrap();
// disconnect client on an error
for pkt in state.on_disconnect(client_id).await.unwrap() {
clients
.read()
.await
.get(&pkt.0)
.unwrap()
.send(pkt.1)
.await
.unwrap();
}
clients
.write()
.await
.remove(&client_id);
break;
}
}
}
@ -173,7 +194,7 @@ where
break;
}
_ => {
warn!("[client {:?} recv error] {:?}", client_id, err);
error!("[client {:?} recv error] {:?}", client_id, err);
}
}
}
@ -206,7 +227,7 @@ where
Ok(pkt) => {
info!("[send to {:?}] {:#?}", client_id, pkt);
if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await {
warn!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
error!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
}
},
Err(err) => {

96
src/entity/character.rs

@ -2,8 +2,8 @@ use std::convert::{From, Into};
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use libpso::packet::ship::{UpdateConfig, WriteInfoboard, KeyboardConfig, GamepadConfig};
use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU, DEFAULT_KEYBOARD_CONFIG1, DEFAULT_KEYBOARD_CONFIG2, DEFAULT_KEYBOARD_CONFIG3, DEFAULT_KEYBOARD_CONFIG4, DEFAULT_GAMEPAD_CONFIG};
use libpso::packet::ship::{UpdateConfig, WriteInfoboard};
use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU};
use crate::entity::item::tech::Technique;
use crate::entity::account::UserAccountId;
@ -157,7 +157,7 @@ pub struct CharacterAppearance {
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TechLevel(pub u8);
#[derive(Clone, Debug, Default)]
@ -167,16 +167,14 @@ pub struct CharacterTechniques {
impl CharacterTechniques {
pub fn set_tech(&mut self, tech: Technique, level: TechLevel) {
self.techs.insert(tech, TechLevel(level.0 - 1));
self.techs.insert(tech, TechLevel(level.0));
}
// from_bytes
pub fn as_bytes(&self) -> [u8; 20] {
self.techs.iter()
.fold([0xFF; 20], |mut techlist, (tech, level)| {
let index = tech.as_value();
techlist[index as usize] = level.0;
techlist[index as usize] = level.0 - 1;
techlist
})
}
@ -264,82 +262,6 @@ pub struct CharacterMaterials {
pub tp: u32,
}
#[derive(Clone, Debug)]
pub struct CharacterKeyboardConfig {
pub keyboard_config: [u8; 0x16C],
}
impl Default for CharacterKeyboardConfig {
fn default() -> CharacterKeyboardConfig {
CharacterKeyboardConfig {
keyboard_config: DEFAULT_KEYBOARD_CONFIG1,
}
}
}
impl CharacterKeyboardConfig {
fn new(preset: usize) -> CharacterKeyboardConfig {
match preset {
1 => {
CharacterKeyboardConfig {
keyboard_config: DEFAULT_KEYBOARD_CONFIG1,
}
},
2 => {
CharacterKeyboardConfig {
keyboard_config: DEFAULT_KEYBOARD_CONFIG2,
}
},
3 => {
CharacterKeyboardConfig {
keyboard_config: DEFAULT_KEYBOARD_CONFIG3,
}
},
4 => {
CharacterKeyboardConfig {
keyboard_config: DEFAULT_KEYBOARD_CONFIG4,
}
},
_ => {
CharacterKeyboardConfig {
keyboard_config: DEFAULT_KEYBOARD_CONFIG1,
}
},
}
}
pub fn update(&mut self, new_config: &KeyboardConfig) {
self.keyboard_config = new_config.keyboard_config;
}
pub fn as_bytes(&self) -> [u8; 0x16C] {
self.keyboard_config
}
}
#[derive(Clone, Debug)]
pub struct CharacterGamepadConfig {
pub gamepad_config: [u8; 0x38],
}
impl Default for CharacterGamepadConfig {
fn default() -> CharacterGamepadConfig {
CharacterGamepadConfig {
gamepad_config: DEFAULT_GAMEPAD_CONFIG,
}
}
}
impl CharacterGamepadConfig {
pub fn update(&mut self, new_config: &GamepadConfig) {
self.gamepad_config = new_config.gamepad_config;
}
pub fn as_bytes(&self) -> [u8; 0x38] {
self.gamepad_config
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)]
pub struct CharacterEntityId(pub u32);
@ -363,12 +285,10 @@ pub struct NewCharacterEntity {
pub tech_menu: CharacterTechMenu,
pub option_flags: u32,
pub keyboard_config: CharacterKeyboardConfig,
pub gamepad_config: CharacterGamepadConfig,
}
impl NewCharacterEntity {
pub fn new(user: UserAccountId, keyboard_config_preset: usize,) -> NewCharacterEntity {
pub fn new(user: UserAccountId) -> NewCharacterEntity {
NewCharacterEntity {
user_id: user,
slot: 0,
@ -384,8 +304,6 @@ impl NewCharacterEntity {
materials: CharacterMaterials::default(),
tech_menu: CharacterTechMenu::default(),
option_flags: 0,
keyboard_config: CharacterKeyboardConfig::new(keyboard_config_preset),
gamepad_config: CharacterGamepadConfig::default(),
}
}
}
@ -411,8 +329,6 @@ pub struct CharacterEntity {
pub tech_menu: CharacterTechMenu,
pub option_flags: u32,
pub keyboard_config: CharacterKeyboardConfig,
pub gamepad_config: CharacterGamepadConfig,
pub playtime: u32,
}

9
src/entity/gateway/entitygateway.rs

@ -1,4 +1,3 @@
use std::convert::From;
use thiserror::Error;
use futures::Future;
@ -10,9 +9,10 @@ use crate::entity::item::*;
// TODO: better granularity?
//#[derive(Error, Debug)]
#[derive(Error, Debug)]
#[error("")]
pub enum GatewayError {
#[error("unknown error")]
Error,
#[error("postgres error {0}")]
PgError(#[from] sqlx::Error)
}
@ -21,12 +21,11 @@ pub enum GatewayError {
pub trait EntityGateway: Send + Sync {
type Transaction: EntityGatewayTransaction + Clone;
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result<R, E>
async fn with_transaction<'a, F, Fut, R>(&'a mut self, _func: F) -> Result<R, anyhow::Error>
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,
R: Send,
E: From<GatewayError>,
Self: Sized
{
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> {
copy_if_needed(&mut *self.working_gateway.characters.lock().await,
&*self.original_gateway.characters.lock().await,
@ -304,12 +308,11 @@ fn apply_modifiers(items: &BTreeMap<ItemEntityId, ItemEntity>,
impl EntityGateway for InMemoryGateway {
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
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,
R: Send,
E: From<GatewayError>,
{
let users = self.users.lock().await.clone();
let user_settings = self.user_settings.lock().await.clone();
@ -419,6 +422,12 @@ impl EntityGateway for InMemoryGateway {
.ok_or(GatewayError::Error)
}
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
let mut user_settings = self.user_settings.lock().await;
user_settings.insert(settings.id, settings.clone());
Ok(())
}
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
let characters = self.characters.lock().await;
const NONE: Option<CharacterEntity> = None;
@ -453,8 +462,6 @@ impl EntityGateway for InMemoryGateway {
materials: character.materials,
tech_menu: character.tech_menu,
option_flags: character.option_flags,
keyboard_config: character.keyboard_config,
gamepad_config: character.gamepad_config,
playtime: 0,
};
characters.insert(new_character.id, new_character.clone());

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

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

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

@ -1,5 +1,5 @@
create table trades (
id serial primary key not null,
character1 integer references character (id) not null,
character2 integer references character (id) not null,
character1 integer references player_character (id) not null,
character2 integer references player_character (id) not null
);

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,
user_account: i32,
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,
shortcuts: Vec<u8>, //[u8; 0xA40],
symbol_chats: Vec<u8>, //[u8; 0x4E0],
@ -64,8 +64,8 @@ impl From<PgUserSettings> for UserSettingsEntity {
user_id: UserAccountId(other.user_account as u32),
settings: settings::UserSettings {
blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
keyboard_config: vec_to_array(other.keyboard_config),
gamepad_config: vec_to_array(other.gamepad_config),
keyboard_config: vec_to_array(other.key_config),
gamepad_config: vec_to_array(other.joystick_config),
option_flags: other.option_flags as u32,
shortcuts: vec_to_array(other.shortcuts),
symbol_chats: vec_to_array(other.symbol_chats),
@ -217,8 +217,6 @@ pub struct PgCharacter {
tp: i16,
tech_menu: Vec<u8>,
keyboard_config: Vec<u8>,
gamepad_config: Vec<u8>,
playtime: i32,
}
@ -246,7 +244,7 @@ impl From<PgCharacter> for CharacterEntity {
prop_y: other.prop_y,
},
techs: CharacterTechniques {
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t + 1)) ).collect()
},
config: CharacterConfig {
raw_data: vec_to_array(other.config)
@ -270,12 +268,6 @@ impl From<PgCharacter> for CharacterEntity {
tech_menu: CharacterTechMenu {
tech_menu: vec_to_array(other.tech_menu)
},
keyboard_config: CharacterKeyboardConfig {
keyboard_config: vec_to_array(other.keyboard_config)
},
gamepad_config: CharacterGamepadConfig {
gamepad_config: vec_to_array(other.gamepad_config)
},
playtime: other.playtime as u32,
}
}

67
src/entity/gateway/postgres/postgres.rs

@ -2,7 +2,7 @@
#![allow(clippy::explicit_auto_deref)]
use std::convert::{From, TryFrom, Into};
use futures::{Future, TryStreamExt};
use futures::Future;
use async_std::stream::StreamExt;
use async_std::sync::{Arc, Mutex};
use libpso::character::guildcard;
@ -67,7 +67,7 @@ impl<'t> PostgresGateway<'t> {
let pool = async_std::task::block_on(async move {
PgPoolOptions::new()
.max_connections(5)
.connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap()
.connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap()
});
PostgresGateway {
@ -217,17 +217,26 @@ async fn save_user_settings(conn: &mut sqlx::PgConnection, settings: &UserSettin
.bind(&settings.settings.symbol_chats.to_vec())
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.id.0)
.fetch_one(conn).await?;
.execute(conn).await?;
Ok(())
}
async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError>
{
let q = r#"insert into player_character
(user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags)
(user_account, slot, name, exp, class,
section_id, costume, skin, face, head,
hair, hair_r, hair_g, hair_b, prop_x,
prop_y, techs, config, infoboard, guildcard,
power, mind, def, evade, luck,
hp, tp, tech_menu, option_flags, playtime)
values
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)
($1, $2, $3, $4, $5,
$6, $7, $8, $9, $10,
$11, $12, $13, $14, $15,
$16, $17, $18, $19, $20,
$21, $22, $23, $24, $25,
$26, $27, $28, $29, $30)
returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q)
.bind(char.user_id.0)
@ -259,6 +268,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
.bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec())
.bind(char.option_flags as i32)
.bind(0)
.fetch_one(conn).await?;
Ok(character.into())
@ -266,17 +276,17 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<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)
.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>
@ -285,7 +295,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12,
hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23,
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29, playtime=$30
where id=$31;"#;
where id=$31;"#;
sqlx::query(q)
.bind(char.user_id.0) // $1
.bind(char.slot as i16) // $2
@ -316,7 +326,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
.bind(char.materials.tp as i16) // $27
.bind(char.tech_menu.tech_menu.to_vec()) // $28
.bind(char.option_flags as i32) // $29
.bind(char.playtime as i32) // $20
.bind(char.playtime as i32) // $30
.bind(char.id.0 as i32) // $31
.execute(conn).await?;
Ok(())
@ -530,7 +540,7 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character
async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError>
{
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2")
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2")
.bind(char_id.0)
.bind(meseta.0 as i32)
.execute(conn)
@ -542,7 +552,7 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
{
#[derive(sqlx::FromRow)]
struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#)
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#)
.bind(char_id.0)
.fetch_one(conn)
.await?;
@ -551,10 +561,10 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError>
{
sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2")
sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set meseta = $3")
.bind(char_id.0)
.bind(meseta.0 as i32)
.bind(bank.0.clone())
.bind(meseta.0 as i32)
.execute(conn)
.await?;
Ok(())
@ -564,7 +574,7 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
{
#[derive(sqlx::FromRow)]
struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1 and bank = $2"#)
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1 and bank = $2"#)
.bind(char_id.0)
.bind(bank.0.clone())
.fetch_one(conn)
@ -584,10 +594,10 @@ async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityI
async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError>
{
sqlx::query_as::<_, PgTradeEntity>(r#"update player_character set playtime=$2 where id=$1;"#)
sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#)
.bind(char_id.0)
.bind(playtime)
.fetch_one(conn)
.execute(conn)
.await?;
Ok(())
}
@ -596,18 +606,17 @@ async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &Charact
impl<'t> EntityGateway for PostgresGateway<'t> {
type Transaction = PostgresTransaction<'t>;
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
async fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> Result<R, anyhow::Error>
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,
R: Send,
E: From<GatewayError>,
{
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)
}

2
src/entity/item/mag.rs

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

4
src/lib.rs

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

16
src/login/character.rs

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

2
src/login/login.rs

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

6
src/patch/patch.rs

@ -395,18 +395,18 @@ pub struct PatchConfig {
pub fn load_config() -> PatchConfig {
let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) {
Err(err) => panic!("Failed to open patch.ron config file. \n{}", err),
Err(err) => panic!("Failed to open patch.ron config file. \n{err}"),
Ok(ini_file) => ini_file,
};
let mut s = String::new();
if let Err(err) = (&ini_file).read_to_string(&mut s) {
panic!("Failed to read patch.ron config file. \n{}", err);
panic!("Failed to read patch.ron config file. \n{err}");
}
let config: PatchConfig = match from_str(s.as_str()) {
Ok(config) => config,
Err(err) => panic!("Failed to load values from patch.ron \n{}",err),
Err(err) => panic!("Failed to load values from patch.ron \n{err}"),
};
config
}

6
src/ship/client.rs

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

88
src/ship/drops/mod.rs

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

90
src/ship/drops/rare_drop_table.rs

@ -50,9 +50,9 @@ impl RareDropItem {
}
struct RareDropRate {
rate: f32,
item: RareDropItem
pub struct RareDropRate {
pub rate: f32,
pub item: RareDropItem
}
@ -71,30 +71,41 @@ pub struct RareDropTable {
shield_stats: GenericShieldTable,
}
impl RareDropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
let cfg: HashMap<String, Vec<RareDropConfigEntity>> = load_data_file(episode, difficulty, section_id, "rare_rate.toml");
let rates = cfg.into_iter()
.map(|(monster, drops)| {
let monster = monster.parse().unwrap();
let drops = drops.into_iter().map(|drop| {
RareDropRate {
rate: drop.rate,
item: RareDropItem::from_string(drop.item),
}
}).collect();
(monster, drops)
fn load_default_monster_rates(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> HashMap<MonsterType, Vec<RareDropRate>> {
let cfg: HashMap<String, Vec<RareDropConfigEntity>> = load_data_file(episode, difficulty, section_id, "rare_rate.toml");
cfg.into_iter()
.map(|(monster, drops)| {
let monster = monster.parse().unwrap();
let drops = drops.into_iter().map(|drop| {
RareDropRate {
rate: drop.rate,
item: RareDropItem::from_string(drop.item),
}
}).collect();
(monster, drops)
}).collect()
}
impl RareDropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
RareDropTable {
rates,
rates: load_default_monster_rates(episode, difficulty, section_id),
attribute_table: AttributeTable::new(episode, difficulty, section_id),
armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
shield_stats: GenericShieldTable::new(episode, difficulty, section_id),
}
}
pub fn builder() -> RareDropTableBuilder {
RareDropTableBuilder {
rates: None,
attribute_table: None,
armor_stats: None,
shield_stats: None,
}
}
pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
match item {
RareDropItem::Weapon(weapon) => {
@ -155,3 +166,46 @@ impl RareDropTable {
})
}
}
pub struct RareDropTableBuilder {
rates: Option<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,
item_id: ClientItemId
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway + Send,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -54,7 +54,7 @@ where
pub(super) fn add_floor_item_to_inventory<EG, TR>(
character: &CharacterEntity
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<Result<((ItemStateProxy, TR), TriggerCreateItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), TriggerCreateItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'static,
@ -103,7 +103,7 @@ pub(super) fn take_item_from_inventory<EG, TR>(
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -111,7 +111,7 @@ where
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id).await?;
let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoFloorItem(item_id))?;
let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoInventoryItem(item_id))?;
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await;
@ -127,7 +127,7 @@ pub(super) fn add_inventory_item_to_shared_floor<EG, TR>(
map_area: MapArea,
drop_position: (f32, f32, f32),
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -160,7 +160,7 @@ pub(super) fn take_meseta_from_inventory<EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -181,7 +181,7 @@ pub(super) fn add_meseta_to_inventory<EG, TR>(
character_id: CharacterEntityId,
amount: u32
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -204,7 +204,7 @@ pub(super) fn add_meseta_to_shared_floor<EG, TR>(
map_area: MapArea,
drop_position: (f32, f32)
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -233,7 +233,7 @@ pub(super) fn take_meseta_from_bank<EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -243,6 +243,7 @@ where
let mut bank = item_state.bank(&character_id).await?;
bank.remove_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ()))
})
@ -253,7 +254,7 @@ pub(super) fn add_meseta_from_bank_to_inventory<EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -274,7 +275,7 @@ pub(super) fn add_meseta_to_bank<EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -284,6 +285,7 @@ where
let mut bank = item_state.bank(&character_id).await?;
bank.add_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ()))
})
@ -296,7 +298,7 @@ pub(super) fn take_item_from_bank<EG, TR>(
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), BankItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), BankItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -316,7 +318,7 @@ where
pub(super) fn add_bank_item_to_inventory<EG, TR>(
character: &CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), BankItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -367,7 +369,7 @@ where
pub(super) fn add_inventory_item_to_bank<EG, TR>(
character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -403,7 +405,7 @@ pub(super) fn equip_inventory_item<EG, TR>(
item_id: ClientItemId,
equip_slot: u8,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -425,7 +427,7 @@ pub(super) fn unequip_inventory_item<EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -448,7 +450,7 @@ pub(super) fn sort_inventory_items<EG, TR>(
character_id: CharacterEntityId,
item_ids: Vec<ClientItemId>,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -470,7 +472,7 @@ where
pub(super) fn use_consumed_item<EG, TR>(
character: &CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), anyhow::Error>>
where
EG: EntityGateway + Clone + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -497,7 +499,7 @@ pub(super) fn feed_mag_item<EG, TR>(
character: CharacterEntity,
mag_item_id: ClientItemId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -551,7 +553,7 @@ pub(super) fn add_bought_item_to_inventory<'a, EG, TR>(
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> Pin<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
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -613,7 +615,7 @@ where
pub(super) fn sell_inventory_item<EG, TR>(
character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
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>,
func: F,
arg: T,
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
) -> Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>
where
'a: 'async_recursion,
EG: EntityGateway,
@ -653,7 +655,7 @@ where
T: Clone + Send + Sync,
F: Fn(I) -> FR + Send + Sync + Clone + 'static,
FR: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
{
let item = match input.pop() {
Some(item) => item,
@ -673,7 +675,7 @@ pub(super) fn iterate<EG, TR, I, O, T, F, FR>(
input: Vec<I>,
func: F,
) -> impl Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -682,7 +684,7 @@ where
T: Send + Clone + 'static + std::fmt::Debug,
F: Fn(I) -> FR + Send + Sync + Clone + 'static,
FR: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
T: Clone + Send + Sync,
{
move |(item_state, transaction), arg| {
@ -701,7 +703,7 @@ async fn foreach_inner<'a, EG, TR, O, T, F, I>(
state: (ItemStateProxy, TR),
mut input: I,
func: Arc<F>,
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
) -> Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>
where
'a: 'async_recursion,
EG: EntityGateway,
@ -709,7 +711,7 @@ where
O: Send,
T: Send,
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,
{
let item = match input.next() {
@ -728,14 +730,14 @@ where
pub(super) fn foreach<EG, TR, O, T, F, I>(
func: F
) -> impl Fn((ItemStateProxy, TR), I)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
O: Send,
T: Send + Clone + 'static + std::fmt::Debug,
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,
I: IntoIterator<Item = T> + Send + Sync + 'static,
I::IntoIter: Send + Sync,
@ -754,7 +756,7 @@ where
pub(super) fn insert<'a, EG, TR, T>(
element: T
) -> impl Fn((ItemStateProxy, TR), ())
-> Pin<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
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -772,12 +774,12 @@ pub(super) fn fork<EG, TR, F1, F2, T, O1, O2>(
func1: F1,
func2: F2,
) -> impl Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), (O1, O2)), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), (O1, O2)), anyhow::Error>>
where
EG: EntityGateway,
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,
O1: Send,
O2: Send,
@ -799,7 +801,7 @@ where
pub(super) fn add_item_to_inventory<EG, TR>(
character: CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -829,7 +831,7 @@ pub(super) fn record_trade<EG, TR>(
character_to: CharacterEntityId,
character_from: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), Vec<InventoryItem>)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<InventoryItem>), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<InventoryItem>), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -855,7 +857,7 @@ where
pub(super) fn assign_new_item_id<EG, TR>(
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -873,7 +875,7 @@ pub(super) fn convert_item_drop_to_floor_item<EG, TR>(
character_id: CharacterEntityId,
item_drop: ItemDrop,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>> + Clone
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -974,7 +976,7 @@ where
pub(super) fn add_item_to_local_floor<EG, TR>(
character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -993,7 +995,7 @@ where
pub(super) fn apply_modifier_to_inventory_item<EG, TR>(
modifier: ItemModifier,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -1006,7 +1008,7 @@ where
weapon.apply_modifier(&modifier);
transaction.gateway().add_weapon_modifier(entity_id, modifier).await?;
},
_ => return Err(ItemStateError::InvalidModifier)
_ => return Err(ItemStateError::InvalidModifier.into())
}
Ok(((item_state, transaction), inventory_item))
@ -1016,7 +1018,7 @@ where
pub(super) fn as_individual_item<EG, TR>(
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), IndividualItemDetail), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), IndividualItemDetail), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -1025,7 +1027,7 @@ where
Box::pin(async move {
let item = match inventory_item.item {
InventoryItemDetail::Individual(individual_item) => individual_item,
_ => return Err(ItemStateError::WrongItemType(inventory_item.item_id))
_ => return Err(ItemStateError::WrongItemType(inventory_item.item_id).into())
};
Ok(((item_state, transaction), item))
@ -1038,7 +1040,7 @@ pub(super) fn apply_item_action_packets<EG, TR>(
character_id: CharacterEntityId,
area_client: AreaClient,
) -> impl Fn((ItemStateProxy, TR), ApplyItemAction)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<SendShipPacket>), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<SendShipPacket>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
@ -1095,7 +1097,7 @@ where
pub(super) fn apply_item_action_character<EG, TR>(
character: &CharacterEntity
) -> impl Fn((ItemStateProxy, TR), Vec<ApplyItemAction>)
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,

76
src/ship/items/apply_item.rs

@ -1,14 +1,16 @@
use std::convert::TryInto;
use futures::future::join_all;
use thiserror::Error;
use anyhow::Context;
use rand::SeedableRng;
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::CharacterEntity;
use crate::entity::character::{CharacterEntity, TechLevel};
use crate::entity::item::mag::{MagCell, MagCellError};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::tech::TechniqueDisk;
use crate::entity::item::{ItemDetail, ItemEntityId};
use crate::ship::items::state::{ItemStateProxy, ItemStateError};
use crate::ship::items::state::ItemStateProxy;
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
@ -18,14 +20,12 @@ pub enum ApplyItemError {
NoCharacter,
#[error("item not equipped")]
ItemNotEquipped,
#[error("invalid item")]
#[error("could not use item invalid item")]
InvalidItem,
#[error("invalid tool")]
InvalidTool,
#[error("gateway error {0}")]
GatewayError(#[from] GatewayError),
#[error("itemstate error {0}")]
ItemStateError(Box<ItemStateError>),
#[error("magcell error {0}")]
MagCellError(#[from] MagCellError),
}
@ -38,49 +38,43 @@ pub enum ApplyItemAction {
//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;
entity_gateway.save_character(character).await?;
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;
entity_gateway.save_character(character).await.unwrap();
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;
entity_gateway.save_character(character).await.unwrap();
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;
entity_gateway.save_character(character).await.unwrap();
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;
entity_gateway.save_character(character).await.unwrap();
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;
entity_gateway.save_character(character).await.unwrap();
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;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
@ -113,7 +107,7 @@ async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy,
character: &CharacterEntity,
cell_entity_id: ItemEntityId,
mag_cell_type: MagCell)
-> Result<Vec<ApplyItemAction>, ApplyItemError>
-> Result<Vec<ApplyItemAction>, anyhow::Error>
where
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_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) {
@ -252,7 +246,7 @@ async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy,
character: &mut CharacterEntity,
entity_id: ItemEntityId,
tool: ToolType)
-> Result<Vec<ApplyItemAction>, ApplyItemError>
-> Result<Vec<ApplyItemAction>, anyhow::Error>
where
EG: EntityGateway + ?Sized,
{
@ -270,6 +264,13 @@ where
ToolType::Monofluid => Ok(Vec::new()),
ToolType::Difluid => Ok(Vec::new()),
ToolType::Trifluid => Ok(Vec::new()),
ToolType::SolAtomizer => Ok(Vec::new()),
ToolType::MoonAtomizer => Ok(Vec::new()),
ToolType::StarAtomizer => Ok(Vec::new()),
ToolType::Telepipe => Ok(Vec::new()),
ToolType::Antidote => Ok(Vec::new()),
ToolType::Antiparalysis => Ok(Vec::new()),
ToolType::TrapVision => Ok(Vec::new()),
ToolType::HuntersReport => Ok(Vec::new()),
ToolType::CellOfMag502
| ToolType::CellOfMag213
@ -299,17 +300,36 @@ where
}
ToolType::JackOLantern => jack_o_lantern(),
// TODO: rest of these
_ => Err(ApplyItemError::InvalidItem)
_ => Err(anyhow::Error::from(ApplyItemError::InvalidTool))
.with_context(|| {
format!("invalid tool {tool:?}")
})
}
}
async fn apply_tech<'a, EG>(_item_state: &mut ItemStateProxy,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
_entity_id: ItemEntityId,
tech: TechniqueDisk)
-> Result<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,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
item: InventoryItem
) -> Result<Vec<ApplyItemAction>, ApplyItemError>
) -> Result<Vec<ApplyItemAction>, anyhow::Error>
where
EG: EntityGateway + ?Sized + Clone + 'static
{
@ -317,7 +337,11 @@ where
InventoryItemDetail::Individual(individual_item) => {
match individual_item.item {
ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await,
_ => Err(ApplyItemError::InvalidItem)
ItemDetail::TechniqueDisk(tech) => apply_tech(item_state, entity_gateway, character, individual_item.entity_id, tech).await,
_ => Err(anyhow::Error::from(ApplyItemError::InvalidItem))
.with_context(|| {
format!("item {individual_item:?}")
})
}
},
InventoryItemDetail::Stacked(stacked_item) => {

20
src/ship/items/bank.rs

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

6
src/ship/items/floor.rs

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

32
src/ship/items/state.rs

@ -1,6 +1,7 @@
use std::collections::HashMap;
use async_std::sync::{Arc, RwLock, Mutex};
use futures::future::join_all;
use anyhow::Context;
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::{CharacterEntity, CharacterEntityId};
@ -21,6 +22,8 @@ pub enum ItemStateError {
NoCharacter(CharacterEntityId),
#[error("room {0} not found")]
NoRoom(RoomId),
#[error("inventory item {0} not found")]
NoInventoryItem(ClientItemId),
#[error("floor item {0} not found")]
NoFloorItem(ClientItemId),
#[error("expected {0} to be a tool")]
@ -55,7 +58,7 @@ pub enum ItemStateError {
ItemNotSellable,
#[error("could not modify item")]
InvalidModifier,
#[error("wrong item type ")]
#[error("wrong item type {0}")]
WrongItemType(ClientItemId),
}
@ -150,7 +153,7 @@ impl Default for ItemState {
}
impl ItemState {
pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result<InventoryState, ItemStateError> {
pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result<InventoryState, anyhow::Error> {
Ok(self.character_inventory
.read()
.await
@ -161,7 +164,7 @@ impl ItemState {
.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
.read()
.await
@ -174,20 +177,20 @@ 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
.write()
.await += 1;
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 bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?;
let equipped = entity_gateway.get_character_equips(&character.id).await?;
let inventory_items = inventory.items.into_iter()
.map(|item| -> Result<InventoryItem, ItemStateError> {
.map(|item| -> Result<InventoryItem, anyhow::Error> {
Ok(match item {
InventoryItemEntity::Individual(item) => {
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 inventory_state = InventoryState {
@ -259,7 +262,7 @@ impl ItemState {
.collect::<Vec<_>>())
.await
.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_state = BankState::new(character.id, BankName("".into()), Bank::new(bank_items), bank_meseta);
@ -334,7 +337,7 @@ impl ItemState {
}
}
pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), ItemStateError> {
pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), anyhow::Error> {
let local_floors = self.character_floor
.read()
.await;
@ -369,6 +372,7 @@ impl ItemState {
.map(|item| (item.clone(), FloorType::Shared))
})
.ok_or_else(|| ItemStateError::NoFloorItem(*item_id))
.with_context(|| format!("character {character_id}\nlocal floors: {local_floors:#?}\nshared floors: {shared_floors:#?}"))
}
}
@ -421,7 +425,7 @@ impl ItemStateProxy {
async fn get_or_clone<K, V>(master: &Arc<RwLock<HashMap<K, RwLock<V>>>>,
proxy: &Arc<Mutex<HashMap<K, V>>>,
key: K,
err: fn(K) -> ItemStateError) -> Result<V, ItemStateError>
err: fn(K) -> ItemStateError) -> Result<V, anyhow::Error>
where
K: Eq + std::hash::Hash + Copy,
V: Clone
@ -451,7 +455,7 @@ impl ItemStateProxy {
}
}
pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, ItemStateError> {
pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, anyhow::Error> {
get_or_clone(&self.item_state.character_inventory,
&self.proxied_state.character_inventory,
*character_id,
@ -462,7 +466,7 @@ impl ItemStateProxy {
self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory);
}
pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, ItemStateError> {
pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, anyhow::Error> {
get_or_clone(&self.item_state.character_bank,
&self.proxied_state.character_bank,
*character_id,
@ -473,7 +477,7 @@ impl ItemStateProxy {
self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank);
}
pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result<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();
Ok(FloorState {
character_id: *character_id,
@ -488,7 +492,7 @@ impl ItemStateProxy {
self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared);
}
pub async fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
pub async fn new_item_id(&mut self) -> Result<ClientItemId, anyhow::Error> {
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::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateError, IndividualItemDetail};
use crate::ship::items::state::{ItemState, ItemStateProxy, IndividualItemDetail};
use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction};
use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::floor::FloorItem;
@ -22,7 +22,7 @@ pub async fn pick_up_item<EG>(
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId)
-> Result<actions::TriggerCreateItem, ItemStateError>
-> Result<actions::TriggerCreateItem, anyhow::Error>
where
EG: EntityGateway + 'static,
EG::Transaction: Clone,
@ -46,7 +46,7 @@ pub async fn drop_item<EG>(
item_id: &ClientItemId,
map_area: MapArea,
drop_position: (f32, f32, f32))
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -70,7 +70,7 @@ pub async fn drop_partial_item<'a, EG>(
map_area: MapArea,
drop_position: (f32, f32),
amount: u32)
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -95,7 +95,7 @@ pub async fn drop_meseta<'a, EG>(
map_area: MapArea,
drop_position: (f32, f32),
amount: u32)
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -117,7 +117,7 @@ pub async fn withdraw_meseta<'a, EG>(
entity_gateway: &mut EG,
character: &CharacterEntity,
amount: u32)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -139,7 +139,7 @@ pub async fn deposit_meseta<'a, EG>(
entity_gateway: &mut EG,
character: &CharacterEntity,
amount: u32)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -162,7 +162,7 @@ pub async fn withdraw_item<'a, EG>(
character: &CharacterEntity,
item_id: &ClientItemId,
amount: u32)
-> Result<InventoryItem, ItemStateError>
-> Result<InventoryItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -187,7 +187,7 @@ pub async fn deposit_item<'a, EG> (
character: &CharacterEntity,
item_id: &ClientItemId,
amount: u32)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -209,7 +209,7 @@ pub async fn equip_item<'a, EG> (
character: &CharacterEntity,
item_id: &ClientItemId,
equip_slot: u8,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -230,7 +230,7 @@ pub async fn unequip_item<'a, EG> (
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -251,7 +251,7 @@ pub async fn sort_inventory<'a, EG> (
entity_gateway: &mut EG,
character: &CharacterEntity,
item_ids: Vec<ClientItemId>,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -274,7 +274,7 @@ pub async fn use_item<'a, EG> (
area_client: AreaClient,
item_id: &ClientItemId,
amount: u32,
) -> Result<Vec<SendShipPacket>, ItemStateError>
) -> Result<Vec<SendShipPacket>, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -303,7 +303,7 @@ pub async fn feed_mag<'a, EG> (
character: &CharacterEntity,
mag_item_id: &ClientItemId,
tool_item_id: &ClientItemId,
) -> Result<(), ItemStateError>
) -> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -327,7 +327,7 @@ pub async fn buy_shop_item<'a, EG> (
shop_item: &'a (dyn ShopItem + Send + Sync),
item_id: ClientItemId,
amount: u32,
) -> Result<InventoryItem, ItemStateError>
) -> Result<InventoryItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -353,7 +353,7 @@ pub async fn sell_item<'a, EG> (
character: &CharacterEntity,
item_id: ClientItemId,
amount: u32,
) -> Result<InventoryItem, ItemStateError>
) -> Result<InventoryItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -373,7 +373,7 @@ pub async fn trade_items<'a, EG> (
entity_gateway: &mut EG,
p1: (&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
EG: EntityGateway + 'static,
{
@ -443,7 +443,7 @@ pub async fn take_meseta<'a, EG> (
entity_gateway: &mut EG,
character_id: &CharacterEntityId,
meseta: Meseta)
-> Result<(), ItemStateError>
-> Result<(), anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -464,7 +464,7 @@ pub async fn enemy_drops_item<'a, EG> (
entity_gateway: &mut EG,
character_id: CharacterEntityId,
item_drop: ItemDrop)
-> Result<FloorItem, ItemStateError>
-> Result<FloorItem, anyhow::Error>
where
EG: EntityGateway + 'static,
{
@ -488,7 +488,7 @@ pub async fn apply_modifier<'a, EG> (
character: &CharacterEntity,
item_id: ClientItemId,
modifier: ItemModifier)
-> Result<IndividualItemDetail, ItemStateError>
-> Result<IndividualItemDetail, anyhow::Error>
where
EG: EntityGateway + 'static,
{

26
src/ship/location.rs

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

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)
}
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
}
}
}

136
src/ship/map/maps.rs

@ -8,12 +8,13 @@ use thiserror::Error;
use crate::ship::ship::ShipEvent;
use crate::ship::monster::MonsterType;
use crate::ship::room::{Episode, RoomMode};
use crate::ship::room::{Episode, RoomMode, PlayerMode};
// TODO: don't use *
use crate::ship::map::*;
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
let mut object_data = Vec::new();
@ -35,7 +36,7 @@ fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) ->
enemy
.map_or(vec![None], |monster| {
let mut monsters = vec![Some(monster)];
match monster.monster {
MonsterType::Monest => {
for _ in 0..30 {
@ -172,25 +173,10 @@ fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<
}
#[derive(Error, Debug)]
#[error("")]
pub enum MapsError {
InvalidMonsterId(usize),
InvalidObjectId(usize),
}
#[derive(Debug)]
pub struct Maps {
map_variants: Vec<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) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
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),
MapVariant::new(MapArea::Forest1, MapVariantMode::Online),
MapVariant::new(MapArea::Forest2, MapVariantMode::Online),
MapVariant::new(MapArea::Caves1, MapVariantMode::Online),
@ -205,10 +191,10 @@ impl Maps {
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online),
MapVariant::new(MapArea::VolOpt, MapVariantMode::Online),
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online),
]
},
(Episode::One, 1) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
]
},
(Episode::One, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
MapVariant::new(MapArea::Forest1, MapVariantMode::Offline),
MapVariant::new(MapArea::Forest2, MapVariantMode::Offline),
MapVariant::new(MapArea::Caves1, MapVariantMode::Offline),
@ -223,10 +209,10 @@ impl Maps {
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Offline),
MapVariant::new(MapArea::VolOpt, MapVariantMode::Offline),
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline),
]
},
(Episode::Two, 0) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
]
},
(Episode::Two, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online),
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online),
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Online),
@ -242,10 +228,10 @@ impl Maps {
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Online),
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Online),
MapVariant::new(MapArea::GolDragon, MapVariantMode::Online),
]
},
(Episode::Two, 1) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
]
},
(Episode::Two, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline),
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline),
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Offline),
@ -261,10 +247,10 @@ impl Maps {
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Offline),
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Offline),
MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline),
]
},
(Episode::Four, _) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
]
},
(Episode::Four, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
MapVariant::new(MapArea::CraterEast, MapVariantMode::Online),
MapVariant::new(MapArea::CraterWest, MapVariantMode::Online),
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Online),
@ -274,23 +260,44 @@ impl Maps {
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Online),
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Online),
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Online),
]
},
_ => unreachable!()
};
]
},
(Episode::Four, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterEast, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterWest, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterNorth, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterInterior, MapVariantMode::Offline),
MapVariant::new(MapArea::SubDesert1, MapVariantMode::Offline),
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Offline),
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Offline),
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Offline),
]
},
}
}
#[derive(Error, Debug)]
#[error("")]
pub enum MapsError {
InvalidMonsterId(usize),
InvalidObjectId(usize),
}
#[derive(Debug)]
pub struct Maps {
map_variants: Vec<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 {
enemy_data: map_variants.iter()
.flat_map(|map_variant| {
enemy_data_from_map_data(map_variant, &room_mode.episode())
})
.map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
.collect(),
object_data: map_variants.iter()
.flat_map(|map_variant| {
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
}).collect(),
map_variants,
enemy_data,
object_data,
}
}
@ -322,7 +329,7 @@ impl Maps {
{
self.enemy_data = enemies
.into_iter()
.map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
.map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event)))
.collect();
self.object_data = objects;
}
@ -351,13 +358,20 @@ impl Maps {
}
}
fn apply_rare_enemy(enemy: Option<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 enemy;
mod object;
mod variant;
mod maps;
pub mod object;
pub mod variant;
pub mod maps;
// TODO: don't just forward everything to the module scope
pub use area::*;

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

@ -1,6 +1,6 @@
use libpso::packet::ship::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{ShipError, Clients, ShipEvent};
use crate::ship::ship::{Clients, ShipEvent};
use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError};
use crate::ship::packet::builder::{player_info};
use crate::ship::items::state::ItemState;
@ -13,7 +13,7 @@ pub async fn join_lobby(id: ClientId,
clients: &Clients,
item_state: &ItemState,
event: ShipEvent)
-> Result<JoinLobby, ShipError> {
-> Result<JoinLobby, anyhow::Error> {
let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
let playerinfo = join_all(
@ -28,9 +28,8 @@ pub async fn join_lobby(id: ClientId,
}}))
.await
.into_iter()
.collect::<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 {
client.block as u16
})).await?;
@ -54,7 +53,7 @@ pub async fn add_to_lobby(id: ClientId,
clients: &Clients,
item_state: &ItemState,
event: ShipEvent)
-> Result<AddToLobby, ShipError> {
-> Result<AddToLobby, anyhow::Error> {
let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
clients.with(id, |client| {
@ -77,7 +76,7 @@ pub async fn add_to_lobby(id: ClientId,
pub async fn remove_from_lobby(id: ClientId,
client_location: &ClientLocation)
-> Result<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_leader_index = 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: &RoomState,
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 players = futures::stream::iter(all_clients.iter())
.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()))?;
clients.with(c.client, |client| Box::pin(async move {
acc.map(|mut a| {
@ -40,14 +39,14 @@ pub async fn join_room(id: ClientId,
leader: leader.local_client.id(),
one: 1,
difficulty: room.mode.difficulty().into(),
battle: room.mode.battle(),
battle: room.mode.battle() as u8,
event: event.into(),
section: room.section_id.into(),
challenge: room.mode.challenge(),
challenge: room.mode.challenge() as u8,
random_seed: room.random_seed,
episode: room.mode.episode().into(),
one2: 1,
single_player: room.mode.single_player(),
single_player: room.mode.player_mode().value(),
unknown: 0,
})
}
@ -59,7 +58,7 @@ pub async fn add_to_room(_id: ClientId,
leader: &AreaClient,
item_state: &ItemState,
event: ShipEvent)
-> Result<AddToRoom, ShipError> {
-> Result<AddToRoom, anyhow::Error> {
let inventory = item_state.get_character_inventory(&client.character).await?;
Ok(AddToRoom {
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>>,
ship_name: &str,
num_blocks: usize)
-> Result<Vec<SendShipPacket>, ShipError>
-> Result<Vec<SendShipPacket>, anyhow::Error>
where
EG: EntityGateway,
{

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

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

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

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

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

@ -16,7 +16,7 @@ pub async fn block_selected(id: ClientId,
pkt: MenuSelect,
clients: &Clients,
item_state: &ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
clients.with_mut(id, |client| {
let item_state = item_state.clone();
Box::pin(async move {
@ -34,8 +34,8 @@ pub async fn block_selected(id: ClientId,
.meseta(inventory.meseta)
.inventory(&inventory)
.bank(&bank)
.keyboard_config(&client.character.keyboard_config.as_bytes())
.gamepad_config(&client.character.gamepad_config.as_bytes())
.keyboard_config(&client.settings.settings.keyboard_config)
.gamepad_config(&client.settings.settings.gamepad_config)
.symbol_chat(&client.settings.settings.symbol_chats)
.tech_menu(&client.character.tech_menu.as_bytes())
.option_flags(client.character.option_flags)
@ -57,7 +57,7 @@ pub async fn send_player_to_lobby(id: ClientId,
clients: &Clients,
item_state: &ItemState,
event: ShipEvent)
-> Result<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 join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, event).await?;
let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, event).await?;
@ -77,7 +77,7 @@ pub async fn change_lobby<EG>(id: ClientId,
rooms: &Rooms,
entity_gateway: &mut EG,
event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -134,7 +134,7 @@ where
pub async fn remove_from_lobby(id: ClientId,
client_location: &mut ClientLocation)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let area_client = client_location.get_local_client(id).await?;
let neighbors = client_location.get_client_neighbors(id).await?;
let leader = client_location.get_leader_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
@ -150,7 +150,7 @@ pub async fn get_room_tab_info(id: ClientId,
pkt: MenuDetail,
client_location: &mut ClientLocation,
clients: &Clients)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = RoomId(pkt.item as usize);
let clients_in_room = client_location.get_clients_in_room(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let room_info = if clients_in_room.is_empty() {
@ -169,7 +169,7 @@ pub async fn get_room_tab_info(id: ClientId,
})).await
})).await
.into_iter()
.collect::<Result<Vec<_>, ShipError>>()?
.collect::<Result<Vec<_>, anyhow::Error>>()?
.join("\n")
};
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,
clients: &Clients,
rooms: &Rooms)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -29,7 +29,7 @@ where
let enemy_exp = rooms.with(room_id, |room| Box::pin(async move {
let monster = room.maps.enemy_by_id(enemy_id)?;
let monster_stats = room.monster_stats.get(&monster.monster).ok_or_else(|| ShipError::UnknownMonster(monster.monster))?;
Ok::<_, ShipError>(monster_stats.exp)
Ok::<_, anyhow::Error>(monster_stats.exp)
})).await??;
let exp_gain = if request_exp.last_hitter == 1 {
@ -59,7 +59,7 @@ where
let (_, before_stats) = LEVEL_TABLE.get_stats_from_exp(char_class, exp);
let (after_level, after_stats) = LEVEL_TABLE.get_stats_from_exp(char_class, exp + exp_gain);
let level_up_pkt = builder::message::character_leveled_up(area_client, after_level, before_stats, after_stats);
let level_up_pkt = builder::message::character_leveled_up(area_client, after_level-1, before_stats, after_stats);
exp_pkts.extend(clients_in_area.into_iter()
.map(move |c| {
(c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerLevelUp(level_up_pkt.clone()))))
@ -83,7 +83,7 @@ pub async fn player_drop_item<EG>(id: ClientId,
clients: &Clients,
rooms: &Rooms,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -112,7 +112,7 @@ pub async fn drop_coordinates(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
rooms: &Rooms)
-> Result<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 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,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -150,7 +150,7 @@ where
})).await?;
if let Some(drop_location) = drop_location {
if drop_location.item_id.0 != no_longer_has_item.item_id {
return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id));
return Err(ShipError::DropInvalidItemId(no_longer_has_item.item_id).into());
}
if no_longer_has_item.item_id == 0xFFFFFFFF {
@ -218,7 +218,7 @@ where
.collect())
}
else {
Err(ShipError::InvalidItem(ClientItemId(no_longer_has_item.item_id)))
Err(ShipError::InvalidItem(ClientItemId(no_longer_has_item.item_id)).into())
}
}
@ -227,7 +227,7 @@ pub async fn update_player_position(id: ClientId,
clients: &Clients,
client_location: &ClientLocation,
rooms: &Rooms)
-> Result<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() }) {
let msg = message.msg.clone();
clients.with_mut(id, |client| {
@ -291,7 +291,7 @@ pub async fn update_player_position(id: ClientId,
}
_ => {},
}
Ok::<_, ShipError>(())
Ok::<_, anyhow::Error>(())
})}).await??;
}
Ok(client_location.get_client_neighbors(id).await?.into_iter()
@ -307,7 +307,7 @@ pub async fn charge_attack<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -333,7 +333,7 @@ pub async fn player_uses_item<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -364,7 +364,7 @@ pub async fn player_used_medical_center<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -390,7 +390,7 @@ pub async fn player_feed_mag<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -415,7 +415,7 @@ pub async fn player_equips_item<EG>(id: ClientId,
entity_gateway: &mut EG,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -440,7 +440,7 @@ pub async fn player_unequips_item<EG>(id: ClientId,
entity_gateway: &mut EG,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -458,7 +458,7 @@ pub async fn player_sorts_items<EG>(id: ClientId,
entity_gateway: &mut EG,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -488,7 +488,7 @@ pub async fn player_sells_item<EG> (id: ClientId,
entity_gateway: &mut EG,
clients: &Clients,
item_state: &mut ItemState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
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::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent};
use crate::ship::room::Rooms;
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::map::enemy::RareMonsterAppearTable;
use crate::ship::location::{ClientLocation};
use crate::ship::packet::builder::quest;
use libpso::util::array_to_utf8;
@ -13,7 +14,7 @@ enum QuestFileType {
Dat
}
fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType), ShipError> {
fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType), anyhow::Error> {
let filename = array_to_utf8(*filename_bytes).map_err(|_| ShipError::InvalidQuestFilename("NOT UTF8".to_string()))?;
let (filename, suffix) = {
let mut s = filename.splitn(2, '.');
@ -24,7 +25,7 @@ fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType)
let datatype = match suffix {
"bin" => QuestFileType::Bin,
"dat" => QuestFileType::Dat,
_ => return Err(ShipError::InvalidQuestFilename(filename.to_owned()))
_ => Err(ShipError::InvalidQuestFilename(filename.to_owned()))?
};
let (category, quest) = {
@ -41,8 +42,8 @@ pub async fn send_quest_category_list(id: ClientId,
rql: RequestQuestList,
client_location: &ClientLocation,
rooms: &Rooms)
-> Result<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();
rooms.with_mut(room_id, |room| Box::pin(async move {
let qcl = quest::quest_category_list(&room.quests[rql.flag.clamp(0, (room.quests.len() - 1) as u32) as usize]);
@ -55,8 +56,8 @@ pub async fn select_quest_category(id: ClientId,
menuselect: MenuSelect,
client_location: &ClientLocation,
rooms: &Rooms)
-> Result<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 {
let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(menuselect.item as usize)
@ -72,8 +73,8 @@ pub async fn quest_detail(id: ClientId,
questdetailrequest: QuestDetailRequest,
client_location: &ClientLocation,
rooms: &Rooms)
-> Result<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 {
let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(questdetailrequest.category as usize)
@ -96,8 +97,8 @@ pub async fn player_chose_quest(id: ClientId,
client_location: &ClientLocation,
rooms: &Rooms,
event: ShipEvent)
-> Result<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 questmenuselect = questmenuselect.clone();
@ -115,14 +116,14 @@ pub async fn player_chose_quest(id: ClientId,
.ok_or_else(|| ShipError::InvalidQuest(questmenuselect.quest))?
.clone();
let rare_monster_drops = room.rare_monster_table.clone();
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_drops, event);
let rare_monster_table = RareMonsterAppearTable::new(room.mode.episode());
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_table, event);
room.map_areas = quest.map_areas.clone();
let bin = quest::quest_header(&questmenuselect, &quest.bin_blob, "bin");
let dat = quest::quest_header(&questmenuselect, &quest.dat_blob, "dat");
let area_clients = client_location.get_all_clients_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let area_clients = client_location.get_all_clients_by_client(id).await?;
for client in &area_clients {
clients.with_mut(client.client, |client| Box::pin(async move {
client.done_loading_quest = false;
@ -141,9 +142,9 @@ pub async fn quest_file_request(id: ClientId,
quest_file_request: QuestFileRequest,
client_location: &ClientLocation,
rooms: &mut Rooms)
-> Result<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();
rooms.with(room_id, |room| Box::pin(async move {
@ -175,8 +176,8 @@ pub async fn quest_chunk_ack(id: ClientId,
quest_chunk_ack: QuestChunkAck,
client_location: &ClientLocation,
rooms: &Rooms)
-> Result<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();
rooms.with(room_id, |room| Box::pin(async move {
@ -211,11 +212,11 @@ pub async fn quest_chunk_ack(id: ClientId,
pub async fn done_loading_quest(id: ClientId,
clients: &Clients,
client_location: &ClientLocation)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
clients.with_mut(id, |client| Box::pin(async move {
client.done_loading_quest = true;
})).await?;
let area_clients = client_location.get_all_clients_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let area_clients = client_location.get_all_clients_by_client(id).await?;
let all_loaded = area_clients.iter()
.map(|client|

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

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

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

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

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

@ -3,7 +3,7 @@ use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients};
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::location::{ClientLocation};
use crate::ship::items::ClientItemId;
use crate::ship::items::state::{ItemState, ItemStateError};
use crate::ship::items::inventory::InventoryItemDetail;
@ -57,9 +57,9 @@ async fn do_trade_action<F>(id: ClientId,
this: &mut ClientTradeState,
other: &mut ClientTradeState,
action: F)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> Result<(), ShipError>,
F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> Result<(), anyhow::Error>,
{
Ok(match action(this, other) {
Ok(_) => {
@ -92,7 +92,7 @@ pub async fn trade_request(id: ClientId,
clients: &Clients,
item_state: &mut ItemState,
trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{
let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet
match trade_request.trade {
@ -293,12 +293,12 @@ async fn inner_items_to_trade(id: ClientId,
clients: &Clients,
item_state: &mut ItemState,
trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{
let pkts = trades
.with(&id, |mut this, other| async move {
if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) {
return Err(ShipError::from(TradeError::MismatchedStatus))
return Err(anyhow::Error::from(ShipError::from(TradeError::MismatchedStatus)))
}
let other_client = other.client();
let (this_inventory, other_inventory) = clients.with(this.client(), |client| {
@ -311,7 +311,7 @@ async fn inner_items_to_trade(id: ClientId,
Box::pin(async move {
item_state.get_character_inventory(&client.character).await
})}).await??;
Ok::<_, ShipError>((this, other_inventory))
Ok::<_, anyhow::Error>((this, other_inventory))
})}).await??;
if items_to_trade.count as usize != (this.items.len() + usize::from(this.meseta != 0)) {
@ -383,7 +383,7 @@ async fn inner_items_to_trade(id: ClientId,
}
}
})
.collect::<Result<Vec<_>, ShipError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
this.status = TradeStatus::ItemsChecked;
if this.status == TradeStatus::ItemsChecked && other.status == TradeStatus::ItemsChecked {
@ -418,7 +418,7 @@ pub async fn items_to_trade(id: ClientId,
clients: &Clients,
item_state: &mut ItemState,
trades: &mut TradeState)
-> Result<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;
match t {
@ -443,7 +443,7 @@ async fn trade_confirmed_inner<EG>(id: ClientId,
clients: &Clients,
item_state: &mut ItemState,
trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
@ -460,14 +460,14 @@ where
.with(&id, |mut this, other| {
async move {
if status_is_not(&this.status, &[TradeStatus::ItemsChecked]) || status_is_not(&other.status, &[TradeStatus::ItemsChecked, TradeStatus::TradeComplete]) {
return Err(ShipError::TradeError(TradeError::MismatchedStatus))
return Err(anyhow::Error::from(ShipError::TradeError(TradeError::MismatchedStatus)))
}
this.status = TradeStatus::TradeComplete;
if this.status == TradeStatus::TradeComplete && other.status == TradeStatus::TradeComplete {
let this_local_client = client_location.get_local_client(this.client()).await?;
let other_local_client = client_location.get_local_client(other.client()).await?;
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let room_id = client_location.get_room(id).await?;
Ok(TradeReady::BothPlayers(room_id,
(this_local_client, /*this_client, */this.clone()),
@ -584,7 +584,7 @@ pub async fn trade_confirmed<EG>(id: ClientId,
clients: &Clients,
item_state: &mut ItemState,
trades: &mut TradeState)
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
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::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
use crate::ship::map::area::MapAreaLookup;
use crate::ship::map::enemy::RareMonsterAppearTable;
use crate::ship::quests;
use crate::ship::ship::{ShipError, ShipEvent};
use crate::ship::location::{MAX_ROOMS, RoomId};
#[derive(Clone)]
pub struct Rooms([Arc<RwLock<Option<RoomState>>>; MAX_ROOMS]);
@ -29,7 +27,7 @@ impl Default for Rooms {
}
impl Rooms {
pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), ShipError> {
pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> {
*self.0
.get(room_id.0)
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
@ -58,7 +56,7 @@ impl Rooms {
}
}
pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, ShipError>
pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a
@ -72,11 +70,11 @@ impl Rooms {
Ok(func(room).await)
}
else {
Err(ShipError::InvalidRoom(room_id.0 as u32))
Err(ShipError::InvalidRoom(room_id.0 as u32).into())
}
}
pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, ShipError>
pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a
@ -91,7 +89,7 @@ impl Rooms {
Ok(func(room).await)
}
else {
Err(ShipError::InvalidRoom(room_id.0 as u32))
Err(ShipError::InvalidRoom(room_id.0 as u32).into())
}
}
@ -135,6 +133,21 @@ pub enum Episode {
Four,
}
#[derive(Debug, Copy, Clone)]
pub enum PlayerMode{
Single,
Multi,
}
impl PlayerMode {
pub fn value(&self) -> u8 {
match self {
PlayerMode::Single => 1,
PlayerMode::Multi => 0,
}
}
}
impl TryFrom<u8> for Episode {
type Error = RoomCreationError;
@ -245,24 +258,18 @@ impl RoomMode {
}
}
pub fn battle(&self) -> u8 {
match self {
RoomMode::Battle {..} => 1,
_ => 0,
}
pub fn battle(&self) -> bool {
matches!(self, RoomMode::Battle {..})
}
pub fn challenge(&self) -> u8 {
match self {
RoomMode::Challenge {..} => 1,
_ => 0,
}
pub fn challenge(&self) -> bool {
matches!(self, RoomMode::Challenge {..})
}
pub fn single_player(&self) -> u8 {
pub fn player_mode(&self) -> PlayerMode {
match self {
RoomMode::Single {..} => 1,
_ => 0,
RoomMode::Single {..} => PlayerMode::Single,
_ => PlayerMode::Multi,
}
}
}
@ -295,13 +302,12 @@ pub struct RoomState {
pub name: String,
pub password: [u16; 16],
pub maps: Maps,
pub drop_table: Box<DropTable<rand_chacha::ChaCha20Rng>>,
pub drop_table: Box<DropTable>,
pub section_id: SectionID,
pub random_seed: u32,
pub bursting: bool,
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
pub map_areas: MapAreaLookup,
pub rare_monster_table: Box<RareMonsterAppearTable>,
pub quest_group: QuestCategoryType,
pub quests: Vec<quests::QuestList>,
// items on ground
@ -343,7 +349,12 @@ impl RoomState {
self.quest_group = QuestCategoryType::from(group);
}
pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID, event: ShipEvent) -> Result<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 {
return Err(RoomCreationError::InvalidMode)
}
@ -372,7 +383,6 @@ impl RoomState {
}
};
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
// push the usual set of quests for the selected mode
let mut qpath = PathBuf::from("data/quests/bb");
@ -401,18 +411,15 @@ impl RoomState {
room_quests.push(quest_list);
}
Ok(RoomState {
monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?),
mode: room_mode,
random_seed: rand::thread_rng().gen(),
rare_monster_table: Box::new(rare_monster_table.clone()),
name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(),
password: create_room.password,
maps: Maps::new(room_mode, &rare_monster_table, event),
maps: map_builder(room_mode, event),
section_id,
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
drop_table: Box::new(drop_table_builder(room_mode.episode(), room_mode.difficulty(), section_id)),
bursting: false,
map_areas: MapAreaLookup::new(&room_mode.episode()),
quest_group: QuestCategoryType::Standard,

44
src/ship/ship.rs

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

3
tests/common.rs

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

29
tests/test_character.rs

@ -31,26 +31,6 @@ async fn test_save_options() {
assert!(char.option_flags == 12345);
}
#[async_std::test]
async fn test_default3_keyboard_mappings() {
/*
check if keyboard is set to default3 when specified. this will only occur for things like creating characters from the web page.
normal client behaviour will simply use default1 when creating a character.
gamepad only has 1 default config
*/
let mut entity_gateway = InMemoryGateway::default();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 3).await;
assert!(char1.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG3);
}
#[async_std::test]
async fn test_invalid_keyboard_preset_value() {
// check if keyboard_config self-corrects to DEFAULT1 if an invalid value (>4) is given
let mut entity_gateway = InMemoryGateway::default();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 10).await;
assert!(char1.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG1);
}
#[async_std::test]
async fn test_change_keyboard_mappings() {
let mut entity_gateway = InMemoryGateway::default();
@ -63,7 +43,8 @@ async fn test_change_keyboard_mappings() {
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
assert!(char1.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG2);
let settings = entity_gateway.get_user_settings_by_user(&user1).await.unwrap();
assert!(settings.settings.keyboard_config == DEFAULT_KEYBOARD_CONFIG1);
// update from default2 to default4
// the client simply sends the full 364 bytes...
@ -95,8 +76,6 @@ async fn test_change_keyboard_mappings() {
],
})).await.unwrap();
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert!(char.keyboard_config.as_bytes() == DEFAULT_KEYBOARD_CONFIG4);
let settings = entity_gateway.get_user_settings_by_user(&user1).await.unwrap();
assert!(settings.settings.keyboard_config == DEFAULT_KEYBOARD_CONFIG4);
}

133
tests/test_exp_gain.rs

@ -4,6 +4,10 @@ use elseware::common::leveltable::CharacterLevelTable;
use elseware::ship::ship::{ShipServerState, SendShipPacket, RecvShipPacket};
use elseware::ship::monster::MonsterType;
use elseware::ship::location::RoomId;
use elseware::ship::map::variant::{MapVariant, MapVariantMode};
use elseware::ship::map::maps::Maps;
use elseware::ship::map::area::MapArea;
use elseware::ship::map::enemy::MapEnemy;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
@ -19,36 +23,30 @@ async fn test_character_gains_exp() {
let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
.gateway(entity_gateway.clone())
.map_builder(Box::new(|_room_mode, _event| {
Maps::new(
vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)],
vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))],
Vec::new(),
)
}))
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
let (enemy_id, exp) = {
//let room = ship.blocks.0[0].rooms.get(RoomId(0)).as_ref().unwrap();
ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move {
let (enemy_id, map_enemy) = (0..).filter_map(|i| {
room.maps.enemy_by_id(i).map(|enemy| {
(i, enemy)
}).ok()
}).next().unwrap();
let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap();
(enemy_id, map_enemy_stats.exp)
})).await.unwrap()
};
ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0,
target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0,
client_id: 0,
unused: 0,
last_hitter: 1,
})))).await.unwrap();
ship.clients.with(ClientId(1), |client| Box::pin(async move {
assert!(exp == client.character.exp);
assert!(13 == client.character.exp);
})).await.unwrap();
}
@ -61,30 +59,29 @@ async fn test_character_levels_up() {
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
.gateway(entity_gateway.clone())
.map_builder(Box::new(|_room_mode, _event| {
Maps::new(
vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)],
vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))],
Vec::new(),
)
}))
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
let enemy_id = ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move {
(0..).filter_map(|i| {
room.maps.enemy_by_id(i).map(|_| {
i
}).ok()
}).next().unwrap()
})).await.unwrap();
let levelup_pkt = ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0 as u8,
target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0 as u16,
client_id: 0,
unused: 0,
last_hitter: 1,
})))).await.unwrap();
assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 2, ..})})));
assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 1, ..})})));
let leveltable = CharacterLevelTable::default();
ship.clients.with(ClientId(1), |client| Box::pin(async move {
@ -99,42 +96,32 @@ async fn test_character_levels_up_multiple_times() {
let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
.gateway(entity_gateway.clone())
.map_builder(Box::new(|_room_mode, _event| {
Maps::new(
vec![MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online)],
vec![Some(MapEnemy::new(MonsterType::DarkFalz2, MapArea::DarkFalz))],
Vec::new(),
)
}))
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
let (enemy_id, exp) = {
ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move {
let (enemy_id, map_enemy) = (0..).filter_map(|i| {
room.maps.enemy_by_id(i).ok().and_then(|enemy| {
if enemy.monster == MonsterType::DarkFalz2 {
Some((i, enemy))
}
else {
None
}
})
}).next().unwrap();
let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap();
(enemy_id, map_enemy_stats.exp)
})).await.unwrap()
};
let levelup_pkt = ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0 as u8,
target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0 as u16,
client_id: 0,
unused: 0,
last_hitter: 1,
})))).await.unwrap();
assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 8, ..})})));
assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 7, ..})})));
ship.clients.with(ClientId(1), |client| Box::pin(async move {
assert!(exp == client.character.exp);
assert!(3000 == client.character.exp);
})).await.unwrap();
}
@ -146,8 +133,15 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() {
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a", 1).await;
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
.gateway(entity_gateway.clone())
.map_builder(Box::new(|_room_mode, _event| {
Maps::new(
vec![MapVariant::new(MapArea::Forest2, MapVariantMode::Online)],
vec![Some(MapEnemy::new(MonsterType::Hildebear, MapArea::Forest2))],
Vec::new(),
)
}))
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
log_in_char(&mut ship, ClientId(2), "a2", "a").await;
@ -157,43 +151,28 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() {
create_room(&mut ship, ClientId(1), "room", "").await;
join_room(&mut ship, ClientId(2), 0).await;
let (enemy_id, exp) = ship.blocks.0[0].rooms.with(RoomId(0), |room| Box::pin(async move {
let (enemy_id, map_enemy) = (0..).filter_map(|i| {
room.maps.enemy_by_id(i).ok().and_then(|enemy| {
if enemy.monster == MonsterType::DarkFalz2 {
Some((i, enemy))
}
else {
None
}
})
}).next().unwrap();
let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap();
(enemy_id, map_enemy_stats.exp)
})).await.unwrap();
ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0,
target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0,
client_id: 0,
unused: 0,
last_hitter: 1,
})))).await.unwrap();
ship.handle(ClientId(2), RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp {
client: enemy_id as u8,
client: 0,
target: 16,
enemy_id: enemy_id as u16,
enemy_id: 0,
client_id: 0,
unused: 0,
last_hitter: 0,
})))).await.unwrap();
ship.clients.with(ClientId(1), |client| Box::pin(async move {
assert!(client.character.exp == exp);
assert_eq!(client.character.exp, 13);
})).await.unwrap();
ship.clients.with(ClientId(2), |client| Box::pin(async move {
assert!(client.character.exp == (exp as f32 * 0.8) as u32);
assert_eq!(client.character.exp, 10);
})).await.unwrap();
}

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::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::entity::character::TechLevel;
//use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType};
use libpso::packet::ship::*;
@ -304,6 +305,92 @@ async fn test_jackolantern() {
}
}
#[async_std::test]
async fn test_use_barta_1() {
let mut entity_gateway = InMemoryGateway::default();
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;
/*
let mut p1_inv = Vec::new();
for tool in vec![item::tool::ToolType::PowerMaterial, item::tool::ToolType::].into_iter() {
let mut item = Vec::new();
for _ in 0..5usize {
item.push(entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Tool(
item::tool::Tool {
tool: tool
}
),
}).await.unwrap());
}
p1_inv.push(item::InventoryItemEntity::Stacked(item));
}*/
let inv = vec![
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::TechniqueDisk(
item::tech::TechniqueDisk {
tech: item::tech::Technique::Foie,
level: 3,
}
)
}
).await.unwrap(),
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::TechniqueDisk(
item::tech::TechniqueDisk {
tech: item::tech::Technique::Barta,
level: 4,
}
)
}
).await.unwrap(),
entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::TechniqueDisk(
item::tech::TechniqueDisk {
tech: item::tech::Technique::Zonde,
level: 5,
}
)
}
).await.unwrap()
];
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(inv)).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
client: 0,
target: 0,
item_id: 0x10000,
})))).await.unwrap();
ship.handle(ClientId(1), RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem {
client: 0,
target: 0,
item_id: 0x10002,
})))).await.unwrap();
let characters = entity_gateway.get_characters_by_user(&user1).await.unwrap();
let char = characters[0].as_ref().unwrap();
assert_eq!(char.techs.techs.len(), 2);
assert_eq!(char.techs.techs.get(&item::tech::Technique::Foie).unwrap(), &TechLevel(3));
assert_eq!(char.techs.techs.get(&item::tech::Technique::Zonde).unwrap(), &TechLevel(5));
assert!(char.techs.techs.get(&item::tech::Technique::Barta).is_none());
}
// TODO: tests for ALL ITEMS WOW
/*

2
tests/test_rooms.rs

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

3
tests/test_shops.rs

@ -1143,7 +1143,8 @@ async fn test_player_cant_sell_if_meseta_would_go_over_max() {
amount: 1,
})))).await.err().unwrap();
//assert_eq!(ack, ShipError::ItemStateError(ItemStateError::FullOfMeseta));
assert!(matches!(ack.downcast::<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();
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)
})))).await.err().unwrap();
assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::TradeError(TradeError::ClientAlreadyInTrade)));
assert!(matches!(ack.downcast::<TradeError>().unwrap(), TradeError::ClientAlreadyInTrade));
}
#[async_std::test]
@ -3187,14 +3187,14 @@ async fn test_client_tries_trading_with_client_already_trading() {
target: 0,
trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0)
})))).await.err().unwrap();
assert!(matches!(ack.downcast::<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 {
client: 2,
target: 0,
trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 1)
})))).await.err().unwrap();
assert!(matches!(ack.downcast::<ShipError>().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade)));
assert!(matches!(ack.downcast::<TradeError>().unwrap(), TradeError::OtherAlreadyInTrade));
}
#[async_std::test]

Loading…
Cancel
Save