Compare commits

..

80 Commits

Author SHA1 Message Date
7ac68d13bb actually add and use AddKill modifier and remove dead code
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2022-07-25 23:47:32 +00:00
6a7d6ffdf2 remove 3rd attr for killcounter weapons and fix tests 2022-07-25 23:42:32 +00:00
a5a21d0968 killcounters, redbox mode, failing unseal test, and random debug strings 2022-07-25 23:37:15 +00:00
3eeee323a9 andy vs. clippy round 5 2022-07-25 23:28:40 +00:00
e39cf3eecc add kill counters to units (limiter) 2022-07-25 23:27:04 +00:00
b0eb494660 add kill counter field to weapons and increment it when a client kills a monster 2022-07-25 23:23:26 +00:00
13017084ee put proper error strings on this struct 2022-07-25 22:10:57 +00:00
cb00ce67c6 split actions into actions+tasks 2022-07-25 22:10:57 +00:00
7d5c4e0165 move bank code out of state 2022-07-25 22:10:56 +00:00
317f236f7c move floor code out of state 2022-07-25 22:10:56 +00:00
bd692b8449 move inventory code out of state 2022-07-25 22:10:56 +00:00
bc104e4408 move ItemStateAction to its own file 2022-07-25 22:10:56 +00:00
d8842d6de7 missed a thing 2022-07-25 22:10:55 +00:00
01f575c0d9 appease clippy, the tyrant 2022-07-25 22:10:55 +00:00
098b1a79e7 remove old item manager code 2022-07-25 22:10:53 +00:00
d1d48cae3b refactor tekking 2022-07-25 22:06:44 +00:00
ce41c7423e add postgres trade stuff 2022-07-25 22:06:44 +00:00
8655082e9e enemy drop refactor 2022-07-25 22:06:43 +00:00
07fd5955a8 fix some meseta-taking things 2022-07-25 22:06:43 +00:00
547b5255eb trade refactor 2022-07-25 22:06:41 +00:00
fad0ccc856 selling items 2022-07-25 22:06:23 +00:00
ad7572b726 buy items from shop 2022-07-25 22:05:54 +00:00
73a93c0957 magstuff 2022-07-25 22:03:12 +00:00
e61901d0ea clean these functions up a bit 2022-07-25 22:03:12 +00:00
193fcf470e bare minimum item usage stuff 2022-07-25 22:03:12 +00:00
4a077c30f1 fix item pickup tests 2022-07-25 22:03:12 +00:00
3f57d244ae spacing 2022-07-25 22:03:12 +00:00
938668074b sort inventory 2022-07-25 22:03:12 +00:00
883370db39 I am dumb 2022-07-25 22:03:12 +00:00
e1e898705e equipping! 2022-07-25 22:03:12 +00:00
8e105b31f2 spacing 2022-07-25 22:03:12 +00:00
3e907c6066 bank itemstate stuff 2022-07-25 22:03:12 +00:00
9bef112d34 make inmemorytransaction actually work 2022-07-25 22:03:12 +00:00
2aa8dae9b2 entitygateway stuff should take &BankName not BankName 2022-07-25 22:03:12 +00:00
8057b82dc5 the start of a long process of replacing ItemManager 2022-07-25 22:03:11 +00:00
d6e368f055 wtf happened here 2022-07-25 22:03:11 +00:00
c7fdd5e58b move partial drops over to item_state 2022-07-25 22:03:11 +00:00
573e1ff60b I was wondering why nightly a week ago was letting me get away with this 2022-07-25 22:03:11 +00:00
8b51f5e45d reorder function 2022-07-25 22:03:11 +00:00
007d73e07f missed fixing these 2022-07-25 22:03:11 +00:00
29f6c9bcac move drop item over to item_state 2022-07-25 22:03:11 +00:00
9d8eecfd5d add/move stuff around for itemstate 2022-07-25 22:03:11 +00:00
867aa3537e add item_state to ShipState 2022-07-25 22:03:11 +00:00
726a6a92a2 add character_id to ItemNote::PlayerDrop 2022-07-25 22:03:11 +00:00
9c95bd8295 cool I can simplify this now 2022-07-25 22:03:11 +00:00
a764fe9835 mostly appease clippy 2022-07-25 22:03:11 +00:00
46af418adb cleanup 2022-07-25 22:03:11 +00:00
92d231cd22 move item state action functions to new file 2022-07-25 22:03:10 +00:00
c713445884 remove boxes from ItemStateAction structs to remove need for static lifetimes 2022-07-25 22:03:10 +00:00
f7d802aa58 don't need static for with_transaction 2022-07-25 22:03:10 +00:00
48b73bae09 move EntityGatewayTransaction to end of file 2022-07-25 22:03:10 +00:00
7c3ae25f29 cleanup 2022-07-25 22:03:10 +00:00
deface35b8 redundant bit of code 2022-07-25 22:03:10 +00:00
93a299af00 remove unused use 2022-07-25 22:03:10 +00:00
d7be62e69e initial ItemState 2022-07-25 22:03:10 +00:00
98d73a2db4 redo this cause entitygateway isnt Clone anymore 2022-07-25 22:03:10 +00:00
1cdf091a2c add transactions! 2022-07-25 22:03:10 +00:00
9baf23e1b6 make all entitygateway functions take &mut self, move postgres functions to separate function for future use 2022-07-25 22:03:07 +00:00
bc8769bc9a update sqlx 2022-07-25 22:02:37 +00:00
f2e7795b54 fix kill count applying logic for later when we need to unseal things
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-06-18 15:19:40 +00:00
68de2504e0 fix drop test
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-18 00:13:11 +00:00
533af3a355 more cleanup
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-16 23:49:07 +00:00
b2f3dc9f50 lol maybe if we actually apply modifiers the tests will stop failing
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-16 00:48:53 +00:00
d07c61d009 based clip
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-13 23:28:58 +00:00
2d5b6c5c01 actually add and use AddKill modifier and remove dead code 2022-06-13 23:24:38 +00:00
c285d5cadc remove test 2022-05-29 23:09:56 +00:00
adf062c269 cleanup 2022-05-29 22:27:57 +00:00
a21fb5a644 forgot about dre but not clippy
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-27 21:24:52 +00:00
e075de3259 test with toomanyunwraps()
Some checks failed
continuous-integration/drone/push Build is failing
2022-05-26 23:39:35 +00:00
a0747ea89a test for 1 kill increases every equipped kill counter 2022-05-24 01:19:53 +00:00
0976bd9ab0 remove 3rd attr for killcounter weapons and fix tests 2022-05-24 01:16:29 +00:00
b4866a3e59 fix tests 2022-05-23 15:03:30 +00:00
e3991f5d41 fix warnings from rebase 2022-05-23 15:01:26 +00:00
117a2daa31 killcounters, redbox mode, failing unseal test, and random debug strings 2022-05-22 19:25:29 +00:00
ca44dcf689 andy vs. clippy round 5 2022-05-22 19:22:55 +00:00
b8961d665d this was supposed to be for unsealing/combining items 2022-05-22 19:22:55 +00:00
e82fe779fc don't actually need this stuff 2022-05-22 19:22:53 +00:00
ad5a1faccc make the right items drop with kill counters 2022-05-22 19:22:19 +00:00
527cf50b9d add kill counters to units (limiter) 2022-05-22 19:21:56 +00:00
9025458ee8 add kill counter field to weapons and increment it when a client kills a monster 2022-05-22 19:20:18 +00:00
183 changed files with 16149 additions and 15442 deletions

View File

@ -1,24 +1,12 @@
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: test elseware name: test elseware
concurrency: concurrency:
limit: 1 limit: 1
environment:
CARGO_INCREMENTAL: false
steps: steps:
- name: clean cache
image: rustlang/rust:nightly
volumes:
- name: cache
path: /usr/local/cargo
- name: target-cache
path: /drone/src/target
commands:
- cargo prune
- name: build - name: build
image: rustlang/rust:nightly image: rustlang/rust:nightly
volumes: volumes:
@ -45,7 +33,7 @@ steps:
- name: target-cache - name: target-cache
path: /drone/src/target path: /drone/src/target
commands: commands:
- cargo test --jobs 1 - cargo test
volumes: volumes:
- name: cache - name: cache

2411
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,48 +2,10 @@
name = "elseware" name = "elseware"
version = "0.1.0" version = "0.1.0"
authors = ["Jake Probst <jake.probst@gmail.com>"] authors = ["Jake Probst <jake.probst@gmail.com>"]
edition = "2021" edition = "2018"
[workspace]
members = [
"src/client",
"src/drops",
"src/entity",
"src/items",
"src/location",
"src/maps",
"src/networking",
"src/pktbuilder",
"src/quests",
"src/room",
"src/shops",
"src/stats",
"src/trade",
"src/patch_server",
"src/login_server",
"src/ship_server",
]
[workspace.dependencies]
entity = { path = "./src/entity" }
maps = { path = "./src/maps" }
networking = { path = "./src/networking" }
shops = { path = "./src/shops" }
stats = { path = "./src/stats" }
items = { path = "./src/items" }
pktbuilder = { path = "./src/pktbuilder" }
quests = { path = "./src/quests" }
location = { path = "./src/location" }
client = { path = "./src/client" }
drops = { path = "./src/drops" }
trade = { path = "./src/trade" }
room = { path = "./src/room" }
patch_server = { path = "./src/patch_server" }
login_server = { path = "./src/login_server" }
ship_server = { path = "./src/ship_server" }
libpso = { git = "http://git.sharnoth.com/jake/libpso", rev="90246b6" }
[dependencies]
libpso = { git = "http://git.sharnoth.com/jake/libpso" }
async-std = { version = "1.9.0", features = ["unstable", "attributes"] } async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
futures = "0.3.5" futures = "0.3.5"
rand = "0.7.3" rand = "0.7.3"
@ -60,38 +22,15 @@ fern = { version = "0.5", features = ["colored"] }
byteorder = "1" byteorder = "1"
enum-utils = "0.1.2" enum-utils = "0.1.2"
derive_more = { version = "0.99.3", features = ["display"]} derive_more = { version = "0.99.3", features = ["display"]}
thiserror = "1.0.37" thiserror = "1.0.15"
ages-prs = "0.1" ages-prs = "0.1"
async-trait = "0.1.51" async-trait = "0.1.51"
async-recursion= "1.0.0" async-recursion= "1.0.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] }
refinery = { version = "0.5.0", features = ["postgres"] } refinery = { version = "0.5.0", features = ["postgres"] }
sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] } sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
strum = "0.19.5" strum = "0.19.5"
strum_macros = "0.19" strum_macros = "0.19"
anyhow = { version = "1.0.68", features = ["backtrace"] } anyhow = { version = "1.0.47", features = ["backtrace"] }
fix-hidden-lifetime-bug = "0.2.4"
[dependencies]
entity = { workspace = true }
maps = { workspace = true }
networking = { workspace = true }
patch_server = { workspace = true }
login_server = { workspace = true }
ship_server = { workspace = true }
libpso = { workspace = true }
async-std = { workspace = true }
bcrypt = { workspace = true }
chrono = { workspace = true }
fern = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
[dev-dependencies]
drops = { workspace = true }
shops = { workspace = true }
items = { workspace = true }
quests = { workspace = true }
stats = { workspace = true }
async-trait = { workspace = true }

View File

@ -11,7 +11,7 @@ photon_blast = "Pilla"
[Surya] [Surya]
feed_table = 3 feed_table = 3
photon_blast = "Golla" photon_blast = "Leilla"
[Vayu] [Vayu]
feed_table = 4 feed_table = 4
@ -19,7 +19,7 @@ photon_blast = "MyllaYoulla"
[Varaha] [Varaha]
feed_table = 4 feed_table = 4
photon_blast = "Golla" photon_blast = "Leilla"
[Kama] [Kama]
feed_table = 4 feed_table = 4
@ -27,7 +27,7 @@ photon_blast = "Pilla"
[Ushasu] [Ushasu]
feed_table = 4 feed_table = 4
photon_blast = "Golla" photon_blast = "Leilla"
[Apsaras] [Apsaras]
feed_table = 4 feed_table = 4
@ -35,7 +35,7 @@ photon_blast = "Estlla"
[Kumara] [Kumara]
feed_table = 4 feed_table = 4
photon_blast = "Golla" photon_blast = "Leilla"
[Kaitabha] [Kaitabha]
feed_table = 4 feed_table = 4
@ -55,7 +55,7 @@ photon_blast = "Estlla"
[Rudra] [Rudra]
feed_table = 2 feed_table = 2
photon_blast = "Golla" photon_blast = "Leilla"
[Marutah] [Marutah]
feed_table = 2 feed_table = 2
@ -63,7 +63,7 @@ photon_blast = "Pilla"
[Yaksa] [Yaksa]
feed_table = 5 feed_table = 5
photon_blast = "Golla" photon_blast = "Leilla"
[Sita] [Sita]
feed_table = 5 feed_table = 5
@ -99,7 +99,7 @@ photon_blast = "Estlla"
[Vritra] [Vritra]
feed_table = 1 feed_table = 1
photon_blast = "Leilla" photon_blast = "Golla"
[Namuci] [Namuci]
feed_table = 2 feed_table = 2
@ -107,7 +107,7 @@ photon_blast = "MyllaYoulla"
[Sumba] [Sumba]
feed_table = 2 feed_table = 2
photon_blast = "Golla" photon_blast = "Leilla"
[Naga] [Naga]
feed_table = 6 feed_table = 6
@ -144,7 +144,7 @@ photon_blast = "Estlla"
[Naraka] [Naraka]
feed_table = 6 feed_table = 6
photon_blast = "Golla" photon_blast = "Leilla"
[Madhu] [Madhu]
feed_table = 6 feed_table = 6

View File

@ -1,8 +1,9 @@
use log::{info}; use log::{info};
use entity::gateway::postgres::PostgresGateway; use elseware::entity::gateway::postgres::PostgresGateway;
use login_server::login::LoginServerState; use elseware::login::login::LoginServerState;
use login_server::character::CharacterServerState; use elseware::login::character::CharacterServerState;
use networking::interserver::AuthToken; use elseware::common::mainloop::{login_mainloop, character_mainloop};
use elseware::common::interserver::AuthToken;
fn main() { fn main() {
let colors = fern::colors::ColoredLevelConfig::new() let colors = fern::colors::ColoredLevelConfig::new()
@ -36,23 +37,15 @@ fn main() {
let shipgate_token = std::env::var("SHIPGATE_TOKEN").unwrap(); let shipgate_token = std::env::var("SHIPGATE_TOKEN").unwrap();
let entity_gateway = PostgresGateway::new(&db_host, &db_dbname, &db_username, &db_password); let entity_gateway = PostgresGateway::new(&db_host, &db_dbname, &db_username, &db_password);
let login_state = LoginServerState::new(entity_gateway.clone(), charserv_ip); let thread_entity_gateway = entity_gateway.clone();
let login_loop = async_std::task::spawn(async move { let login_state = LoginServerState::new(thread_entity_gateway, charserv_ip);
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await; let login_loop = login_mainloop(login_state, elseware::login::login::LOGIN_PORT);
});
let char_state = CharacterServerState::new(entity_gateway, AuthToken(shipgate_token)); let char_state = CharacterServerState::new(entity_gateway, AuthToken(shipgate_token));
let sub_char_state = char_state.clone(); let character_loop = character_mainloop(char_state, elseware::login::character::CHARACTER_PORT, elseware::login::login::COMMUNICATION_PORT);
let character_loop = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
});
let inter_character_loop = async_std::task::spawn(async move {
networking::mainloop::run_interserver_listen(char_state, login_server::login::COMMUNICATION_PORT).await;
});
info!("[auth/character] starting server"); info!("[auth/character] starting server");
async_std::task::block_on(async move { async_std::task::block_on(async move {
futures::future::join_all(vec![login_loop, character_loop, inter_character_loop]).await futures::future::join_all(vec![login_loop, character_loop]).await
}); });
} }

View File

@ -1,18 +1,19 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use log::{info}; use log::{info};
use networking::interserver::AuthToken; use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd};
use login_server::login::LoginServerState; use elseware::login::login::LoginServerState;
use login_server::character::CharacterServerState; use elseware::login::character::CharacterServerState;
use patch_server::{PatchServerState, generate_patch_tree, load_config, load_motd}; use elseware::ship::ship::ShipServerStateBuilder;
use ship_server::ShipServerStateBuilder; use elseware::entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
#[allow(unused_imports)]
use elseware::entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
use elseware::entity::character::NewCharacterEntity;
use elseware::entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
use elseware::common::interserver::AuthToken;
use maps::Holiday; use elseware::entity::item;
use entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::common::mainloop::*;
use entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
use entity::character::NewCharacterEntity;
use entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
use entity::item;
fn setup_logger() { fn setup_logger() {
let colors = fern::colors::ColoredLevelConfig::new() let colors = fern::colors::ColoredLevelConfig::new()
@ -52,7 +53,7 @@ fn main() {
for i in 0..5 { for i in 0..5 {
let fake_user = NewUserAccountEntity { let fake_user = NewUserAccountEntity {
email: format!("fake{i}@email.com"), email: format!("fake{}@email.com", i),
username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) }, username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) },
password: bcrypt::hash("qwer", 5).unwrap(), password: bcrypt::hash("qwer", 5).unwrap(),
guildcard: i + 1, guildcard: i + 1,
@ -64,18 +65,18 @@ fn main() {
}; };
let fake_user = entity_gateway.create_user(fake_user).await.unwrap(); let fake_user = entity_gateway.create_user(fake_user).await.unwrap();
entity_gateway.create_user_settings(NewUserSettingsEntity::new(fake_user.id)).await.unwrap(); entity_gateway.create_user_settings(NewUserSettingsEntity::new(fake_user.id)).await.unwrap();
let mut character = NewCharacterEntity::new(fake_user.id); let mut character = NewCharacterEntity::new(fake_user.id, 1);
character.name = format!("Test Char {}", i*2); character.name = format!("Test Char {}", i*2);
let character = entity_gateway.create_character(character).await.unwrap(); let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap(); entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, &item::BankIdentifier::Character, 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); let mut character = NewCharacterEntity::new(fake_user.id, 1);
character.slot = 2; character.slot = 2;
character.name = "ItemRefactor".into(); character.name = "ItemRefactor".into();
character.exp = 80000000; character.exp = 80000000;
let character = entity_gateway.create_character(character).await.unwrap(); let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap(); entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, &item::BankIdentifier::Character, item::Meseta(999999)).await.unwrap(); entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
for _ in 0..3 { for _ in 0..3 {
entity_gateway.create_item( entity_gateway.create_item(
@ -87,6 +88,7 @@ fn main() {
special: None, special: None,
attrs: [None, None, None], attrs: [None, None, None],
tekked: true, tekked: true,
kills: None,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -107,13 +109,14 @@ fn main() {
NewItemEntity { NewItemEntity {
item: ItemDetail::Weapon( item: ItemDetail::Weapon(
item::weapon::Weapon { item::weapon::Weapon {
weapon: item::weapon::WeaponType::Raygun, weapon: item::weapon::WeaponType::SealedJSword,
grind: 5, grind: 5,
special: Some(item::weapon::WeaponSpecial::Hell), special: None,
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}), attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}),
None,], None,],
tekked: false, tekked: true,
kills: Some(22998),
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -121,13 +124,14 @@ fn main() {
NewItemEntity { NewItemEntity {
item: ItemDetail::Weapon( item: ItemDetail::Weapon(
item::weapon::Weapon { item::weapon::Weapon {
weapon: item::weapon::WeaponType::Handgun, weapon: item::weapon::WeaponType::Club,
grind: 5, grind: 10,
special: Some(item::weapon::WeaponSpecial::Charge), special: Some(item::weapon::WeaponSpecial::Charge),
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}), attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}),
None,], None,],
tekked: true, tekked: true,
kills: None,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -142,6 +146,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
None,], None,],
tekked: true, tekked: true,
kills: None,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -156,6 +161,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
None,], None,],
tekked: true, tekked: true,
kills: None,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -170,6 +176,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),], Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true, tekked: true,
kills: None,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -196,7 +203,7 @@ fn main() {
item::NewItemEntity { item::NewItemEntity {
item: ItemDetail::Tool ( item: ItemDetail::Tool (
item::tool::Tool { item::tool::Tool {
tool: item::tool::ToolType::CellOfMag502, tool: item::tool::ToolType::MagicRockMoola,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -212,8 +219,17 @@ fn main() {
let item6_1 = entity_gateway.create_item( let item6_1 = entity_gateway.create_item(
NewItemEntity { NewItemEntity {
item: ItemDetail::ESWeapon( item: ItemDetail::Weapon(
item::esweapon::ESWeapon::new(item::esweapon::ESWeaponType::Saber) item::weapon::Weapon {
weapon: item::weapon::WeaponType::Autogun,
grind: 5,
special: Some(item::weapon::WeaponSpecial::Hell),
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 70}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 80}),
None,],
tekked: false,
kills: None,
}
), ),
}).await.unwrap(); }).await.unwrap();
let item7_a = entity_gateway.create_item( let item7_a = entity_gateway.create_item(
@ -243,8 +259,9 @@ fn main() {
NewItemEntity { NewItemEntity {
item: ItemDetail::Unit( item: ItemDetail::Unit(
item::unit::Unit { item::unit::Unit {
unit: item::unit::UnitType::PriestMind, unit: item::unit::UnitType::Limiter,
modifier: Some(item::unit::UnitModifier::PlusPlus), modifier: None,
kills: Some(19999),
} }
), ),
} }
@ -254,7 +271,8 @@ fn main() {
item: ItemDetail::Unit( item: ItemDetail::Unit(
item::unit::Unit { item::unit::Unit {
unit: item::unit::UnitType::PriestMind, unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::Plus), modifier: Some(item::unit::UnitModifier::Minus),
kills: None,
} }
), ),
} }
@ -265,6 +283,7 @@ fn main() {
item::unit::Unit { item::unit::Unit {
unit: item::unit::UnitType::PriestMind, unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::Minus), modifier: Some(item::unit::UnitModifier::Minus),
kills: None,
} }
), ),
} }
@ -275,6 +294,7 @@ fn main() {
item::unit::Unit { item::unit::Unit {
unit: item::unit::UnitType::PriestMind, unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::MinusMinus), modifier: Some(item::unit::UnitModifier::MinusMinus),
kills: None,
} }
), ),
} }
@ -297,6 +317,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}), Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),], Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true, tekked: true,
kills: None,
} }
), ),
}).await.unwrap(); }).await.unwrap();
@ -324,91 +345,56 @@ fn main() {
}; };
entity_gateway.set_character_equips(&character.id, &equipped).await.unwrap(); entity_gateway.set_character_equips(&character.id, &equipped).await.unwrap();
let inventory = item::InventoryEntity::new(vec![InventoryItemEntity::from(item0), item1.into(), item2_w.into(), item3.into(), let inventory = item::InventoryEntity::new(vec![InventoryItemEntity::from(item0), item1.into(), item2_w.into(), item3.into(), item4.into(), item5_m.into(), item6.into(), item6_1.into(), item7_a.into(), item8_s.into(), item9_u0.into(), item10_u1.into(), item11_u2.into(), item12_u3.into(), item13.into(), item14.into(), monomates.into()]);
item4.into(), item5_m.into(), item6.into(), item6_1.into(), item7_a.into(),
item8_s.into(), item9_u0.into(), item10_u1.into(), item11_u2.into(), item12_u3.into(),
item13.into(), item14.into(), monomates.into()]);
entity_gateway.set_character_inventory(&character.id, &inventory).await.unwrap(); entity_gateway.set_character_inventory(&character.id, &inventory).await.unwrap();
entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), &item::BankIdentifier::Character).await.unwrap(); entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), &item::BankName("".into())).await.unwrap();
} }
info!("[patch] starting server"); info!("[patch] starting server");
let patch_config = load_config(); let patch_config = load_config();
let patch_motd = load_motd(); let patch_motd = load_motd();
let (patch_file_tree, patch_file_lookup) = generate_patch_tree(patch_config.path.as_str()); let (patch_file_tree, patch_file_lookup) = generate_patch_tree(patch_config.path.as_str());
let patch_state = PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd); let patch_state = Box::new(PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd));
let patch_loop = async_std::task::spawn(async move { let patch_loop = patch_mainloop(*patch_state, patch_config.port);
networking::mainloop::run_server(patch_state, patch_config.port).await;
});
let thread_entity_gateway = entity_gateway.clone();
info!("[auth] starting server"); info!("[auth] starting server");
let login_state = LoginServerState::new(entity_gateway.clone(), "127.0.0.1".parse().unwrap()); let login_state = Box::new(LoginServerState::new(thread_entity_gateway, "127.0.0.1".parse().unwrap()));
let login_loop = async_std::task::spawn(async move { let login_loop = login_mainloop(*login_state, elseware::login::login::LOGIN_PORT);
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await;
});
let thread_entity_gateway = entity_gateway.clone();
info!("[character] starting server"); info!("[character] starting server");
let char_state = CharacterServerState::new(entity_gateway.clone(), AuthToken("".into())); let char_state = Box::new(CharacterServerState::new(thread_entity_gateway, AuthToken("".into())));
let sub_char_state = char_state.clone(); let character_loop = character_mainloop(*char_state, elseware::login::character::CHARACTER_PORT, elseware::login::login::COMMUNICATION_PORT);
let character_loop = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
});
let sub_char_state = char_state.clone(); let thread_entity_gateway = entity_gateway.clone();
let inter_character_loop = async_std::task::spawn(async move { info!("[ship] starting server");
networking::mainloop::run_interserver_listen(sub_char_state, login_server::login::COMMUNICATION_PORT).await; let ship_state = Box::new(ShipServerStateBuilder::default()
});
info!("[ship] starting servers");
let ship_state = ShipServerStateBuilder::default()
.name("US/Sona-Nyl".into()) .name("US/Sona-Nyl".into())
.ip(Ipv4Addr::new(127,0,0,1)) .ip(Ipv4Addr::new(127,0,0,1))
.port(ship_server::SHIP_PORT) .port(elseware::ship::ship::SHIP_PORT)
.event(Holiday::Halloween) .gateway(thread_entity_gateway)
.gateway(entity_gateway.clone()) .build());
.build(); let ship_loop = ship_mainloop(*ship_state, elseware::ship::ship::SHIP_PORT, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT);
let sub_ship_state = ship_state.clone();
let ship_loop1 = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await;
});
let sub_ship_state = ship_state.clone();
let inter_ship_loop1 = async_std::task::spawn(async move {
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
});
let ship_state = ShipServerStateBuilder::default() let thread_entity_gateway = entity_gateway.clone();
let ship_state = Box::new(ShipServerStateBuilder::default()
.name("EU/Dylath-Leen".into()) .name("EU/Dylath-Leen".into())
.ip(Ipv4Addr::new(127,0,0,1)) .ip(Ipv4Addr::new(127,0,0,1))
.port(ship_server::SHIP_PORT+2000) .port(elseware::ship::ship::SHIP_PORT+2000)
.event(Holiday::Christmas) .gateway(thread_entity_gateway)
.gateway(entity_gateway.clone()) .build());
.build(); let ship_loop2 = ship_mainloop(*ship_state, elseware::ship::ship::SHIP_PORT+2000, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT);
let sub_ship_state = ship_state.clone();
let ship_loop2 = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+2000).await;
});
let sub_ship_state = ship_state.clone();
let inter_ship_loop2 = async_std::task::spawn(async move {
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
});
let ship_state = ShipServerStateBuilder::default() let thread_entity_gateway = entity_gateway.clone();
let ship_state = Box::new(ShipServerStateBuilder::default()
.name("JP/Thalarion".into()) .name("JP/Thalarion".into())
.ip(Ipv4Addr::new(127,0,0,1)) .ip(Ipv4Addr::new(127,0,0,1))
.port(ship_server::SHIP_PORT+3000) .port(elseware::ship::ship::SHIP_PORT+3000)
.gateway(entity_gateway.clone()) .gateway(thread_entity_gateway)
.build(); .build());
let sub_ship_state = ship_state.clone(); let ship_loop3 = ship_mainloop(*ship_state, elseware::ship::ship::SHIP_PORT+3000, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT);
let ship_loop3 = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+3000).await;
});
let sub_ship_state = ship_state.clone();
let inter_ship_loop3 = async_std::task::spawn(async move {
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
});
futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop, futures::future::join_all(vec![patch_loop, login_loop, character_loop, ship_loop, ship_loop2, ship_loop3]).await;
ship_loop1, ship_loop2, ship_loop3,
inter_ship_loop1, inter_ship_loop2, inter_ship_loop3]).await;
}); });
} }

View File

@ -1,5 +1,6 @@
use patch_server::{PatchServerState, generate_patch_tree, load_config_env, load_motd}; use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config_env, load_motd};
use log::info; use log::{info};
use elseware::common::mainloop::patch_mainloop;
fn main() { fn main() {
info!("[patch] starting server"); info!("[patch] starting server");
@ -7,10 +8,9 @@ fn main() {
let patch_motd = load_motd(); let patch_motd = load_motd();
let (patch_file_tree, patch_file_lookup) = generate_patch_tree(patch_config.path.as_str()); let (patch_file_tree, patch_file_lookup) = generate_patch_tree(patch_config.path.as_str());
let patch_state = PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd); let patch_state = PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd);
let patch_loop = patch_mainloop(patch_state, patch_config.port);
let patch_loop = async_std::task::spawn(async move { async_std::task::block_on(async move {
networking::mainloop::run_server(patch_state, patch_config.port).await; patch_loop.await
}); });
async_std::task::block_on(patch_loop);
} }

View File

@ -1,7 +1,8 @@
use log::info; use log::{info};
use entity::gateway::postgres::PostgresGateway; use elseware::entity::gateway::postgres::PostgresGateway;
use ship_server::ShipServerStateBuilder; use elseware::ship::ship::ShipServerStateBuilder;
use networking::interserver::AuthToken; use elseware::common::mainloop::ship_mainloop;
use elseware::common::interserver::AuthToken;
fn main() { fn main() {
let colors = fern::colors::ColoredLevelConfig::new() let colors = fern::colors::ColoredLevelConfig::new()
@ -36,27 +37,19 @@ fn main() {
let shipgate_token = std::env::var("SHIPGATE_TOKEN").unwrap(); let shipgate_token = std::env::var("SHIPGATE_TOKEN").unwrap();
let ship_name = std::env::var("SHIP_NAME").unwrap().parse().unwrap(); let ship_name = std::env::var("SHIP_NAME").unwrap().parse().unwrap();
let ip = std::env::var("SELF_IP").unwrap().parse().unwrap(); let ip = std::env::var("SELF_IP").unwrap().parse().unwrap();
let ship_state = ShipServerStateBuilder::default() let ship_state = ShipServerStateBuilder::default()
.name(ship_name) .name(ship_name)
.ip(ip) .ip(ip)
.port(ship_server::SHIP_PORT) .port(elseware::ship::ship::SHIP_PORT)
.gateway(entity_gateway) .gateway(entity_gateway)
.auth_token(AuthToken(shipgate_token)) .auth_token(AuthToken(shipgate_token))
.build(); .build();
let shipgate_ip = std::env::var("SHIPGATE_IP").unwrap().parse().unwrap(); let shipgate_ip = std::env::var("SHIPGATE_IP").unwrap().parse().unwrap();
let ship_loop = ship_mainloop(ship_state, elseware::ship::ship::SHIP_PORT, shipgate_ip, elseware::login::login::COMMUNICATION_PORT);
let sub_ship_state = ship_state.clone();
let ship_loop = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await;
});
let inter_ship_loop = async_std::task::spawn(async move {
networking::mainloop::run_interserver_connect(ship_state, shipgate_ip, login_server::login::COMMUNICATION_PORT).await;
});
info!("[auth/character] starting server"); info!("[auth/character] starting server");
async_std::task::block_on(async move { async_std::task::block_on(async move {
futures::future::join_all(vec![ship_loop, inter_ship_loop]).await; ship_loop.await
}); });
} }

View File

@ -1,20 +0,0 @@
[package]
name = "client"
version = "0.1.0"
edition = "2021"
[dependencies]
entity = { workspace = true }
maps = { workspace = true }
networking = { workspace = true }
shops = { workspace = true }
items = { workspace = true }
libpso = { workspace = true }
async-std = { workspace = true }
futures = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true }
chrono = { workspace = true }

View File

@ -1,174 +0,0 @@
use std::collections::HashMap;
use async_std::sync::{Arc, RwLock, RwLockReadGuard};
use futures::future::BoxFuture;
use libpso::packet::ship::*;
use libpso::packet::login::Session;
use networking::serverstate::ClientId;
use entity::account::{UserAccountEntity, UserSettingsEntity};
use entity::character::CharacterEntity;
use entity::item;
use maps::area::MapArea;
use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("not found {0}")]
NotFound(ClientId),
}
#[derive(Clone, Default)]
pub struct Clients(Arc<RwLock<HashMap<ClientId, RwLock<ClientState>>>>);
impl Clients {
pub async fn add(&mut self, client_id: ClientId, client_state: ClientState) {
self.0
.write()
.await
.insert(client_id, RwLock::new(client_state));
}
pub async fn remove(&mut self, client_id: &ClientId) -> Option<ClientState> {
Some(self.0
.write()
.await
.remove(client_id)?
.into_inner())
}
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a,
{
let clients = self.0
.read()
.await;
let client = clients
.get(&client_id)
.ok_or(ClientError::NotFound(client_id))?
.read()
.await;
Ok(func(&client).await)
}
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a,
{
let clients = self.0
.read()
.await;
let mut client_states: [std::mem::MaybeUninit<RwLockReadGuard<ClientState>>; N] = unsafe {
std::mem::MaybeUninit::uninit().assume_init()
};
for (cindex, client_id) in client_ids.iter().enumerate() {
let c = clients
.get(client_id)
.ok_or(ClientError::NotFound(*client_id))?
.read()
.await;
client_states[cindex].write(c);
}
let client_states = unsafe {
std::mem::transmute_copy(&client_states)
};
Ok(func(client_states).await)
}
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a,
{
let clients = self.0
.read()
.await;
let mut client = clients
.get(&client_id)
.ok_or(ClientError::NotFound(client_id))?
.write()
.await;
Ok(func(&mut client).await)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ItemDropLocation {
pub map_area: MapArea,
pub x: f32,
pub z: f32,
pub item_id: items::ClientItemId,
}
pub struct LoadingQuest {
pub header_bin: Option<QuestHeader>,
pub header_dat: Option<QuestHeader>,
}
pub struct ClientState {
pub user: UserAccountEntity,
pub settings: UserSettingsEntity,
pub character: CharacterEntity,
_session: Session,
//guildcard: GuildCard,
pub block: usize,
pub item_drop_location: Option<ItemDropLocation>,
pub done_loading_quest: bool,
pub area: Option<MapArea>,
pub x: f32,
pub y: f32,
pub z: f32,
pub weapon_shop: Vec<WeaponShopItem>,
pub tool_shop: Vec<ToolShopItem>,
pub armor_shop: Vec<ArmorShopItem>,
pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>,
pub character_playtime: chrono::Duration,
pub log_on_time: chrono::DateTime<chrono::Utc>,
}
impl ClientState {
pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState {
let character_playtime = chrono::Duration::seconds(character.playtime as i64);
ClientState {
user,
settings,
character,
_session: session,
block: 0,
item_drop_location: None,
done_loading_quest: false,
area: None,
x: 0.0,
y: 0.0,
z: 0.0,
weapon_shop: Vec::new(),
tool_shop: Vec::new(),
armor_shop: Vec::new(),
tek: None,
character_playtime,
log_on_time: chrono::Utc::now(),
}
}
pub fn update_playtime(&mut self) {
let additional_playtime = chrono::Utc::now() - self.log_on_time;
self.character.playtime = (self.character_playtime + additional_playtime).num_seconds() as u32;
}
}

View File

@ -1,9 +1,8 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use async_std::channel;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use entity::account::UserAccountId; use crate::entity::account::UserAccountId;
use entity::character::CharacterEntityId; use crate::entity::character::CharacterEntityId;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ServerId(pub usize); pub struct ServerId(pub usize);
@ -47,13 +46,12 @@ pub enum ShipMessage {
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait InterserverActor: Clone { pub trait InterserverActor {
type SendMessage: Serialize; type SendMessage: Serialize;
type RecvMessage: DeserializeOwned; type RecvMessage: DeserializeOwned;
type Error; type Error;
async fn on_connect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)>; async fn on_connect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)>;
async fn on_action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error>; async fn action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error>;
async fn on_disconnect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)>; async fn on_disconnect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)>;
async fn set_sender(&mut self, server_id: ServerId, tx: channel::Sender<Self::SendMessage>);
} }

View File

@ -1,9 +1,7 @@
use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use serde_json::Value; use serde_json::Value;
use entity::character::CharacterClass; use crate::entity::character::CharacterClass;
use std::sync::LazyLock;
pub static LEVEL_TABLE: LazyLock<CharacterLevelTable> = LazyLock::new(CharacterLevelTable::default);
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub struct CharacterStats { pub struct CharacterStats {
@ -29,14 +27,14 @@ struct CharacterLevelEntry {
} }
pub struct CharacterLevelTable { pub struct CharacterLevelTable {
table: Box<[[CharacterLevelEntry; 200]; 12]>, table: HashMap<CharacterClass, [CharacterLevelEntry; 200]>,
} }
impl Default for CharacterLevelTable { impl Default for CharacterLevelTable {
fn default() -> CharacterLevelTable { fn default() -> CharacterLevelTable {
let file = File::open("data/char_stats.json").unwrap(); let file = File::open("data/char_stats.json").unwrap();
let json: Value = serde_json::from_reader(file).unwrap(); let json: Value = serde_json::from_reader(file).unwrap();
let mut table = Box::new([[CharacterLevelEntry::default(); 200]; 12]); let mut table = HashMap::new();
for it in json.as_object().unwrap().iter(){ for it in json.as_object().unwrap().iter(){
let cl = match it.0.as_str() { let cl = match it.0.as_str() {
@ -69,7 +67,7 @@ impl Default for CharacterLevelTable {
} }
} }
table[u8::from(cl) as usize] = statlist; table.insert(cl, statlist);
} }
CharacterLevelTable { CharacterLevelTable {
@ -80,7 +78,7 @@ impl Default for CharacterLevelTable {
impl CharacterLevelTable { impl CharacterLevelTable {
pub fn get_level_from_exp(&self, ch_class: CharacterClass, exp: u32) -> u32 { pub fn get_level_from_exp(&self, ch_class: CharacterClass, exp: u32) -> u32 {
if let Some(statlist) = self.table.get(u8::from(ch_class) as usize) { if let Some(statlist) = self.table.get(&ch_class) {
statlist statlist
.iter() .iter()
.filter(|stat| { .filter(|stat| {
@ -94,7 +92,7 @@ impl CharacterLevelTable {
} }
pub fn get_stats_from_exp(&self, ch_class: CharacterClass, exp: u32) -> (u32, CharacterStats) { pub fn get_stats_from_exp(&self, ch_class: CharacterClass, exp: u32) -> (u32, CharacterStats) {
if let Some(statlist) = self.table.get(u8::from(ch_class) as usize) { if let Some(statlist) = self.table.get(&ch_class) {
statlist statlist
.iter() .iter()
.filter(|stat| { .filter(|stat| {

View File

@ -0,0 +1,317 @@
use std::pin::Pin;
use futures::future::Future;
use log::{trace, info, warn};
use async_std::sync::{Arc, Mutex};
use async_std::io::prelude::{ReadExt, WriteExt};
use std::collections::HashMap;
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use libpso::PacketParseError;
use crate::common::serverstate::ClientId;
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
#[derive(Debug)]
pub enum NetworkError {
CouldNotSend,
CipherError(CipherError),
PacketParseError(PacketParseError),
IOError(std::io::Error),
DataNotReady,
ClientDisconnected,
}
impl From<CipherError> for NetworkError {
fn from(err: CipherError) -> NetworkError {
NetworkError::CipherError(err)
}
}
impl From<std::io::Error> for NetworkError {
fn from(err: std::io::Error) -> NetworkError {
NetworkError::IOError(err)
}
}
impl From<PacketParseError> for NetworkError {
fn from(err: PacketParseError) -> NetworkError {
NetworkError::PacketParseError(err)
}
}
struct PacketReceiver {
socket: Arc<async_std::net::TcpStream>,
cipher: Arc<Mutex<Box<dyn PSOCipher + Send>>>,
recv_buffer: Vec<u8>,
incoming_data: Vec<u8>,
}
impl PacketReceiver {
fn new(socket: Arc<async_std::net::TcpStream>, cipher: Arc<Mutex<Box<dyn PSOCipher + Send>>>) -> PacketReceiver {
PacketReceiver {
socket,
cipher,
recv_buffer: Vec::new(),
incoming_data: Vec::new(),
}
}
async fn fill_recv_buffer(&mut self) -> Result<(), NetworkError> {
let mut data = [0u8; 0x8000];
let mut socket = &*self.socket;
let len = socket.read(&mut data).await?;
if len == 0 {
return Err(NetworkError::ClientDisconnected);
}
self.recv_buffer.extend_from_slice(&data[..len]);
let mut dec_buf = {
let mut cipher = self.cipher.lock().await;
let block_chunk_len = self.recv_buffer.len() / cipher.block_size() * cipher.block_size();
let buf = self.recv_buffer.drain(..block_chunk_len).collect();
cipher.decrypt(&buf)?
};
self.incoming_data.append(&mut dec_buf);
Ok(())
}
async fn recv_pkts<R: RecvServerPacket + Send + std::fmt::Debug>(&mut self) -> Result<Vec<R>, NetworkError> {
self.fill_recv_buffer().await?;
let mut result = Vec::new();
loop {
if self.incoming_data.len() < 2 {
break;
}
let pkt_size = u16::from_le_bytes([self.incoming_data[0], self.incoming_data[1]]) as usize;
let mut pkt_len = pkt_size;
while pkt_len % self.cipher.lock().await.block_size() != 0 {
pkt_len += 1;
}
if pkt_len > self.incoming_data.len() {
break;
}
let pkt_data = self.incoming_data.drain(..pkt_len).collect::<Vec<_>>();
trace!("[recv buf] {:?}", pkt_data);
let pkt = match R::from_bytes(&pkt_data[..pkt_size]) {
Ok(p) => p,
Err(err) => {
warn!("error RecvServerPacket::from_bytes: {:?}", err);
continue
},
};
result.push(pkt);
}
Ok(result)
}
}
async fn send_pkt<S: SendServerPacket + Send + std::fmt::Debug>(socket: Arc<async_std::net::TcpStream>,
cipher: Arc<Mutex<Box<dyn PSOCipher + Send>>>, pkt: S)
-> Result<(), NetworkError>
{
let buf = pkt.as_bytes();
trace!("[send buf] {:?}", buf);
let cbuf = cipher.lock().await.encrypt(&buf)?;
let mut ssock = &*socket;
ssock.write_all(&cbuf).await?;
Ok(())
}
enum ClientAction<S, R> {
NewClient(ClientId, async_std::channel::Sender<S>),
Packet(ClientId, R),
Disconnect(ClientId),
}
enum ServerStateAction<S> {
Cipher(Box<dyn PSOCipher + Send + Sync>, Box<dyn PSOCipher + Send + Sync>),
Packet(S),
Disconnect,
}
fn client_recv_loop<S, R>(client_id: ClientId,
socket: Arc<async_std::net::TcpStream>,
cipher: Arc<Mutex<Box<dyn PSOCipher + Send>>>,
server_sender: async_std::channel::Sender<ClientAction<ServerStateAction<S>, R>>,
client_sender: async_std::channel::Sender<ServerStateAction<S>>)
where
S: SendServerPacket + std::fmt::Debug + Send + 'static,
R: RecvServerPacket + std::fmt::Debug + Send + 'static,
{
async_std::task::spawn(async move {
server_sender.send(ClientAction::NewClient(client_id, client_sender)).await.unwrap();
let mut pkt_receiver = PacketReceiver::new(socket, cipher);
loop {
match pkt_receiver.recv_pkts().await {
Ok(pkts) => {
for pkt in pkts {
info!("[recv from {:?}] {:?}", client_id, pkt);
server_sender.send(ClientAction::Packet(client_id, pkt)).await.unwrap();
}
},
Err(err) => {
match err {
NetworkError::ClientDisconnected => {
trace!("[client disconnected] {:?}", client_id);
server_sender.send(ClientAction::Disconnect(client_id)).await.unwrap();
break;
}
_ => {
warn!("[client {:?} recv error] {:?}", client_id, err);
}
}
}
}
}
});
}
fn client_send_loop<S>(client_id: ClientId,
socket: Arc<async_std::net::TcpStream>,
cipher_in: Arc<Mutex<Box<dyn PSOCipher + Send>>>,
cipher_out: Arc<Mutex<Box<dyn PSOCipher + Send>>>,
client_receiver: async_std::channel::Receiver<ServerStateAction<S>>)
where
S: SendServerPacket + std::fmt::Debug + Send + 'static,
{
async_std::task::spawn(async move {
loop {
let action = client_receiver.recv().await.unwrap();
match action {
ServerStateAction::Cipher(inc, outc) => {
*cipher_in.lock().await = inc;
*cipher_out.lock().await = outc;
}
ServerStateAction::Packet(pkt) => {
info!("[send to {:?}] {:?}", client_id, pkt);
if let Err(err) = send_pkt(socket.clone(), cipher_out.clone(), pkt).await {
warn!("[client {:?} send error ] {:?}", client_id, err);
}
},
ServerStateAction::Disconnect => {
break;
}
};
}
});
}
fn state_client_loop<STATE, S, R, E>(state: Arc<Mutex<STATE>>,
server_state_receiver: async_std::channel::Receiver<ClientAction<ServerStateAction<S>, R>>) where
STATE: ServerState<SendPacket=S, RecvPacket=R, PacketError=E> + Send + 'static,
S: SendServerPacket + std::fmt::Debug + Send + 'static,
R: RecvServerPacket + std::fmt::Debug + Send + 'static,
E: std::fmt::Debug + Send,
{
async_std::task::spawn(async move {
let mut clients = HashMap::new();
loop {
let action = server_state_receiver.recv().await.unwrap();
let mut state = state.lock().await;
match action {
ClientAction::NewClient(client_id, sender) => {
let actions = state.on_connect(client_id).await;
match actions {
Ok(actions) => {
for action in actions {
match action {
OnConnect::Cipher((inc, outc)) => {
sender.send(ServerStateAction::Cipher(inc, outc)).await.unwrap();
},
OnConnect::Packet(pkt) => {
sender.send(ServerStateAction::Packet(pkt)).await.unwrap();
}
}
}
},
Err(err) => {
warn!("[client {:?} state on_connect error] {:?}", client_id, err);
}
}
clients.insert(client_id, sender);
},
ClientAction::Packet(client_id, pkt) => {
let pkts = state.handle(client_id, &pkt).await;
match pkts {
Ok(pkts) => {
for (client_id, pkt) in pkts {
if let Some(client) = clients.get_mut(&client_id) {
client.send(ServerStateAction::Packet(pkt)).await.unwrap();
}
}
},
Err(err) => {
warn!("[client {:?} state handler error] {:?}", client_id, err);
}
}
},
ClientAction::Disconnect(client_id) => {
let pkts = state.on_disconnect(client_id).await;
match pkts {
Ok(pkts) => {
for (client_id, pkt) in pkts {
if let Some(client) = clients.get_mut(&client_id) {
client.send(ServerStateAction::Packet(pkt)).await.unwrap();
}
}
if let Some(client) = clients.get_mut(&client_id) {
client.send(ServerStateAction::Disconnect).await.unwrap();
}
}
Err(err) => {
warn!("[client {:?} state on_disconnect error] {:?}", client_id, err);
}
}
}
}
}
});
}
pub fn client_accept_mainloop<STATE, S, R, E>(state: Arc<Mutex<STATE>>, client_port: u16) -> Pin<Box<dyn Future<Output = ()>>>
where
STATE: ServerState<SendPacket=S, RecvPacket=R, PacketError=E> + Send + 'static,
S: SendServerPacket + std::fmt::Debug + Send + Sync + 'static,
R: RecvServerPacket + std::fmt::Debug + Send + Sync + 'static,
E: std::fmt::Debug + Send,
{
Box::pin(async_std::task::spawn(async move {
let listener = async_std::net::TcpListener::bind(&std::net::SocketAddr::from((std::net::Ipv4Addr::new(0,0,0,0), client_port))).await.unwrap();
let mut id = 0;
let (server_state_sender, server_state_receiver) = async_std::channel::bounded(1024);
state_client_loop(state, server_state_receiver);
loop {
let (sock, addr) = listener.accept().await.unwrap();
id += 1;
let client_id = crate::common::serverstate::ClientId(id);
info!("new client {:?} {:?} {:?}", client_id, sock, addr);
let (client_sender, client_receiver) = async_std::channel::bounded(64);
let socket = Arc::new(sock);
let cipher_in: Arc<Mutex<Box<dyn PSOCipher + Send>>> = Arc::new(Mutex::new(Box::new(NullCipher {})));
let cipher_out: Arc<Mutex<Box<dyn PSOCipher + Send>>> = Arc::new(Mutex::new(Box::new(NullCipher {})));
client_recv_loop(client_id, socket.clone(), cipher_in.clone(), server_state_sender.clone(), client_sender);
client_send_loop(client_id, socket.clone(), cipher_in.clone(), cipher_out.clone(), client_receiver);
}
}))
}

View File

@ -0,0 +1,260 @@
use std::time::Duration;
use std::pin::Pin;
use futures::future::Future;
use log::{info, warn};
use async_std::sync::{Arc, Mutex};
use async_std::io::prelude::{ReadExt, WriteExt};
use std::collections::HashMap;
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::common::interserver::{ServerId, InterserverActor};
use crate::login::character::CharacterServerState;
use crate::ship::ship::ShipServerState;
use crate::entity::gateway::entitygateway::EntityGateway;
#[derive(Debug)]
enum MessageReceiverError {
//InvalidSize,
InvalidPayload,
//NetworkError(std::io::Error),
Disconnected,
}
struct MessageReceiver {
socket: async_std::net::TcpStream,
}
impl MessageReceiver {
fn new(socket: async_std::net::TcpStream) -> MessageReceiver {
MessageReceiver {
socket,
}
}
async fn recv<R: DeserializeOwned + std::fmt::Debug + Send>(&mut self) -> Result<R, MessageReceiverError> {
let mut size_buf = [0u8; 4];
self.socket.read_exact(&mut size_buf).await.map_err(|_| MessageReceiverError::Disconnected)?;
let size = u32::from_le_bytes(size_buf) as usize;
let mut payload = vec![0u8; size];
self.socket.read_exact(&mut payload).await.map_err(|_| MessageReceiverError::Disconnected)?;
let payload = String::from_utf8(payload).map_err(|_| MessageReceiverError::InvalidPayload)?;
let msg = serde_json::from_str(&payload).map_err(|_| MessageReceiverError::InvalidPayload)?;
Ok(msg)
}
}
#[derive(Debug)]
enum InterserverInputAction<S, R> {
NewConnection(ServerId, async_std::channel::Sender<S>),
Message(ServerId, R),
Disconnect(ServerId),
}
async fn interserver_state_loop<A, S, R>(state: Arc<Mutex<A>>, action_receiver: async_std::channel::Receiver<InterserverInputAction<S, R>>)
where
A: InterserverActor<SendMessage=S, RecvMessage=R, Error=()> + Send + 'static,
S: Serialize + Send + 'static,
R: DeserializeOwned + Send + 'static,
{
async_std::task::spawn(async move {
let mut ships = HashMap::new();
loop {
info!("interserver loop");
let action = match action_receiver.recv().await {
Ok(action) => action,
Err(err) => {
warn!("error in iterserver state loop {:?}", err);
continue;
}
};
let mut state = state.lock().await;
match action {
InterserverInputAction::NewConnection(server_id, ship_action_sender) => {
ships.insert(server_id, ship_action_sender);
for (server, action) in state.on_connect(server_id).await {
if let Some(sender) = ships.get_mut(&server) {
sender.send(action).await.unwrap();
}
}
},
InterserverInputAction::Message(server_id, message) => {
let actions = state.action(server_id, message).await;
match actions {
Ok(actions) => {
for (server, action) in actions{
if let Some(sender) = ships.get_mut(&server) {
sender.send(action).await.unwrap();
}
}
},
Err(err) => {
warn!("[server {:?} state handler error] {:?}", server_id, err);
}
}
},
InterserverInputAction::Disconnect(server_id) => {
let actions = state.on_disconnect(server_id).await;
ships.remove(&server_id);
for (server, action) in actions {
if let Some(sender) = ships.get_mut(&server) {
sender.send(action).await.unwrap();
}
}
}
}
}
});
}
async fn login_recv_loop<S, R>(server_id: ServerId,
socket: async_std::net::TcpStream,
state_loop_sender: async_std::channel::Sender<InterserverInputAction<S, R>>,
output_loop_sender: async_std::channel::Sender<S>)
where
S: Serialize + std::fmt::Debug + Send + 'static,
R: DeserializeOwned + std::fmt::Debug + Send + 'static,
{
async_std::task::spawn(async move {
state_loop_sender.send(InterserverInputAction::NewConnection(server_id, output_loop_sender)).await.unwrap();
let mut msg_receiver = MessageReceiver::new(socket);
loop {
info!("login recv loop");
match msg_receiver.recv().await {
Ok(msg) => {
info!("[login recv loop msg] {:?}", msg);
state_loop_sender.send(InterserverInputAction::Message(server_id, msg)).await.unwrap();
},
Err(err) => {
if let MessageReceiverError::Disconnected = err {
info!("[login recv loop disconnect] {:?}", server_id);
state_loop_sender.send(InterserverInputAction::Disconnect(server_id)).await.unwrap();
break;
}
info!("[login recv loop err] {:?}", err);
}
}
}
});
}
async fn interserver_send_loop<S>(server_id: ServerId,
mut socket: async_std::net::TcpStream,
output_loop_receiver: async_std::channel::Receiver<S>)
where
S: Serialize + std::fmt::Debug + Send + 'static,
{
async_std::task::spawn(async move {
loop {
info!("login send loop");
match output_loop_receiver.recv().await {
Ok(msg) => {
let payload = serde_json::to_string(&msg);
if let Ok(payload) = payload {
let len_bytes = u32::to_le_bytes(payload.len() as u32);
if let Err(err) = socket.write_all(&len_bytes).await {
warn!("interserver send failed: {:?}", err);
break;
}
if let Err(err) = socket.write_all(payload.as_bytes()).await {
warn!("intserserver send failed: {:?}", err);
break;
}
}
},
Err(err) => {
warn!("error in send_loop: {:?}, {:?}", server_id, err);
break;
}
}
}
});
}
pub fn login_listen_mainloop<EG: EntityGateway + 'static>(state: Arc<Mutex<CharacterServerState<EG>>>, port: u16) -> Pin<Box<dyn Future<Output = ()>>> {
Box::pin(async_std::task::spawn(async move {
let listener = async_std::net::TcpListener::bind(&std::net::SocketAddr::from((std::net::Ipv4Addr::new(0,0,0,0), port))).await.unwrap();
let mut id = 0;
let (server_state_sender, server_state_receiver) = async_std::channel::bounded(1024);
interserver_state_loop(state.clone(), server_state_receiver).await;
loop {
let (socket, addr) = listener.accept().await.unwrap();
info!("new ship server: {:?} {:?}", socket, addr);
id += 1;
let server_id = crate::common::interserver::ServerId(id);
let (client_sender, client_receiver) = async_std::channel::bounded(64);
{
let mut state = state.lock().await;
let local_sender = client_sender.clone();
state.set_sender(server_id, Box::new(move |message| {
async_std::task::block_on(local_sender.send(message)).unwrap();
}))
}
login_recv_loop(server_id, socket.clone(), server_state_sender.clone(), client_sender).await;
interserver_send_loop(server_id, socket.clone(), client_receiver).await;
}
}))
}
pub fn ship_connect_mainloop<EG: EntityGateway + 'static>(state: Arc<Mutex<ShipServerState<EG>>>, ip: std::net::Ipv4Addr, port: u16) -> Pin<Box<dyn Future<Output = ()>>> {
Box::pin(async_std::task::spawn(async move {
let mut id = 0;
let (server_state_sender, server_state_receiver) = async_std::channel::bounded(1024);
interserver_state_loop(state.clone(), server_state_receiver).await;
loop {
info!("trying to connect to loginserv");
let socket = match async_std::net::TcpStream::connect((ip, port)).await {
Ok(socket) => socket,
Err(err) => {
info!("err trying to connect to loginserv {:?}", err);
async_std::task::sleep(Duration::from_secs(10)).await;
continue;
}
};
id += 1;
let server_id = crate::common::interserver::ServerId(id);
info!("found loginserv: {:?} {:?}", server_id, socket);
let (client_sender, client_receiver) = async_std::channel::bounded(64);
{
let mut state = state.lock().await;
let local_sender = client_sender.clone();
state.set_sender(Box::new(move |message| {
async_std::task::block_on(local_sender.send(message)).unwrap();
}))
}
login_recv_loop(server_id, socket.clone(), server_state_sender.clone(), client_sender).await;
interserver_send_loop(server_id, socket.clone(), client_receiver).await;
let mut buf = [0u8; 1];
loop {
let peek = socket.peek(&mut buf).await;
match peek {
Ok(len) if len == 0 => {
break
},
_ => {
}
}
}
}
}))
}

View File

@ -0,0 +1,45 @@
mod client;
mod interserver;
use std::pin::Pin;
use futures::future::{Future, join_all, FutureExt};
use async_std::sync::{Arc, Mutex};
use crate::common::mainloop::client::client_accept_mainloop;
use crate::common::mainloop::interserver::{ship_connect_mainloop, login_listen_mainloop};
pub use crate::common::mainloop::client::NetworkError;
use crate::patch::patch::PatchServerState;
use crate::login::login::LoginServerState;
use crate::login::character::CharacterServerState;
use crate::ship::ship::ShipServerState;
use crate::entity::gateway::entitygateway::EntityGateway;
pub fn patch_mainloop(patch_state: PatchServerState, patch_port: u16) -> Pin<Box<dyn Future<Output = ()>>> {
let patch_state = Arc::new(Mutex::new(patch_state));
let client_mainloop = client_accept_mainloop(patch_state, patch_port);
Box::pin(client_mainloop)
}
pub fn login_mainloop<EG: EntityGateway + 'static>(login_state: LoginServerState<EG>, login_port: u16) -> Pin<Box<dyn Future<Output = ()>>> {
let login_state = Arc::new(Mutex::new(login_state));
let client_mainloop = client_accept_mainloop(login_state, login_port);
Box::pin(client_mainloop)
}
pub fn character_mainloop<EG: EntityGateway + 'static>(character_state: CharacterServerState<EG>, character_port: u16, comm_port: u16) -> Pin<Box<dyn Future<Output = ()>>> {
let character_state = Arc::new(Mutex::new(character_state));
let client_mainloop = client_accept_mainloop(character_state.clone(), character_port);
let ship_communication_mainloop = login_listen_mainloop(character_state, comm_port);
Box::pin(join_all(vec![client_mainloop, ship_communication_mainloop]).map(|_| ()))
}
pub fn ship_mainloop<EG: EntityGateway + 'static>(ship_state: ShipServerState<EG>, ship_port: u16, comm_ip: std::net::Ipv4Addr, comm_port: u16) -> Pin<Box<dyn Future<Output = ()>>> {
let ship_state = Arc::new(Mutex::new(ship_state));
let client_mainloop = client_accept_mainloop(ship_state.clone(), ship_port);
let login_communication_mainloop = ship_connect_mainloop(ship_state, comm_ip, comm_port);
Box::pin(join_all(vec![client_mainloop, login_communication_mainloop]).map(|_| ()))
}

19
src/common/mod.rs Normal file
View File

@ -0,0 +1,19 @@
pub mod cipherkeys;
pub mod serverstate;
pub mod mainloop;
pub mod leveltable;
pub mod interserver;
// https://www.reddit.com/r/rust/comments/33xhhu/how_to_create_an_array_of_structs_that_havent/
#[macro_export]
macro_rules! init_array(
($ty:ty, $len:expr, $val:expr) => (
{
let mut array: [$ty; $len] = unsafe { std::mem::uninitialized() };
for i in array.iter_mut() {
unsafe { ::std::ptr::write(i, $val); }
}
array
}
)
);

View File

@ -4,10 +4,9 @@ use libpso::crypto::PSOCipher;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub struct ClientId(pub usize); pub struct ClientId(pub usize);
pub enum OnConnect<S: SendServerPacket, C: PSOCipher> { pub enum OnConnect<S: SendServerPacket> {
Packet(S), Packet(S),
Cipher(C, C), Cipher((Box<dyn PSOCipher + Send + Sync>, Box<dyn PSOCipher + Send + Sync>)),
//Cipher((Box<dyn PSOCipher + Send + Sync>, Box<dyn PSOCipher + Send + Sync>)),
} }
pub trait RecvServerPacket: Sized + Sync { pub trait RecvServerPacket: Sized + Sync {
@ -20,14 +19,14 @@ pub trait SendServerPacket: Sized + Sync {
// TODO: rename this trait, this isn't the state but the actionability of the state re: the client // TODO: rename this trait, this isn't the state but the actionability of the state re: the client
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait ServerState: Clone { pub trait ServerState {
type SendPacket: SendServerPacket; type SendPacket: SendServerPacket;
type RecvPacket: RecvServerPacket; type RecvPacket: RecvServerPacket;
type Cipher: PSOCipher;
type PacketError; type PacketError;
async fn on_connect(&mut self, id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, Self::PacketError>; async fn on_connect(&mut self, id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket>>, Self::PacketError>;
async fn handle(&mut self, id: ClientId, pkt: Self::RecvPacket) -> Result<Vec<(ClientId, Self::SendPacket)>, Self::PacketError>; async fn handle(&mut self, id: ClientId, pkt: &Self::RecvPacket)
//-> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)>>, Self::PacketError>; -> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)> + Send>, Self::PacketError>;
async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, Self::SendPacket)>, Self::PacketError>; async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, Self::SendPacket)>, Self::PacketError>;
} }

View File

@ -1,17 +0,0 @@
[package]
name = "drops"
version = "0.1.0"
edition = "2021"
[dependencies]
entity = { workspace = true }
maps = { workspace = true }
stats = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
serde = { workspace = true }
enum-utils = { workspace = true }
toml = { workspace = true }
chrono = { workspace = true }

View File

@ -1,298 +0,0 @@
#![allow(dead_code, unused_must_use)]
// TODO: there is some structure duplication that occurs here:
// the rare and box tables instantiate their own copies of the
// generic drop tables as they need them to apply their modifiers
// to their drops
pub mod rare_drop_table;
mod generic_weapon;
mod generic_armor;
mod generic_shield;
mod generic_unit;
mod tool_table;
mod tech_table;
mod box_drop_table;
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use std::io::Read;
use serde::{Serialize, Deserialize};
use rand::{Rng, SeedableRng};
use maps::monster::MonsterType;
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use entity::character::SectionID;
use crate::generic_weapon::GenericWeaponTable;
use crate::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable;
use crate::generic_unit::GenericUnitTable;
use crate::tool_table::ToolTable;
use crate::rare_drop_table::RareDropTable;
use crate::box_drop_table::BoxDropTable;
use maps::object::MapObject;
use entity::item::{ItemType, weapon, armor, shield, unit, mag, tool, tech, esweapon};
fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
let mut path = PathBuf::from("data/drops/");
path.push(episode.to_string());
path.push(difficulty.to_string().to_lowercase());
path.push(section_id.to_string().to_lowercase());
path.push(filename);
path
}
pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
let path = data_file_path(episode, difficulty, section_id, filename);
let mut f = File::open(path).unwrap();
let mut s = String::new();
f.read_to_string(&mut s);
toml::from_str::<T>(s.as_str()).unwrap()
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub enum MonsterDropType {
#[serde(rename = "weapon")]
Weapon,
#[serde(rename = "armor")]
Armor,
#[serde(rename = "shield")]
Shield,
#[serde(rename = "unit")]
Unit,
#[serde(rename = "none")]
None,
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub struct MonsterDropStats {
pub dar: u32,
pub drop_type: MonsterDropType,
pub min_meseta: u32,
pub max_meseta: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ItemDropType {
Weapon(weapon::Weapon),
Armor(armor::Armor),
Shield(shield::Shield),
Unit(unit::Unit),
Tool(tool::Tool),
//Tools(Vec<tool::Tool>),
TechniqueDisk(tech::TechniqueDisk),
Mag(mag::Mag),
Meseta(u32),
}
impl ItemDropType {
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
match item_type {
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ItemDrop {
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
pub item: ItemDropType,
}
pub trait DropTable {
fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType>;
fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType>;
}
pub struct StandardDropTable {
monster_stats: HashMap<MonsterType, MonsterDropStats>,
rare_table: RareDropTable,
weapon_table: GenericWeaponTable,
armor_table: GenericArmorTable,
shield_table: GenericShieldTable,
unit_table: GenericUnitTable,
tool_table: ToolTable,
box_table: BoxDropTable,
rng: rand_chacha::ChaCha20Rng,
}
impl StandardDropTable {
#[allow(clippy::new_ret_no_self)]
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
Box::new(StandardDropTable {
monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
rare_table: RareDropTable::new(episode, difficulty, section_id),
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
armor_table: GenericArmorTable::new(episode, difficulty, section_id),
shield_table: GenericShieldTable::new(episode, difficulty, section_id),
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: 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,
}
}
fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option<ItemDropType> {
Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1)))
}
fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
match monster.drop_type {
MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng),
MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng),
MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng),
MonsterDropType::None => None,
}
}
}
impl DropTable for StandardDropTable {
fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
let monster_stat = *self.monster_stats.get(monster)?;
let drop_anything = self.rng.gen_range(0, 100);
if drop_anything > monster_stat.dar {
return None;
}
if let Some(item) = self.rare_table.get_drop(map_area, monster, &mut self.rng) {
return Some(item);
}
let drop_type = self.rng.gen_range(0, 3);
match drop_type {
0 => {
self.generate_meseta(&monster_stat)
},
1 => {
self.tool_table.get_drop(map_area, &mut self.rng)
},
2 => {
self.generate_typed_drop(map_area, &monster_stat)
},
_ => panic!()
}
}
fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
self.box_table.get_drop(map_area, object, &mut self.rng)
}
}
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) -> Box<dyn DropTable + Send + Sync> {
Box::new(StandardDropTable {
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::*;
use rand::seq::IteratorRandom;
#[test]
fn test_initializing_drop_table() {
let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap();
let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate]
.into_iter().choose(&mut rng).unwrap();
let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]
.into_iter().choose(&mut rng).unwrap();
DropTable::new(episode, difficulty, section_id);
}
}

View File

@ -1,23 +0,0 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
[dependencies]
libpso = { workspace = true }
maps = { workspace = true }
chrono = { workspace = true }
anyhow = { workspace = true }
async-std = { workspace = true }
sqlx = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true }
async-trait = { workspace = true }
enum-utils = { workspace = true }
derive_more = { workspace = true }
refinery = { workspace = true }
lazy_static = { workspace = true }
futures = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
toml = { workspace = true }

View File

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

View File

@ -1,31 +1,35 @@
use std::convert::From;
use thiserror::Error; use thiserror::Error;
use futures::future::{Future, BoxFuture}; use futures::Future;
use crate::account::*; use crate::entity::account::*;
use crate::character::*; use crate::entity::character::*;
use crate::item::*; use crate::entity::item::*;
use crate::room::*;
// TODO: better granularity? // TODO: better granularity?
//#[derive(Error, Debug)]
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("")]
pub enum GatewayError { pub enum GatewayError {
#[error("unknown error")]
Error, Error,
#[error("postgres error {0}")]
PgError(#[from] sqlx::Error) PgError(#[from] sqlx::Error)
} }
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait EntityGateway: Send + Sync { pub trait EntityGateway: Send + Sync {
type Transaction<'t>: EntityGatewayTransaction + Clone where Self: 't; async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
{
unimplemented!();
}
fn with_transaction<'a, F, Fut, R>(&'a mut self, _func: F) -> BoxFuture<'a, Result<R, anyhow::Error>> async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result<R, E>
where where
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a, Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a, F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
R: Send, R: Send,
E: From<GatewayError>,
Self: Sized Self: Sized
{ {
unimplemented!(); unimplemented!();
@ -96,15 +100,25 @@ pub trait EntityGateway: Send + Sync {
unimplemented!(); unimplemented!();
} }
async fn add_weapon_modifier(&mut self, _item_id: &ItemEntityId, _modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { async fn add_weapon_modifier(&mut self, _item_id: &ItemEntityId, _modifier: weapon::WeaponModifier) -> Result<(), GatewayError> {
unimplemented!(); unimplemented!();
} }
async fn add_unit_modifier(&mut self, _item_id: &ItemEntityId, _modifier: unit::UnitModifier) -> Result<(), GatewayError> {
unimplemented!();
}
/*
async fn get_items_by_character(&self, _char_id: &CharacterEntityId) -> Result<Vec<ItemEntity>, GatewayError> {
unimplemented!();
}
*/
async fn get_character_inventory(&mut self, _char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> { async fn get_character_inventory(&mut self, _char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
unimplemented!(); unimplemented!();
} }
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> { async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
unimplemented!(); unimplemented!();
} }
@ -112,7 +126,7 @@ pub trait EntityGateway: Send + Sync {
unimplemented!(); unimplemented!();
} }
async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _bank: &BankEntity, _bank_identifier: &BankIdentifier) -> Result<(), GatewayError> { async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _inventory: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> {
unimplemented!(); unimplemented!();
} }
@ -132,41 +146,27 @@ pub trait EntityGateway: Send + Sync {
unimplemented!(); unimplemented!();
} }
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> { async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName) -> Result<Meseta, GatewayError> {
unimplemented!(); unimplemented!();
} }
async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier, _amount: Meseta) -> Result<(), GatewayError> { async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName, _amount: Meseta) -> Result<(), GatewayError> {
unimplemented!(); unimplemented!();
} }
async fn create_trade(&mut self, _char_id1: &CharacterEntityId, _char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> { async fn create_trade(&mut self, _char_id1: &CharacterEntityId, _char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
unimplemented!(); unimplemented!();
} }
async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> {
unimplemented!();
}
async fn create_room(&mut self, _room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
unimplemented!();
}
async fn add_room_note(&mut self, _room_id: RoomEntityId, _note: RoomNote) -> Result<(), GatewayError> {
unimplemented!();
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait EntityGatewayTransaction: Send + Sync + Sized { pub trait EntityGatewayTransaction: Send + Sync {
type ParentGateway: EntityGateway + Clone; fn gateway(&mut self) -> &mut dyn EntityGateway {
fn gateway(&mut self) -> &mut Self::ParentGateway {
unimplemented!() unimplemented!()
} }
async fn commit(self) -> Result<(), GatewayError> { async fn commit(self: Box<Self>) -> Result<(), GatewayError> {
unimplemented!() unimplemented!()
} }
} }

View File

@ -0,0 +1,547 @@
use std::collections::BTreeMap;
use std::convert::TryInto;
use futures::Future;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use std::sync::{Arc, Mutex};
// TODO: implement multiple banks
pub struct InMemoryGatewayTransaction<'a> {
working_gateway: InMemoryGateway,
original_gateway: &'a mut InMemoryGateway,
}
#[async_trait::async_trait]
impl<'a> EntityGatewayTransaction for InMemoryGatewayTransaction<'a> {
fn gateway(&mut self) -> &mut dyn EntityGateway {
&mut self.working_gateway
}
async fn commit(mut self: Box<Self>) -> Result<(), GatewayError> {
self.original_gateway.users.lock().unwrap().clear();
self.original_gateway.users.lock().unwrap().extend(self.working_gateway.users.lock().unwrap().clone());
self.original_gateway.user_settings.lock().unwrap().clear();
self.original_gateway.user_settings.lock().unwrap().extend(self.working_gateway.user_settings.lock().unwrap().clone());
self.original_gateway.characters.lock().unwrap().clear();
self.original_gateway.characters.lock().unwrap().extend(self.working_gateway.characters.lock().unwrap().clone());
self.original_gateway.character_meseta.lock().unwrap().clear();
self.original_gateway.character_meseta.lock().unwrap().extend(self.working_gateway.character_meseta.lock().unwrap().clone());
self.original_gateway.bank_meseta.lock().unwrap().clear();
self.original_gateway.bank_meseta.lock().unwrap().extend(self.working_gateway.bank_meseta.lock().unwrap().clone());
self.original_gateway.items.lock().unwrap().clear();
self.original_gateway.items.lock().unwrap().extend(self.working_gateway.items.lock().unwrap().clone());
self.original_gateway.inventories.lock().unwrap().clear();
self.original_gateway.inventories.lock().unwrap().extend(self.working_gateway.inventories.lock().unwrap().clone());
self.original_gateway.banks.lock().unwrap().clear();
self.original_gateway.banks.lock().unwrap().extend(self.working_gateway.banks.lock().unwrap().clone());
self.original_gateway.equips.lock().unwrap().clear();
self.original_gateway.equips.lock().unwrap().extend(self.working_gateway.equips.lock().unwrap().clone());
self.original_gateway.mag_modifiers.lock().unwrap().clear();
self.original_gateway.mag_modifiers.lock().unwrap().extend(self.working_gateway.mag_modifiers.lock().unwrap().clone());
self.original_gateway.weapon_modifiers.lock().unwrap().clear();
self.original_gateway.weapon_modifiers.lock().unwrap().extend(self.working_gateway.weapon_modifiers.lock().unwrap().clone());
self.original_gateway.trades.lock().unwrap().clear();
self.original_gateway.trades.lock().unwrap().extend(self.working_gateway.trades.lock().unwrap().clone());
Ok(())
}
}
#[derive(Clone)]
pub struct InMemoryGateway {
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
bank_meseta: Arc<Mutex<BTreeMap<(CharacterEntityId, BankName), Meseta>>>,
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
unit_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<unit::UnitModifier>>>>,
trades: Arc<Mutex<Vec<TradeEntity>>>,
unit_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<unit::UnitModifier>>>>,
}
impl Default for InMemoryGateway {
fn default() -> InMemoryGateway {
InMemoryGateway {
users: Arc::new(Mutex::new(BTreeMap::new())),
user_settings: Arc::new(Mutex::new(BTreeMap::new())),
characters: Arc::new(Mutex::new(BTreeMap::new())),
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
items: Arc::new(Mutex::new(BTreeMap::new())),
inventories: Arc::new(Mutex::new(BTreeMap::new())),
banks: Arc::new(Mutex::new(BTreeMap::new())),
equips: Arc::new(Mutex::new(BTreeMap::new())),
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
unit_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
trades: Arc::new(Mutex::new(Vec::new())),
unit_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
}
}
}
impl InMemoryGateway {
fn apply_modifiers(&self, inventory: InventoryEntity ) -> InventoryEntity {
let items = self.items.lock().unwrap();
let inventory_items = inventory.items.into_iter()
.map(|item| {
item.map_individual(|mut item| {
item.item = match item.item {
ItemDetail::Weapon(mut weapon) => {
if let Some(weapon_modifiers) = self.weapon_modifiers.lock().unwrap().get(&item.id) {
if weapon.weapon.has_counter() {
weapon.kills = Some(0);
}
for weapon_modifier in weapon_modifiers.iter() {
weapon.apply_modifier(weapon_modifier);
}
}
ItemDetail::Weapon(weapon)
},
ItemDetail::Mag(mag) => {
let mut mag = mag::Mag::baby_mag(mag.color as u16);
if let Some(mag_modifiers) = self.mag_modifiers.lock().unwrap().get(&item.id) {
for mag_modifier in mag_modifiers.iter() {
match mag_modifier {
mag::MagModifier::FeedMag {food} => {
if let Some(mag_feed) = items.get(food) {
if let ItemDetail::Tool(mag_feed) = mag_feed.item {
mag.feed(mag_feed.tool)
}
}
},
mag::MagModifier::OwnerChange(class, section_id) => {
mag.change_owner(*class, *section_id)
},
mag::MagModifier::MagCell(mag_cell_id) => {
if let Some(mag_cell) = items.get(mag_cell_id) {
if let ItemDetail::Tool(mag_cell) = mag_cell.item {
mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()).unwrap()
}
}
},
_ => {}
}
}
}
ItemDetail::Mag(mag)
},
ItemDetail::Unit(mut unit) => {
if let Some(unit_modifiers) = self.unit_modifiers.lock().unwrap().get(&item.id) {
if unit.unit.has_counter() {
unit.kills = Some(0);
}
for unit_modifier in unit_modifiers.iter() {
unit.apply_modifier(unit_modifier);
}
}
ItemDetail::Unit(unit)
},
_ => {
item.item
}
};
item
})
})
.collect();
InventoryEntity::new(inventory_items)
}
}
#[async_trait::async_trait]
impl EntityGateway for InMemoryGateway {
async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
{
let working_gateway = {
let users = self.users.lock().unwrap().clone();
let user_settings = self.user_settings.lock().unwrap().clone();
let characters = self.characters.lock().unwrap().clone();
let character_meseta = self.character_meseta.lock().unwrap().clone();
let bank_meseta = self.bank_meseta.lock().unwrap().clone();
let items = self.items.lock().unwrap().clone();
let inventories = self.inventories.lock().unwrap().clone();
let banks = self.banks.lock().unwrap().clone();
let equips = self.equips.lock().unwrap().clone();
let mag_modifiers = self.mag_modifiers.lock().unwrap().clone();
let weapon_modifiers = self.weapon_modifiers.lock().unwrap().clone();
let trades = self.trades.lock().unwrap().clone();
InMemoryGateway {
users: Arc::new(Mutex::new(users)),
user_settings: Arc::new(Mutex::new(user_settings)),
characters: Arc::new(Mutex::new(characters)),
character_meseta: Arc::new(Mutex::new(character_meseta)),
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
items: Arc::new(Mutex::new(items)),
inventories: Arc::new(Mutex::new(inventories)),
banks: Arc::new(Mutex::new(banks)),
equips: Arc::new(Mutex::new(equips)),
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
trades: Arc::new(Mutex::new(trades)),
}
};
Ok(Box::new(InMemoryGatewayTransaction {
working_gateway,
original_gateway: self,
}))
}
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
where
Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
R: Send,
E: From<GatewayError>,
{
let users = self.users.lock().unwrap().clone();
let user_settings = self.user_settings.lock().unwrap().clone();
let characters = self.characters.lock().unwrap().clone();
let character_meseta = self.character_meseta.lock().unwrap().clone();
let bank_meseta = self.bank_meseta.lock().unwrap().clone();
let items = self.items.lock().unwrap().clone();
let inventories = self.inventories.lock().unwrap().clone();
let banks = self.banks.lock().unwrap().clone();
let equips = self.equips.lock().unwrap().clone();
let mag_modifiers = self.mag_modifiers.lock().unwrap().clone();
let weapon_modifiers = self.weapon_modifiers.lock().unwrap().clone();
let trades = self.trades.lock().unwrap().clone();
let working_gateway = InMemoryGateway {
users: Arc::new(Mutex::new(users)),
user_settings: Arc::new(Mutex::new(user_settings)),
characters: Arc::new(Mutex::new(characters)),
character_meseta: Arc::new(Mutex::new(character_meseta)),
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
items: Arc::new(Mutex::new(items)),
inventories: Arc::new(Mutex::new(inventories)),
banks: Arc::new(Mutex::new(banks)),
equips: Arc::new(Mutex::new(equips)),
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
trades: Arc::new(Mutex::new(trades)),
};
let transaction = Box::new(InMemoryGatewayTransaction {
working_gateway,
original_gateway: self,
});
let (transaction, result) = func(transaction).await?;
transaction.commit().await?;
Ok(result)
}
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
let mut users = self.users.lock().unwrap();
let id = users
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let user = UserAccountEntity {
id: UserAccountId(id),
username: user.username,
password: user.password,
guildcard: user.guildcard,
team_id: user.team_id,
banned_until: user.banned_until,
muted_until: user.muted_until,
created_at: chrono::Utc::now(),
flags: user.flags,
activated: user.activated,
at_login: false,
at_character: false,
at_ship: false,
};
users.insert(user.id, user.clone());
Ok(user)
}
async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
let users = self.users.lock().unwrap();
users.get(&id).cloned().ok_or(GatewayError::Error)
}
async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> {
let users = self.users.lock().unwrap();
users
.iter()
.find(|(_, k)| k.username == username)
.map(|(_, k)| k.clone())
.ok_or(GatewayError::Error)
}
async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> {
let mut users = self.users.lock().unwrap();
users.insert(user.id, user.clone());
Ok(())
}
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> {
let mut user_settings = self.user_settings.lock().unwrap();
let id = user_settings
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let new_settings = UserSettingsEntity {
id: UserSettingsId(id),
user_id: settings.user_id,
settings: settings.settings,
};
user_settings.insert(new_settings.id, new_settings.clone());
Ok(new_settings)
}
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
let user_settings = self.user_settings.lock().unwrap();
user_settings
.iter()
.find(|(_, k)| k.user_id == user.id)
.map(|(_, k)| k.clone())
.ok_or(GatewayError::Error)
}
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
let characters = self.characters.lock().unwrap();
const NONE: Option<CharacterEntity> = None;
let mut chars = [NONE; 4];
characters
.iter()
.filter(|(_, c)| c.user_id == user.id)
.for_each(|(_, c)| chars[c.slot as usize] = Some(c.clone()));
Ok(chars)
}
async fn create_character(&mut self, character: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
let mut characters = self.characters.lock().unwrap();
let id = characters
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let new_character = CharacterEntity {
id: CharacterEntityId(id),
user_id: character.user_id,
slot: character.slot,
name: character.name,
exp: character.exp,
char_class: character.char_class,
section_id: character.section_id,
appearance: character.appearance,
techs: character.techs,
config: character.config,
info_board: character.info_board,
guildcard: character.guildcard,
materials: character.materials,
tech_menu: character.tech_menu,
option_flags: character.option_flags,
keyboard_config: character.keyboard_config,
gamepad_config: character.gamepad_config,
};
characters.insert(new_character.id, new_character.clone());
Ok(new_character)
}
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
let mut characters = self.characters.lock().unwrap();
characters.insert(char.id, char.clone());
Ok(())
}
async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
Ok(GuildCardDataEntity::new(user.id))
}
async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
let mut items = self.items.lock().unwrap();
let id = items
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let new_item = ItemEntity {
id: ItemEntityId(id),
item: item.item,
};
items.insert(ItemEntityId(id), new_item.clone());
Ok(new_item)
}
async fn add_item_note(&mut self, _item_id: &ItemEntityId, _item_note: ItemNote) -> Result<(), GatewayError> {
Ok(())
}
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> {
self.mag_modifiers.lock().unwrap()
.entry(*mag_item_id)
.or_insert_with(Vec::new)
.push(mag::MagModifier::FeedMag {
food: *tool_item_id
});
Ok(())
}
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> {
self.mag_modifiers.lock().unwrap()
.entry(*mag_item_id)
.or_insert_with(Vec::new)
.push(mag::MagModifier::OwnerChange(character.char_class, character.section_id));
Ok(())
}
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> {
self.mag_modifiers.lock().unwrap()
.entry(*mag_item_id)
.or_insert_with(Vec::new)
.push(mag::MagModifier::MagCell(*mag_cell_id));
Ok(())
}
async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> {
self.weapon_modifiers.lock().unwrap()
.entry(*item_id)
.or_insert_with(Vec::new)
.push(modifier);
Ok(())
}
async fn add_unit_modifier(&mut self, item_id: &ItemEntityId, modifier: unit::UnitModifier) -> Result<(), GatewayError> {
self.unit_modifiers.lock().unwrap()
.entry(*item_id)
.or_insert_with(Vec::new)
.push(modifier);
Ok(())
}
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
let inventories = self.inventories.lock().unwrap();
Ok(inventories
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, inv)| inv.clone())
.map(|inv| self.apply_modifiers(inv))
.unwrap_or_default())
}
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
let banks = self.banks.lock().unwrap();
Ok(banks
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, b)| b.clone())
.unwrap_or_default())
}
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
let mut inventories = self.inventories.lock().unwrap();
inventories.insert(*char_id, inventory.clone());
Ok(())
}
// TOOD: impl bank name
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> {
let mut banks = self.banks.lock().unwrap();
banks.insert(*char_id, bank.clone());
Ok(())
}
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
let equips = self.equips.lock().unwrap();
Ok(equips
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, inv)| inv.clone())
.unwrap_or_default())
}
async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equipped: &EquippedEntity) -> Result<(), GatewayError> {
let mut equips = self.equips.lock().unwrap();
equips.insert(*char_id, equipped.clone());
Ok(())
}
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
let mut character_meseta = self.character_meseta.lock().unwrap();
character_meseta.insert(*char_id, meseta);
Ok(())
}
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
let mut character_meseta = self.character_meseta.lock().unwrap();
if let Some(meseta) = character_meseta.get_mut(char_id) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
let mut bank_meseta = self.bank_meseta.lock().unwrap();
bank_meseta.insert((*char_id, bank.clone()), meseta);
Ok(())
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
let mut bank_meseta = self.bank_meseta.lock().unwrap();
if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank.clone())) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
}
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
let mut trades = self.trades.lock().unwrap();
let id = trades.len() as u32;
let new_trade = TradeEntity {
id: TradeId(id),
character1: *char_id1,
character2: *char_id2,
};
trades.push(new_trade.clone());
Ok(new_trade)
}
async fn increment_kill_counter(&mut self, item_id: &ItemEntityId) -> Result<(), GatewayError> {
if let Some(item_entity) = self.items.lock().unwrap().get_mut(item_id) {
item_entity.increase_kill_counter();
}
Ok(())
}
async fn get_kill_counter() {
println!("src/entity/gateway/inmemory.rs::get_kill_counter() - unimplemented!");
}
async fn set_kill_counter() {
println!("src/entity/gateway/inmemory.rs::set_kill_counter() - unimplemented!");
}
}

View File

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

View File

@ -0,0 +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,
);

View File

@ -0,0 +1,3 @@
use refinery::include_migration_mods;
include_migration_mods!("src/entity/gateway/postgres/migrations");

View File

@ -4,13 +4,10 @@ use std::convert::Into;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use libpso::character::settings; use libpso::character::settings;
use libpso::util::vec_to_array; use libpso::util::vec_to_array;
use crate::account::*; use crate::entity::account::*;
use crate::character::*; use crate::entity::character::*;
use crate::item::*; use crate::entity::item::*;
use crate::room::*; use crate::ship::map::MapArea;
use maps::area::MapArea;
use maps::room::{Episode, Difficulty};
use maps::monster::MonsterType;
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount { pub struct PgUserAccount {
@ -52,8 +49,8 @@ pub struct PgUserSettings {
id: i32, id: i32,
user_account: i32, user_account: i32,
blocked_users: Vec<u8>, //[u32; 0x1E], blocked_users: Vec<u8>, //[u32; 0x1E],
key_config: Vec<u8>, //[u8; 0x16C], keyboard_config: Vec<u8>, //[u8; 0x16C],
joystick_config: Vec<u8>, //[u8; 0x38], gamepad_config: Vec<u8>, //[u8; 0x38],
option_flags: i32, option_flags: i32,
shortcuts: Vec<u8>, //[u8; 0xA40], shortcuts: Vec<u8>, //[u8; 0xA40],
symbol_chats: Vec<u8>, //[u8; 0x4E0], symbol_chats: Vec<u8>, //[u8; 0x4E0],
@ -67,8 +64,8 @@ impl From<PgUserSettings> for UserSettingsEntity {
user_id: UserAccountId(other.user_account as u32), user_id: UserAccountId(other.user_account as u32),
settings: settings::UserSettings { settings: settings::UserSettings {
blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()), blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
keyboard_config: vec_to_array(other.key_config), keyboard_config: vec_to_array(other.keyboard_config),
gamepad_config: vec_to_array(other.joystick_config), gamepad_config: vec_to_array(other.gamepad_config),
option_flags: other.option_flags as u32, option_flags: other.option_flags as u32,
shortcuts: vec_to_array(other.shortcuts), shortcuts: vec_to_array(other.shortcuts),
symbol_chats: vec_to_array(other.symbol_chats), symbol_chats: vec_to_array(other.symbol_chats),
@ -220,8 +217,8 @@ pub struct PgCharacter {
tp: i16, tp: i16,
tech_menu: Vec<u8>, tech_menu: Vec<u8>,
keyboard_config: Vec<u8>,
playtime: i32, gamepad_config: Vec<u8>,
} }
impl From<PgCharacter> for CharacterEntity { impl From<PgCharacter> for CharacterEntity {
@ -247,13 +244,13 @@ impl From<PgCharacter> for CharacterEntity {
prop_y: other.prop_y, prop_y: other.prop_y,
}, },
techs: CharacterTechniques { techs: CharacterTechniques {
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t + 1)) ).collect() techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
}, },
config: CharacterConfig { config: CharacterConfig {
raw_data: vec_to_array(other.config) raw_data: vec_to_array(other.config)
}, },
info_board: CharacterInfoboard { info_board: CharacterInfoboard {
board: libpso::util::utf8_to_utf16_array(other.infoboard), board: libpso::utf8_to_utf16_array!(other.infoboard, 172),
}, },
guildcard: CharacterGuildCard { guildcard: CharacterGuildCard {
description: other.guildcard, description: other.guildcard,
@ -271,7 +268,12 @@ impl From<PgCharacter> for CharacterEntity {
tech_menu: CharacterTechMenu { tech_menu: CharacterTechMenu {
tech_menu: vec_to_array(other.tech_menu) tech_menu: vec_to_array(other.tech_menu)
}, },
playtime: other.playtime as u32, keyboard_config: CharacterKeyboardConfig {
keyboard_config: vec_to_array(other.keyboard_config)
},
gamepad_config: CharacterGamepadConfig {
gamepad_config: vec_to_array(other.gamepad_config)
},
} }
} }
} }
@ -289,6 +291,7 @@ pub struct PgWeapon {
grind: u8, grind: u8,
attrs: HashMap<weapon::Attribute, i8>, attrs: HashMap<weapon::Attribute, i8>,
tekked: bool, tekked: bool,
kills: Option<u16>,
} }
impl From<weapon::Weapon> for PgWeapon { impl From<weapon::Weapon> for PgWeapon {
@ -299,6 +302,7 @@ impl From<weapon::Weapon> for PgWeapon {
grind: other.grind, grind: other.grind,
attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(), attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(),
tekked: other.tekked, tekked: other.tekked,
kills: other.kills,
} }
} }
} }
@ -319,6 +323,7 @@ impl From<PgWeapon> for weapon::Weapon {
grind: other.grind, grind: other.grind,
attrs, attrs,
tekked: other.tekked, tekked: other.tekked,
kills: other.kills,
} }
} }
} }
@ -390,6 +395,7 @@ impl From<PgShield> for shield::Shield {
pub struct PgUnit { pub struct PgUnit {
unit: unit::UnitType, unit: unit::UnitType,
modifier: Option<unit::UnitModifier>, modifier: Option<unit::UnitModifier>,
kills: Option<u16>,
} }
impl From<unit::Unit> for PgUnit { impl From<unit::Unit> for PgUnit {
@ -397,6 +403,7 @@ impl From<unit::Unit> for PgUnit {
PgUnit { PgUnit {
unit: other.unit, unit: other.unit,
modifier: other.modifier, modifier: other.modifier,
kills: other.kills,
} }
} }
} }
@ -406,6 +413,7 @@ impl From<PgUnit> for unit::Unit {
unit::Unit { unit::Unit {
unit: other.unit, unit: other.unit,
modifier: other.modifier, modifier: other.modifier,
kills: other.kills,
} }
} }
} }
@ -580,16 +588,6 @@ pub enum PgItemNoteDetail {
}, },
EnemyDrop { EnemyDrop {
character_id: u32, character_id: u32,
room_id: u32,
monster_type: MonsterType,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: u32,
room_id: u32,
map_area: MapArea, map_area: MapArea,
x: f32, x: f32,
y: f32, y: f32,
@ -605,19 +603,14 @@ pub enum PgItemNoteDetail {
y: f32, y: f32,
z: f32, z: f32,
}, },
Consumed { Consumed,
character_id: u32,
},
FedToMag { FedToMag {
character_id: u32,
mag: u32, mag: u32,
}, },
BoughtAtShop { BoughtAtShop {
character_id: u32, character_id: u32,
}, },
SoldToShop { SoldToShop,
character_id: u32,
},
Trade { Trade {
trade_id: u32, trade_id: u32,
character_to: u32, character_to: u32,
@ -625,14 +618,11 @@ pub enum PgItemNoteDetail {
}, },
Withdraw { Withdraw {
character_id: u32, character_id: u32,
bank: BankIdentifier, bank: String,
}, },
Deposit { Deposit {
character_id: u32, character_id: u32,
bank: BankIdentifier, bank: String,
},
FloorLimitReached {
map_area: MapArea,
} }
} }
@ -642,16 +632,8 @@ impl From<ItemNote> for PgItemNoteDetail {
ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation { ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation {
character_id: character_id.0, character_id: character_id.0,
}, },
ItemNote::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop { ItemNote::EnemyDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
character_id: character_id.0, character_id: character_id.0,
room_id: room_id.0,
monster_type,
map_area,
x,y,z,
},
ItemNote::BoxDrop{character_id, room_id, map_area, x, y, z} => PgItemNoteDetail::BoxDrop {
character_id: character_id.0,
room_id: room_id.0,
map_area, map_area,
x,y,z, x,y,z,
}, },
@ -663,19 +645,14 @@ impl From<ItemNote> for PgItemNoteDetail {
map_area, map_area,
x,y,z, x,y,z,
}, },
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed { ItemNote::Consumed => PgItemNoteDetail::Consumed,
character_id: character_id.0, ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
},
ItemNote::FedToMag{character_id, mag} => PgItemNoteDetail::FedToMag{
character_id: character_id.0,
mag: mag.0 mag: mag.0
}, },
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop { ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
character_id: character_id.0, character_id: character_id.0,
}, },
ItemNote::SoldToShop{character_id} => PgItemNoteDetail::SoldToShop { ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop,
character_id: character_id.0,
},
ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade { ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade {
trade_id: trade_id.0, trade_id: trade_id.0,
character_to: character_to.0, character_to: character_to.0,
@ -684,20 +661,15 @@ impl From<ItemNote> for PgItemNoteDetail {
ItemNote::Withdraw{character_id, bank} => { ItemNote::Withdraw{character_id, bank} => {
PgItemNoteDetail::Withdraw { PgItemNoteDetail::Withdraw {
character_id: character_id.0, character_id: character_id.0,
bank, bank: bank.0,
} }
}, },
ItemNote::Deposit{character_id, bank} => { ItemNote::Deposit{character_id, bank} => {
PgItemNoteDetail::Deposit { PgItemNoteDetail::Deposit {
character_id: character_id.0, character_id: character_id.0,
bank, bank: bank.0,
} }
}, }
ItemNote::FloorLimitReached { map_area } => {
PgItemNoteDetail::FloorLimitReached {
map_area,
}
},
} }
} }
} }
@ -706,57 +678,41 @@ impl From<PgItemNoteDetail> for ItemNote {
fn from(other: PgItemNoteDetail) -> ItemNote { fn from(other: PgItemNoteDetail) -> ItemNote {
match other { match other {
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation { PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id as u32),
}, },
PgItemNoteDetail::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => ItemNote::EnemyDrop { PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id as u32),
room_id: RoomEntityId(room_id),
monster_type,
map_area,
x,y,z,
},
PgItemNoteDetail::BoxDrop{character_id, room_id, map_area, x, y, z} => ItemNote::BoxDrop {
character_id: CharacterEntityId(character_id),
room_id: RoomEntityId(room_id),
map_area, map_area,
x,y,z, x,y,z,
}, },
PgItemNoteDetail::Pickup{character_id} => ItemNote::Pickup { PgItemNoteDetail::Pickup{character_id} => ItemNote::Pickup {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id as u32),
}, },
PgItemNoteDetail::PlayerDrop{character_id, map_area, x, y, z} => ItemNote::PlayerDrop { PgItemNoteDetail::PlayerDrop{character_id, map_area, x, y, z} => ItemNote::PlayerDrop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id as u32),
map_area, map_area,
x,y,z, x,y,z,
}, },
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed { PgItemNoteDetail::Consumed => ItemNote::Consumed,
character_id: CharacterEntityId(character_id), PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
},
PgItemNoteDetail::FedToMag{character_id, mag} => ItemNote::FedToMag{
character_id: CharacterEntityId(character_id),
mag: ItemEntityId(mag) mag: ItemEntityId(mag)
}, },
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop { PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
}, },
PgItemNoteDetail::SoldToShop{character_id} => ItemNote::SoldToShop { PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop,
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade { PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade {
trade_id: TradeId(trade_id), trade_id: TradeId(trade_id as u32),
character_to: CharacterEntityId(character_to), character_to: CharacterEntityId(character_to as u32),
character_from: CharacterEntityId(character_from), character_from: CharacterEntityId(character_from as u32),
}, },
PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw { PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id as u32),
bank, bank: BankName(bank),
}, },
PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit { PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id as u32),
bank, bank: BankName(bank),
},
PgItemNoteDetail::FloorLimitReached { map_area } => ItemNote::FloorLimitReached {
map_area,
}, },
} }
} }
@ -924,27 +880,3 @@ impl From<PgTradeEntity> for TradeEntity {
} }
} }
} }
#[derive(Debug, sqlx::FromRow, Serialize)]
pub struct PgRoomEntity {
id: i32,
name: String,
section_id: i8,
mode: i8,
episode: i8,
difficulty: i8,
}
impl From<PgRoomEntity> for RoomEntity {
fn from(other: PgRoomEntity) -> RoomEntity {
RoomEntity {
id: RoomEntityId(other.id as u32),
name: other.name,
section_id: SectionID::from(other.section_id as u8),
mode: RoomEntityMode::from(other.mode as u8),
episode: Episode::try_from(other.episode as u8).unwrap(),
difficulty: Difficulty::try_from(other.difficulty as u8).unwrap(),
}
}
}

View File

@ -1,16 +1,11 @@
// this lint is currently bugged and suggests incorrect code https://github.com/rust-lang/rust-clippy/issues/9123 use std::convert::{From, TryFrom, Into};
#![allow(clippy::explicit_auto_deref)] use futures::{Future, TryStreamExt};
use async_std::stream::StreamExt;
use std::convert::{From, Into};
use futures::future::{Future, BoxFuture};
use futures::stream::{StreamExt, FuturesOrdered};
use async_std::sync::{Arc, Mutex};
use libpso::character::guildcard; use libpso::character::guildcard;
use crate::account::*; use crate::entity::account::*;
use crate::character::*; use crate::entity::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError}; use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*; use crate::entity::item::*;
use crate::room::*;
use super::models::*; use super::models::*;
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
@ -19,30 +14,23 @@ use sqlx::Connection;
mod embedded { mod embedded {
use refinery::embed_migrations; use refinery::embed_migrations;
embed_migrations!("src/gateway/postgres/migrations"); embed_migrations!("src/entity/gateway/postgres/migrations");
} }
#[derive(Clone)]
pub struct PostgresTransaction<'t> { pub struct PostgresTransaction<'t> {
pgtransaction: Arc<Mutex<sqlx::Transaction<'t, sqlx::Postgres>>>, pgtransaction: sqlx::Transaction<'t, sqlx::Postgres>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'t> EntityGatewayTransaction for PostgresTransaction<'t> { impl<'t> EntityGatewayTransaction for PostgresTransaction<'t> {
type ParentGateway = PostgresTransaction<'t>; fn gateway(&mut self) -> &mut dyn EntityGateway {
fn gateway(&mut self) -> &mut Self::ParentGateway {
self self
} }
async fn commit(self) -> Result<(), GatewayError> { async fn commit(self: Box<Self>) -> Result<(), GatewayError> {
Arc::try_unwrap(self.pgtransaction) self.pgtransaction.commit().await?;
.unwrap()
.into_inner()
.commit()
.await?;
Ok(()) Ok(())
} }
} }
@ -65,7 +53,7 @@ impl PostgresGateway {
let pool = async_std::task::block_on(async move { let pool = async_std::task::block_on(async move {
PgPoolOptions::new() PgPoolOptions::new()
.max_connections(5) .max_connections(5)
.connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap() .connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap()
}); });
PostgresGateway { PostgresGateway {
@ -90,7 +78,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
.bind(id.0 as i32) .bind(id.0 as i32)
.fetch(conn); .fetch(conn);
weapon_modifiers.for_each(|modifier| async move { weapon_modifiers.for_each(|modifier| {
if let Ok(modifier) = modifier { if let Ok(modifier) = modifier {
weapon.apply_modifier(&modifier.modifier); weapon.apply_modifier(&modifier.modifier);
} }
@ -98,7 +86,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
ItemDetail::Weapon(weapon) ItemDetail::Weapon(weapon)
}, },
ItemDetail::Mag(mag) => { ItemDetail::Mag(mut mag) => {
let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell
from mag_modifier from mag_modifier
left join item on item.id = cast (modifier ->> 'FeedMag' as integer) left join item on item.id = cast (modifier ->> 'FeedMag' as integer)
@ -108,7 +96,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
.bind(id.0 as i32) .bind(id.0 as i32)
.fetch(conn); .fetch(conn);
let mag = mag_modifiers.fold(mag, |mut mag, modifier| async { mag_modifiers.for_each(|modifier| {
let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap(); let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap();
let modifier: mag::MagModifier = modifier.0.into(); let modifier: mag::MagModifier = modifier.0.into();
match modifier { match modifier {
@ -125,8 +113,6 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
mag.change_owner(class, section_id) mag.change_owner(class, section_id)
}, },
} }
mag
}).await; }).await;
ItemDetail::Mag(mag) ItemDetail::Mag(mag)
@ -140,31 +126,6 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
} }
} }
async fn fetch_item<T>(conn: &mut sqlx::PgConnection, item: PgInventoryItemEntity, individual: fn(ItemEntity) -> T, stacked: fn(Vec<ItemEntity>) -> T) -> Result<T, GatewayError>
{
match item {
PgInventoryItemEntity::Individual(item) => {
let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(item)
.fetch_one(&mut *conn).await
.map(|item| item.into())
.map(|item| apply_item_modifications(&mut *conn, item))?
.await;
Ok(individual(entity))
},
PgInventoryItemEntity::Stacked(items) => {
let mut stacked_item = Vec::new();
for s_item in items {
stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(s_item)
.fetch_one(&mut *conn).await
.map(|item| item.into())?)
}
Ok(stacked(stacked_item))
}
}
}
async fn create_user(conn: &mut sqlx::PgConnection, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> async fn create_user(conn: &mut sqlx::PgConnection, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError>
{ {
let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password, activated) values ($1, $2, $3, $4) returning *;") let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password, activated) values ($1, $2, $3, $4) returning *;")
@ -179,7 +140,7 @@ async fn create_user(conn: &mut sqlx::PgConnection, user: NewUserAccountEntity)
async fn get_user_by_id(conn: &mut sqlx::PgConnection, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> async fn get_user_by_id(conn: &mut sqlx::PgConnection, id: UserAccountId) -> Result<UserAccountEntity, GatewayError>
{ {
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1") let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1")
.bind(id.0 as i32) .bind(id.0)
.fetch_one(conn).await?; .fetch_one(conn).await?;
Ok(user.into()) Ok(user.into())
} }
@ -198,10 +159,10 @@ async fn save_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> R
sqlx::query("UPDATE user_accounts set username=$1, password=$2, banned=$3, muted=$4, flags=$5 where id=$6") sqlx::query("UPDATE user_accounts set username=$1, password=$2, banned=$3, muted=$4, flags=$5 where id=$6")
.bind(&user.username) .bind(&user.username)
.bind(&user.password) .bind(&user.password)
.bind(user.banned_until) .bind(&user.banned_until)
.bind(user.muted_until) .bind(&user.muted_until)
.bind(user.flags as i32) .bind(&user.flags)
.bind(user.id.0 as i32) .bind(&user.id.0)
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
} }
@ -210,7 +171,7 @@ async fn create_user_settings(conn: &mut sqlx::PgConnection, settings: NewUserSe
{ {
let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name) let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name)
values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;") values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;")
.bind(settings.user_id.0 as i32) .bind(settings.user_id.0)
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.settings.keyboard_config.to_vec()) .bind(settings.settings.keyboard_config.to_vec())
.bind(settings.settings.gamepad_config.to_vec()) .bind(settings.settings.gamepad_config.to_vec())
@ -225,7 +186,7 @@ async fn create_user_settings(conn: &mut sqlx::PgConnection, settings: NewUserSe
async fn get_user_settings_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> async fn get_user_settings_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError>
{ {
let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1") let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1")
.bind(user.id.0 as i32) .bind(user.id.0)
.fetch_one(conn).await?; .fetch_one(conn).await?;
Ok(settings.into()) Ok(settings.into())
} }
@ -236,34 +197,25 @@ async fn save_user_settings(conn: &mut sqlx::PgConnection, settings: &UserSettin
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(&settings.settings.keyboard_config.to_vec()) .bind(&settings.settings.keyboard_config.to_vec())
.bind(&settings.settings.gamepad_config.to_vec()) .bind(&settings.settings.gamepad_config.to_vec())
.bind(settings.settings.option_flags as i32) .bind(&settings.settings.option_flags)
.bind(&settings.settings.shortcuts.to_vec()) .bind(&settings.settings.shortcuts.to_vec())
.bind(&settings.settings.symbol_chats.to_vec()) .bind(&settings.settings.symbol_chats.to_vec())
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.id.0 as i32) .bind(&settings.id.0)
.execute(conn).await?; .fetch_one(conn).await?;
Ok(()) Ok(())
} }
async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError>
{ {
let q = r#"insert into player_character let q = r#"insert into player_character
(user_account, slot, name, exp, class, (user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
section_id, costume, skin, face, head, config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags)
hair, hair_r, hair_g, hair_b, prop_x,
prop_y, techs, config, infoboard, guildcard,
power, mind, def, evade, luck,
hp, tp, tech_menu, option_flags, playtime)
values values
($1, $2, $3, $4, $5, ($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)
$6, $7, $8, $9, $10,
$11, $12, $13, $14, $15,
$16, $17, $18, $19, $20,
$21, $22, $23, $24, $25,
$26, $27, $28, $29, $30)
returning *;"#; returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q) let character = sqlx::query_as::<_, PgCharacter>(q)
.bind(char.user_id.0 as i32) .bind(char.user_id.0)
.bind(char.slot as i16) .bind(char.slot as i16)
.bind(char.name) .bind(char.name)
.bind(char.exp as i32) .bind(char.exp as i32)
@ -292,7 +244,6 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
.bind(char.materials.tp as i16) .bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec()) .bind(char.tech_menu.tech_menu.to_vec())
.bind(char.option_flags as i32) .bind(char.option_flags as i32)
.bind(0)
.fetch_one(conn).await?; .fetch_one(conn).await?;
Ok(character.into()) Ok(character.into())
@ -300,17 +251,17 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError>
{ {
let stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by created_at;") let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot")
.bind(user.id.0 as i32) .bind(user.id.0)
.fetch(conn); .fetch(conn);
const NONE: Option<CharacterEntity> = None;
let mut result = [NONE; 4];
while let Some(character) = stream.try_next().await? {
let index = character.slot as usize;
result[index] = Some(character.into())
}
Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| async move { Ok(result)
if let Ok(char) = char {
let slot = char.slot as usize;
acc[slot] = Some(char.into())
}
acc
}).await)
} }
async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -> Result<(), GatewayError> async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -> Result<(), GatewayError>
@ -318,40 +269,39 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
let q = r#"update player_character set let q = r#"update player_character set
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, 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, 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 evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29
where id=$31;"#; where id=$32;"#;
sqlx::query(q) sqlx::query(q)
.bind(char.user_id.0 as i32) // $1 .bind(char.user_id.0)
.bind(char.slot as i16) // $2 .bind(char.slot as i16)
.bind(&char.name) // $3 .bind(&char.name)
.bind(char.exp as i32) // $4 .bind(char.exp as i32)
.bind(char.char_class.to_string()) // $5 .bind(char.char_class.to_string())
.bind(char.section_id.to_string()) // $6 .bind(char.section_id.to_string())
.bind(char.appearance.costume as i16) // $7 .bind(char.appearance.costume as i16)
.bind(char.appearance.skin as i16) // $8 .bind(char.appearance.skin as i16)
.bind(char.appearance.face as i16) // $9 .bind(char.appearance.face as i16)
.bind(char.appearance.head as i16) // $10 .bind(char.appearance.head as i16)
.bind(char.appearance.hair as i16) // $11 .bind(char.appearance.hair as i16)
.bind(char.appearance.hair_r as i16) // $12 .bind(char.appearance.hair_r as i16)
.bind(char.appearance.hair_g as i16) // $13 .bind(char.appearance.hair_g as i16)
.bind(char.appearance.hair_b as i16) // $14 .bind(char.appearance.hair_b as i16)
.bind(char.appearance.prop_x) // $15 .bind(char.appearance.prop_x)
.bind(char.appearance.prop_y) // $16 .bind(char.appearance.prop_y)
.bind(&char.techs.as_bytes().to_vec()) // $17 .bind(&char.techs.as_bytes().to_vec())
.bind(&char.config.as_bytes().to_vec()) // $18 .bind(&char.config.as_bytes().to_vec())
.bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) // $19 .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0)))
.bind(&char.guildcard.description) // $20 .bind(&char.guildcard.description)
.bind(char.materials.power as i16) // $21 .bind(char.materials.power as i16)
.bind(char.materials.mind as i16) // $22 .bind(char.materials.mind as i16)
.bind(char.materials.def as i16) // $23 .bind(char.materials.def as i16)
.bind(char.materials.evade as i16) // $24 .bind(char.materials.evade as i16)
.bind(char.materials.luck as i16) // $25 .bind(char.materials.luck as i16)
.bind(char.materials.hp as i16) // $26 .bind(char.materials.hp as i16)
.bind(char.materials.tp as i16) // $27 .bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec()) // $28 .bind(char.tech_menu.tech_menu.to_vec())
.bind(char.option_flags as i32) // $29 .bind(char.option_flags as i32)
.bind(char.playtime as i32) // $30 .bind(char.id.0 as i32)
.bind(char.id.0 as i32) // $31
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
} }
@ -371,7 +321,7 @@ async fn create_item(conn: &mut sqlx::PgConnection, item: NewItemEntity) -> Resu
async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError>
{ {
sqlx::query("insert into item_note(item, note) values ($1, $2)") sqlx::query("insert into item_note(item, note) values ($1, $2)")
.bind(item_id.0 as i32) .bind(item_id.0)
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note))) .bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -380,7 +330,7 @@ async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, it
async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError>
{ {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0 as i32) .bind(mag_item_id.0)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id}))) .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id})))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -389,7 +339,7 @@ async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, too
async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError>
{ {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0 as i32) .bind(mag_item_id.0)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id)))) .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id))))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -398,16 +348,16 @@ async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntit
async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError>
{ {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0 as i32) .bind(mag_item_id.0)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id)))) .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id))))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
} }
async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError>
{ {
sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);") sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);")
.bind(item_id.0 as i32) .bind(item_id.0)
.bind(sqlx::types::Json(modifier)) .bind(sqlx::types::Json(modifier))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -415,65 +365,76 @@ async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntity
async fn get_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> async fn get_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError>
{ {
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit let mut t = conn.begin().await?;
let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1") let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1")
.bind(char_id.0 as i32) .bind(char_id.0)
.fetch_one(&mut **conn.lock().await).await?; .fetch_one(&mut t).await?;
Ok(InventoryEntity::new( // TODO: inefficient
inventory.items.0 let mut real_inventory = Vec::new();
.into_iter() for inv_item in inventory.items.0.into_iter() {
.map(move |item| { match inv_item {
let conn = conn.clone(); PgInventoryItemEntity::Individual(item) => {
async move { let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
fetch_item(&mut **conn.lock().await, item, InventoryItemEntity::Individual, InventoryItemEntity::Stacked).await .bind(item)
.fetch_one(&mut t).await
.map(|item| item.into())
.map(|item| apply_item_modifications(&mut t, item))?
.await;
real_inventory.push(InventoryItemEntity::Individual(entity));
},
PgInventoryItemEntity::Stacked(items) => {
let mut stacked_item = Vec::new();
for s_item in items {
stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(s_item)
.fetch_one(&mut t).await
.map(|item| item.into())?)
} }
}) real_inventory.push(InventoryItemEntity::Stacked(stacked_item));
.collect::<FuturesOrdered<_>>() }
.collect::<Vec<_>>() }
.await }
.into_iter()
.collect::<Result<Vec<_>, _>>()?)) Ok(InventoryEntity::new(real_inventory))
} }
async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError>
{ {
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit let mut t = conn.begin().await?;
let bank = match bank_identifier { let bank = sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1 and name = $2")
BankIdentifier::Character => { .bind(char_id.0)
sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1") .bind(bank_name.0.clone())
.bind(char_id.0 as i32) .fetch_one(&mut t).await?;
.fetch_one(&mut **conn.lock().await).await? // TODO: inefficient
}, let mut real_bank = Vec::new();
BankIdentifier::Shared(bank_name) => { for bank_item in bank.items.0.into_iter() {
sqlx::query_as::<_, PgInventoryEntity>("select player_character.id as pchar, shared_bank.items as items from shared_bank match bank_item {
join player_character on shared_bank.user_account = player_character.user_account PgInventoryItemEntity::Individual(item) => {
where player_character.id = $1 and shared_bank.name = $2") let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
.bind(char_id.0 as i32) .bind(item)
.bind(&bank_name.0) .fetch_one(&mut t).await
.fetch_optional(&mut **conn.lock().await) .map(|item| item.into())
.await? .map(|item| apply_item_modifications(&mut t, item))?
.unwrap_or_else(|| PgInventoryEntity { .await;
pchar: char_id.0 as i32, real_bank.push(BankItemEntity::Individual(entity));
items: sqlx::types::Json::default(), },
}) PgInventoryItemEntity::Stacked(items) => {
} let mut stacked_item = Vec::new();
}; for s_item in items {
stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
Ok(BankEntity::new( .bind(s_item)
bank.items.0 .fetch_one(&mut t).await
.into_iter() .map(|item| item.into())
.map(move |item| { .map(|item| apply_item_modifications(&mut t, item))?
let conn = conn.clone(); .await)
async move {
fetch_item(&mut **conn.lock().await, item, BankItemEntity::Individual, BankItemEntity::Stacked).await
} }
}) real_bank.push(BankItemEntity::Stacked(stacked_item));
.collect::<FuturesOrdered<_>>() }
.collect::<Vec<_>>() }
.await }
.into_iter()
.collect::<Result<Vec<_>, _>>()?)) Ok(BankEntity::new(real_bank))
} }
async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError>
@ -492,15 +453,15 @@ async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &Charac
.collect::<Vec<_>>(); .collect::<Vec<_>>();
sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2") sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2")
.bind(char_id.0 as i32) .bind(char_id.0)
.bind(sqlx::types::Json(inventory)) .bind(sqlx::types::Json(inventory))
.execute(conn) .execute(conn)
.await?; .await?;
Ok(()) Ok(())
} }
async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError>
async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> { {
let bank = bank.items.iter() let bank = bank.items.iter()
.map(|item| { .map(|item| {
match item { match item {
@ -514,33 +475,19 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match bank_identifier { sqlx::query("insert into bank (pchar, items, name) values ($1, $2, $3) on conflict (pchar, name) do update set items = $2")
BankIdentifier::Character => { .bind(char_id.0)
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, '') on conflict (pchar, name) do update set items = $2") .bind(sqlx::types::Json(bank))
.bind(char_id.0 as i32) .bind(bank_name.0.clone())
.bind(sqlx::types::Json(bank)) .execute(conn)
.execute(conn) .await?;
.await?;
},
BankIdentifier::Shared(bank_name) => {
sqlx::query("insert into shared_bank (user_account, items, name)
select player_character.user_account, $2, $3 from player_character
where player_character.id = $1
on conflict (user_account, name) do update set items = $2;")
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(bank))
.bind(&bank_name.0)
.execute(conn)
.await?;
}
}
Ok(()) Ok(())
} }
async fn get_character_equips(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> async fn get_character_equips(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError>
{ {
let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1") let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1")
.bind(char_id.0 as i32) .bind(char_id.0)
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
@ -551,7 +498,7 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character
{ {
sqlx::query(r#"insert into equipped (pchar, weapon, armor, shield, unit0, unit1, unit2, unit3, mag) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) sqlx::query(r#"insert into equipped (pchar, weapon, armor, shield, unit0, unit1, unit2, unit3, mag) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
on conflict (pchar) do update set weapon=$2, armor=$3, shield=$4, unit0=$5, unit1=$6, unit2=$7, unit3=$8, mag=$9"#) on conflict (pchar) do update set weapon=$2, armor=$3, shield=$4, unit0=$5, unit1=$6, unit2=$7, unit3=$8, mag=$9"#)
.bind(char_id.0 as i32) .bind(char_id.0)
.bind(equips.weapon.map(|i| i.0 as i32)) .bind(equips.weapon.map(|i| i.0 as i32))
.bind(equips.armor.map(|i| i.0 as i32)) .bind(equips.armor.map(|i| i.0 as i32))
.bind(equips.shield.map(|i| i.0 as i32)) .bind(equips.shield.map(|i| i.0 as i32))
@ -565,11 +512,10 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character
Ok(()) Ok(())
} }
async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError>
{ {
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2") sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2")
.bind(char_id.0 as i32) .bind(char_id.0)
.bind(meseta.0 as i32) .bind(meseta.0 as i32)
.execute(conn) .execute(conn)
.await?; .await?;
@ -580,125 +526,68 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
{ {
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
struct PgMeseta(i32); struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#) let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#)
.bind(char_id.0 as i32) .bind(char_id.0)
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
Ok(Meseta(meseta.0 as u32)) Ok(Meseta(meseta.0 as u32))
} }
async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError>
{ {
match bank_identifier { sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2")
BankIdentifier::Character => { .bind(char_id.0)
sqlx::query("insert into bank_meseta values ($1, '', $2) on conflict (pchar, bank) do update set meseta = $2") .bind(meseta.0 as i32)
.bind(char_id.0 as i32) .bind(bank.0.clone())
.bind(meseta.0 as i32) .execute(conn)
.execute(conn) .await?;
.await?;
},
BankIdentifier::Shared(bank_name) => {
sqlx::query("insert into shared_bank_meseta (user_account, name, meseta)
select player_character.user_account, $2, $3 from player_character
where player_character.id = $1
on conflict (user_account, name) do update set meseta = $3")
.bind(char_id.0 as i32)
.bind(&bank_name.0)
.bind(meseta.0 as i32)
.execute(conn)
.await?;
}
}
Ok(()) Ok(())
} }
async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError>
{ {
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
struct PgMeseta(i32); struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1 and bank = $2"#)
let meseta = match bank_identifier { .bind(char_id.0)
BankIdentifier::Character => { .bind(bank.0.clone())
sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1"#) .fetch_one(conn)
.bind(char_id.0 as i32) .await?;
.fetch_one(conn)
.await?
},
BankIdentifier::Shared(bank_name) => {
sqlx::query_as::<_, PgMeseta>(r#"select shared_bank_meseta.meseta from shared_bank_meseta
join player_character on shared_bank_meseta.user_account = player_character.user_account
where player_character.id = $1 and shared_bank_meseta.name = $2"#)
.bind(char_id.0 as i32)
.bind(&bank_name.0)
.fetch_optional(conn)
.await?
.unwrap_or(PgMeseta(0))
}
};
Ok(Meseta(meseta.0 as u32)) Ok(Meseta(meseta.0 as u32))
} }
async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError>
{ {
let trade = sqlx::query_as::<_, PgTradeEntity>(r#"insert into trades (character1, character2) values ($1, $2) returning *;"#) let trade = sqlx::query_as::<_, PgTradeEntity>(r#"insert into trades (character1, character2) values ($1, $2) returning *;"#)
.bind(char_id1.0 as i32) .bind(char_id1.0)
.bind(char_id2.0 as i32) .bind(char_id2.0)
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
Ok(trade.into()) Ok(trade.into())
} }
async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError>
{
sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#)
.bind(char_id.0 as i32)
.bind(playtime as i32)
.execute(conn)
.await?;
Ok(())
}
async fn create_room(conn: &mut sqlx::PgConnection, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
sqlx::query_as::<_, PgRoomEntity>("insert into room (name, section_id, mode, episode, difficulty) values ($1, $2, $3, $4, $5) returning *")
.bind(room.name)
.bind(u8::from(room.section_id) as i8)
.bind(u8::from(room.mode) as i8)
.bind(u8::from(room.episode) as i8)
.bind(u8::from(room.difficulty) as i8)
.fetch_one(conn)
.await
.map(|room| room.into())
.map_err(|err| err.into())
}
async fn add_room_note(conn: &mut sqlx::PgConnection, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
sqlx::query("insert into room_note (room, note) values ($1, $2)")
.bind(room_id.0 as i32)
.bind(sqlx::types::Json(note))
.execute(conn)
.await?;
Ok(())
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for PostgresGateway { impl EntityGateway for PostgresGateway {
type Transaction<'t> = PostgresTransaction<'t> where Self: 't; async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
where
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
R: Send,
{ {
Box::pin(async move { Ok(Box::new(PostgresTransaction {
let transaction = PostgresTransaction { pgtransaction: self.pool.begin().await?,
pgtransaction: Arc::new(Mutex::new(self.pool.begin().await?)) }))
}; }
let (transaction, result) = func(transaction).await?;
transaction.commit().await?; async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
Ok(result) where
}) Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
R: Send,
E: From<GatewayError>,
{
let transaction = Box::new(PostgresTransaction {
pgtransaction: self.pool.begin().await.map_err(|_| ()).unwrap()
});
let (transaction, result) = func(transaction).await.map_err(|_| ()).unwrap();
transaction.commit().await.map_err(|_| ()).unwrap();
Ok(result)
} }
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> { async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
@ -769,7 +658,7 @@ impl EntityGateway for PostgresGateway {
use_mag_cell(&mut *self.pool.acquire().await?, mag_item_id, mag_cell_id).await use_mag_cell(&mut *self.pool.acquire().await?, mag_item_id, mag_cell_id).await
} }
async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> {
add_weapon_modifier(&mut *self.pool.acquire().await?, item_id, modifier).await add_weapon_modifier(&mut *self.pool.acquire().await?, item_id, modifier).await
} }
@ -777,16 +666,16 @@ impl EntityGateway for PostgresGateway {
get_character_inventory(&mut *self.pool.acquire().await?, char_id).await get_character_inventory(&mut *self.pool.acquire().await?, char_id).await
} }
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> { async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError> {
get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_identifier).await get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_name).await
} }
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> { async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
set_character_inventory(&mut *self.pool.acquire().await?, char_id, inventory).await set_character_inventory(&mut *self.pool.acquire().await?, char_id, inventory).await
} }
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> { async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> {
set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_identifier).await set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_name).await
} }
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> { async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
@ -805,74 +694,60 @@ impl EntityGateway for PostgresGateway {
get_character_meseta(&mut *self.pool.acquire().await?, char_id).await get_character_meseta(&mut *self.pool.acquire().await?, char_id).await
} }
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> { async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank_identifier, meseta).await set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank, meseta).await
} }
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> { async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
get_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank_identifier).await get_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank).await
} }
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> { async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
create_trade(&mut *self.pool.acquire().await?, char_id1, char_id2).await create_trade(&mut *self.pool.acquire().await?, char_id1, char_id2).await
} }
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
set_character_playtime(&mut *self.pool.acquire().await?, char_id, playtime).await
}
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
create_room(&mut *self.pool.acquire().await?, room).await
}
async fn add_room_note(&mut self, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
add_room_note(&mut *self.pool.acquire().await?, room_id, note).await
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'c> EntityGateway for PostgresTransaction<'c> { impl<'c> EntityGateway for PostgresTransaction<'c> {
type Transaction<'t> = PostgresTransaction<'c> where Self: 't;
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> { async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
create_user(&mut *self.pgtransaction.lock().await, user).await create_user(&mut *self.pgtransaction, user).await
} }
async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> { async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
get_user_by_id(&mut *self.pgtransaction.lock().await, id).await get_user_by_id(&mut *self.pgtransaction, id).await
} }
async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> { async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> {
get_user_by_name(&mut *self.pgtransaction.lock().await, username).await get_user_by_name(&mut *self.pgtransaction, username).await
} }
async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> { async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> {
save_user(&mut *self.pgtransaction.lock().await, user).await save_user(&mut *self.pgtransaction, user).await
} }
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> { async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> {
create_user_settings(&mut *self.pgtransaction.lock().await, settings).await create_user_settings(&mut *self.pgtransaction, settings).await
} }
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> { async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
get_user_settings_by_user(&mut *self.pgtransaction.lock().await, user).await get_user_settings_by_user(&mut *self.pgtransaction, user).await
} }
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> { async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
save_user_settings(&mut *self.pgtransaction.lock().await, settings).await save_user_settings(&mut *self.pgtransaction, settings).await
} }
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> { async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
create_character(&mut *self.pgtransaction.lock().await, char).await create_character(&mut *self.pgtransaction, char).await
} }
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> { async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
get_characters_by_user(&mut *self.pgtransaction.lock().await, user).await get_characters_by_user(&mut *self.pgtransaction, user).await
} }
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> { async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
save_character(&mut *self.pgtransaction.lock().await, char).await save_character(&mut *self.pgtransaction, char).await
} }
async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> { async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
@ -884,83 +759,71 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
} }
async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> { async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
create_item(&mut *self.pgtransaction.lock().await, item).await create_item(&mut *self.pgtransaction, item).await
} }
async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> { async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> {
add_item_note(&mut *self.pgtransaction.lock().await, item_id, item_note).await add_item_note(&mut *self.pgtransaction, item_id, item_note).await
} }
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> { async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> {
feed_mag(&mut *self.pgtransaction.lock().await, mag_item_id, tool_item_id).await feed_mag(&mut *self.pgtransaction, mag_item_id, tool_item_id).await
} }
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> { async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> {
change_mag_owner(&mut *self.pgtransaction.lock().await, mag_item_id, character).await change_mag_owner(&mut *self.pgtransaction, mag_item_id, character).await
} }
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> { async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> {
use_mag_cell(&mut *self.pgtransaction.lock().await, mag_item_id, mag_cell_id).await use_mag_cell(&mut *self.pgtransaction, mag_item_id, mag_cell_id).await
} }
async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> { async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> {
add_weapon_modifier(&mut *self.pgtransaction.lock().await, item_id, modifier).await add_weapon_modifier(&mut *self.pgtransaction, item_id, modifier).await
} }
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> { async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
get_character_inventory(&mut *self.pgtransaction.lock().await, char_id).await get_character_inventory(&mut *self.pgtransaction, char_id).await
} }
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> { async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError> {
get_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank_identifier).await get_character_bank(&mut *self.pgtransaction, char_id, bank_name).await
} }
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> { async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
set_character_inventory(&mut *self.pgtransaction.lock().await, char_id, inventory).await set_character_inventory(&mut *self.pgtransaction, char_id, inventory).await
} }
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> { async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> {
set_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank, bank_identifier).await set_character_bank(&mut *self.pgtransaction, char_id, bank, bank_name).await
} }
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> { async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
get_character_equips(&mut *self.pgtransaction.lock().await, char_id).await get_character_equips(&mut *self.pgtransaction, char_id).await
} }
async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equips: &EquippedEntity) -> Result<(), GatewayError> { async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equips: &EquippedEntity) -> Result<(), GatewayError> {
set_character_equips(&mut *self.pgtransaction.lock().await, char_id, equips).await set_character_equips(&mut *self.pgtransaction, char_id, equips).await
} }
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> { async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
set_character_meseta(&mut *self.pgtransaction.lock().await, char_id, meseta).await set_character_meseta(&mut *self.pgtransaction, char_id, meseta).await
} }
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> { async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
get_character_meseta(&mut *self.pgtransaction.lock().await, char_id).await get_character_meseta(&mut *self.pgtransaction, char_id).await
} }
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> { async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
set_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank_identifier, meseta).await set_bank_meseta(&mut *self.pgtransaction, char_id, bank, meseta).await
} }
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> { async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
get_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank_identifier).await get_bank_meseta(&mut *self.pgtransaction, char_id, bank).await
} }
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> { async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
create_trade(&mut *self.pgtransaction.lock().await, char_id1, char_id2).await create_trade(&mut *self.pgtransaction, char_id1, char_id2).await
}
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
set_character_playtime(&mut *self.pgtransaction.lock().await, char_id, playtime).await
}
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
create_room(&mut *self.pgtransaction.lock().await, room).await
}
async fn add_room_note(&mut self, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
add_room_note(&mut *self.pgtransaction.lock().await, room_id, note).await
} }
} }

View File

@ -1,5 +1,5 @@
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::item::ItemEntityId; use crate::entity::item::ItemEntityId;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ItemParseError { pub enum ItemParseError {

View File

@ -1,9 +1,9 @@
use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
use std::collections::HashMap;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::item::tool::ToolType; use crate::entity::item::tool::ToolType;
use crate::character::{CharacterClass, SectionID}; use crate::entity::character::{CharacterClass, SectionID};
use crate::item::ItemEntityId; use crate::entity::item::ItemEntityId;
use std::io::Read; use std::io::Read;
use std::cmp::Ordering::{Less, Greater, Equal}; use std::cmp::Ordering::{Less, Greater, Equal};
@ -521,7 +521,7 @@ pub enum MagCellError {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum MagModifier { pub enum MagModifier {
FeedMag { FeedMag{
food: ItemEntityId, food: ItemEntityId,
}, },
BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags BankMag, // when putting a mag in the bank it truncates the values which has applications when raising degenerate mags

View File

@ -9,11 +9,9 @@ pub mod mag;
pub mod esweapon; pub mod esweapon;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::character::CharacterEntityId; use crate::entity::character::CharacterEntityId;
use crate::room::RoomEntityId; use crate::ship::map::MapArea;
use maps::area::MapArea; use crate::ship::drops::ItemDropType;
use maps::monster::MonsterType;
//use crate::ship::drops::ItemDropType;
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ItemEntityId(pub u32); pub struct ItemEntityId(pub u32);
@ -24,12 +22,6 @@ pub struct BankName(pub String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TradeId(pub u32); pub struct TradeId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, derive_more::Display)]
pub enum BankIdentifier {
Character,
Shared(BankName),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ItemNote { pub enum ItemNote {
CharacterCreation { CharacterCreation {
@ -37,16 +29,8 @@ pub enum ItemNote {
}, },
EnemyDrop { EnemyDrop {
character_id: CharacterEntityId, character_id: CharacterEntityId,
room_id: RoomEntityId, //monster_type: MonsterType,
monster_type: MonsterType, //droprate: f32,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: CharacterEntityId,
room_id: RoomEntityId,
map_area: MapArea, map_area: MapArea,
x: f32, x: f32,
y: f32, y: f32,
@ -62,19 +46,15 @@ pub enum ItemNote {
y: f32, y: f32,
z: f32, z: f32,
}, },
Consumed { Consumed, // TODO: character_id
character_id: CharacterEntityId,
},
FedToMag { FedToMag {
character_id: CharacterEntityId, //character_id: CharacterEntityId,
mag: ItemEntityId, mag: ItemEntityId,
}, },
BoughtAtShop { BoughtAtShop {
character_id: CharacterEntityId, character_id: CharacterEntityId,
}, },
SoldToShop { SoldToShop,
character_id: CharacterEntityId,
},
Trade { Trade {
trade_id: TradeId, trade_id: TradeId,
character_to: CharacterEntityId, character_to: CharacterEntityId,
@ -82,14 +62,11 @@ pub enum ItemNote {
}, },
Withdraw { Withdraw {
character_id: CharacterEntityId, character_id: CharacterEntityId,
bank: BankIdentifier, bank: BankName,
}, },
Deposit { Deposit {
character_id: CharacterEntityId, character_id: CharacterEntityId,
bank: BankIdentifier, bank: BankName,
},
FloorLimitReached {
map_area: MapArea,
}, },
} }
@ -156,6 +133,26 @@ impl ItemDetail {
} }
} }
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
match item_type {
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
_ => None,
}
}
pub fn as_client_bytes(&self) -> [u8; 16] { pub fn as_client_bytes(&self) -> [u8; 16] {
match self { match self {
ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Weapon(w) => w.as_bytes(),
@ -182,6 +179,19 @@ impl ItemDetail {
_ => None, _ => None,
} }
} }
pub fn has_kill_counter(self) -> bool {
match self {
ItemDetail::Weapon(w) => w.kills.is_some(),
ItemDetail::Armor(_a) => false,
ItemDetail::Shield(_s) => false,
ItemDetail::Unit(u) => u.kills.is_some(),
ItemDetail::Tool(_t) => false,
ItemDetail::TechniqueDisk(_d) => false,
ItemDetail::Mag(_m) => false,
ItemDetail::ESWeapon(_e) => false,
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -195,7 +205,6 @@ pub struct ItemEntity {
pub item: ItemDetail, pub item: ItemDetail,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InventoryItemEntity { pub enum InventoryItemEntity {
Individual(ItemEntity), Individual(ItemEntity),
@ -244,13 +253,6 @@ impl InventoryItemEntity {
_ => None, _ => None,
} }
} }
pub fn stacked(&self) -> Option<&Vec<ItemEntity>> {
match self {
InventoryItemEntity::Stacked(i) => Some(i),
_ => None,
}
}
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]

View File

@ -212,43 +212,7 @@ impl ToolType {
ToolType::Addslot | ToolType::Addslot |
ToolType::PhotonDrop | ToolType::PhotonDrop |
ToolType::PhotonSphere | ToolType::PhotonSphere |
ToolType::PhotonCrystal | ToolType::PhotonCrystal)
ToolType::JackOLantern |
ToolType::ChristmasPresent |
ToolType::EasterEgg |
ToolType::CellOfMag502 |
ToolType::CellOfMag213 |
ToolType::PartsOfRobochao |
ToolType::HeartOfOpaOpa |
ToolType::HeartOfPian |
ToolType::HeartOfChao |
ToolType::HeartOfChuChu |
ToolType::HeartOfAngel |
ToolType::HeartOfDevil |
ToolType::KitOfHamburger |
ToolType::PanthersSpirit |
ToolType::KitOfMark3 |
ToolType::KitOfMasterSystem |
ToolType::KitOfGenesis |
ToolType::KitOfSegaSaturn |
ToolType::KitOfDreamcast |
ToolType::HeartOfKapuKapu |
ToolType::Tablet |
ToolType::DragonScale |
ToolType::HeavenStrikerCoat |
ToolType::PioneerParts |
ToolType::AmitiesMemo |
ToolType::HeartOfMorolian |
ToolType::RappysBeak |
ToolType::YahoosEngine |
ToolType::DPhotonCore |
ToolType::LibertaKit |
ToolType::CellOfMag0503 |
ToolType::CellOfMag0504 |
ToolType::CellOfMag0505 |
ToolType::CellOfMag0506 |
ToolType::CellOfMag0507
)
} }
pub fn max_stack(&self) -> usize { pub fn max_stack(&self) -> usize {
@ -280,41 +244,6 @@ impl ToolType {
ToolType::PhotonDrop => 99, ToolType::PhotonDrop => 99,
ToolType::PhotonSphere => 99, ToolType::PhotonSphere => 99,
ToolType::PhotonCrystal => 99, ToolType::PhotonCrystal => 99,
ToolType::JackOLantern => 99,
ToolType::ChristmasPresent => 99,
ToolType::EasterEgg => 99,
ToolType::CellOfMag502 => 99,
ToolType::CellOfMag213 => 99,
ToolType::PartsOfRobochao => 99,
ToolType::HeartOfOpaOpa => 99,
ToolType::HeartOfPian => 99,
ToolType::HeartOfChao => 99,
ToolType::HeartOfChuChu => 99,
ToolType::HeartOfAngel => 99,
ToolType::HeartOfDevil => 99,
ToolType::KitOfHamburger => 99,
ToolType::PanthersSpirit => 99,
ToolType::KitOfMark3 => 99,
ToolType::KitOfMasterSystem => 99,
ToolType::KitOfGenesis => 99,
ToolType::KitOfSegaSaturn => 99,
ToolType::KitOfDreamcast => 99,
ToolType::HeartOfKapuKapu => 99,
ToolType::Tablet => 99,
ToolType::DragonScale => 99,
ToolType::HeavenStrikerCoat => 99,
ToolType::PioneerParts => 99,
ToolType::AmitiesMemo => 99,
ToolType::HeartOfMorolian => 99,
ToolType::RappysBeak => 99,
ToolType::YahoosEngine => 99,
ToolType::DPhotonCore => 99,
ToolType::LibertaKit => 99,
ToolType::CellOfMag0503 => 99,
ToolType::CellOfMag0504 => 99,
ToolType::CellOfMag0505 => 99,
ToolType::CellOfMag0506 => 99,
ToolType::CellOfMag0507 => 99,
_ => 1, _ => 1,
} }
} }
@ -326,7 +255,6 @@ impl ToolType {
ToolType::HeartOfOpaOpa | ToolType::HeartOfOpaOpa |
ToolType::HeartOfPian | ToolType::HeartOfPian |
ToolType::HeartOfChao | ToolType::HeartOfChao |
ToolType::HeartOfChuChu |
ToolType::HeartOfAngel | ToolType::HeartOfAngel |
ToolType::HeartOfDevil | ToolType::HeartOfDevil |
ToolType::KitOfHamburger | ToolType::KitOfHamburger |
@ -336,7 +264,6 @@ impl ToolType {
ToolType::KitOfGenesis | ToolType::KitOfGenesis |
ToolType::KitOfSegaSaturn | ToolType::KitOfSegaSaturn |
ToolType::KitOfDreamcast | ToolType::KitOfDreamcast |
ToolType::HeartOfKapuKapu |
ToolType::Tablet | ToolType::Tablet |
ToolType::DragonScale | ToolType::DragonScale |
ToolType::HeavenStrikerCoat | ToolType::HeavenStrikerCoat |
@ -735,7 +662,7 @@ impl Tool {
} }
pub fn from_bytes(data: [u8; 16]) -> Result<Tool, ItemParseError> { pub fn from_bytes(data: [u8; 16]) -> Result<Tool, ItemParseError> {
let t = ToolType::parse_type([data[0], data[1], data[2]]); let t = ToolType::parse_type([data[0], data[1], data[2]]);
if let Ok(t) = t { if let Ok(t) = t {
Ok(Tool { Ok(Tool {
tool: t, tool: t,

View File

@ -1,4 +1,6 @@
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::ship::monster::MonsterType;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ItemParseError { pub enum ItemParseError {
@ -321,6 +323,10 @@ impl UnitType {
_ => Err(ItemParseError::InvalidUnitType), _ => Err(ItemParseError::InvalidUnitType),
} }
} }
pub fn has_counter(&self) -> bool {
matches!(self, UnitType::Limiter)
}
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -329,12 +335,17 @@ pub enum UnitModifier {
Plus, Plus,
Minus, Minus,
MinusMinus, MinusMinus,
AddKill {
enemy: MonsterType,
// attack: u32, // maybe one day for TURBO logging?
},
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Unit { pub struct Unit {
pub unit: UnitType, pub unit: UnitType,
pub modifier: Option<UnitModifier>, pub modifier: Option<UnitModifier>,
pub kills: Option<u16>,
} }
@ -359,13 +370,19 @@ impl Unit {
result[6] = 0xFE; result[6] = 0xFE;
result[7] = 0xFF; result[7] = 0xFF;
}, },
_ => {},
} }
} }
if self.unit.has_counter() {
result[10..12].copy_from_slice(&self.kills.unwrap_or(0u16).to_be_bytes());
result[10] += 0x80;
}
result result
} }
pub fn from_bytes(data: [u8; 16]) -> Result<Unit, ItemParseError> { pub fn from_bytes(data: [u8; 16]) -> Result<Unit, ItemParseError> {
let u = UnitType::parse_type([data[0], data[1], data[2]]); let u = UnitType::parse_type([data[0], data[1], data[2]]);
let mut k = None;
if let Ok(u) = u { if let Ok(u) = u {
let m = match u16::from_le_bytes([data[6], data[7]]) { let m = match u16::from_le_bytes([data[6], data[7]]) {
0x02 => Some(UnitModifier::PlusPlus), 0x02 => Some(UnitModifier::PlusPlus),
@ -375,9 +392,14 @@ impl Unit {
_ => None, _ => None,
}; };
if data[10] & 0x80 == 0x80 {
k = Some(u16::from_be_bytes([data[10] - 0x80, data[11]]));
}
Ok(Unit{ Ok(Unit{
unit: u, unit: u,
modifier: m, modifier: m,
kills: k,
}) })
} }
else { else {
@ -456,4 +478,12 @@ impl Unit {
_ => 0, _ => 0,
} }
} }
pub fn apply_modifier(&mut self, modifier: &UnitModifier) {
if let UnitModifier::AddKill{enemy: _} = modifier {
if let Some(kills) = self.kills {
self.kills = Some(kills + 1);
}
};
}
} }

View File

@ -1,4 +1,5 @@
use crate::item::ItemEntityId; use crate::entity::item::ItemEntityId;
use crate::ship::monster::MonsterType;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -1421,6 +1422,10 @@ impl WeaponType {
_ => Err(ItemParseError::InvalidWeaponType), _ => Err(ItemParseError::InvalidWeaponType),
} }
} }
pub fn has_counter(&self) -> bool {
matches!(self, WeaponType::SealedJSword | WeaponType::LameDArgent)
}
} }
@ -1455,6 +1460,10 @@ pub enum WeaponModifier {
percent: TekPercentModifier, percent: TekPercentModifier,
grind: i32, grind: i32,
}, },
AddKill {
enemy: MonsterType,
// attack: u32, // maybe one day for TURBO logging?
},
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@ -1464,6 +1473,7 @@ pub struct Weapon {
pub grind: u8, pub grind: u8,
pub attrs: [Option<WeaponAttribute>; 3], pub attrs: [Option<WeaponAttribute>; 3],
pub tekked: bool, pub tekked: bool,
pub kills: Option<u16>,
} }
@ -1475,11 +1485,15 @@ impl Weapon {
grind: 0, grind: 0,
attrs: [None; 3], attrs: [None; 3],
tekked: true, tekked: true,
kills: None,
} }
} }
// TODO: apply other modifiers
pub fn apply_modifier(&mut self, modifier: &WeaponModifier) { pub fn apply_modifier(&mut self, modifier: &WeaponModifier) {
match modifier { match modifier {
WeaponModifier::AddPercents{attr: _, pds: _} => {},
WeaponModifier::AddGrind{amount: _, grinder: _} => {},
WeaponModifier::Tekked{special, percent, grind} => { WeaponModifier::Tekked{special, percent, grind} => {
match special { match special {
TekSpecialModifier::Plus => { TekSpecialModifier::Plus => {
@ -1519,13 +1533,12 @@ impl Weapon {
self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8; self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8;
self.tekked = true; self.tekked = true;
}, },
WeaponModifier::AddGrind {amount, ..} => { WeaponModifier::AddKill{enemy: _} => {
self.grind += *amount as u8; if let Some(kills) = self.kills {
self.kills = Some(kills + 1);
}
}, },
WeaponModifier::AddPercents {..} => { };
// TODO
}
}
} }
pub fn as_bytes(&self) -> [u8; 16] { pub fn as_bytes(&self) -> [u8; 16] {
@ -1538,9 +1551,14 @@ impl Weapon {
result[4] += 0x80 result[4] += 0x80
}; };
result[6..8].copy_from_slice(&self.attrs[0].map(|s| s.value()).unwrap_or([0,0])); result[6..8].copy_from_slice(&self.attrs[0].map(|s| s.value()).unwrap_or([0,0]));
result[8..10].copy_from_slice(&self.attrs[1].map(|s| s.value()).unwrap_or([0,0])); result[8..10].copy_from_slice(&self.attrs[1].map(|s| s.value()).unwrap_or([0,0]));
result[10..12].copy_from_slice(&self.attrs[2].map(|s| s.value()).unwrap_or([0,0])); if self.weapon.has_counter() {
result[10..12].copy_from_slice(&self.kills.unwrap_or(0u16).to_be_bytes());
result[10] += 0x80;
} else {
result[10..12].copy_from_slice(&self.attrs[2].map(|s| s.value()).unwrap_or([0,0]));
}
result result
} }
@ -1551,6 +1569,7 @@ impl Weapon {
if let Ok(weapon) = wep { if let Ok(weapon) = wep {
let mut special = None; let mut special = None;
let mut tekked = true; let mut tekked = true;
let mut kills = None;
let grind = data[3]; let grind = data[3];
if data[4] >= 0x81 && data[4] <= 0xA8 { if data[4] >= 0x81 && data[4] <= 0xA8 {
@ -1583,12 +1602,18 @@ impl Weapon {
} }
} }
if data[10] & 0x80 == 0x80 {
attrs[2] = None;
kills = Some(u16::from_be_bytes([data[10] - 0x80, data[11]]));
}
Ok(Weapon { Ok(Weapon {
weapon, weapon,
special, special,
grind, grind,
attrs, attrs,
tekked, tekked,
kills,
}) })
} }
else { else {

View File

@ -2,4 +2,3 @@ pub mod gateway;
pub mod account; pub mod account;
pub mod character; pub mod character;
pub mod item; pub mod item;
pub mod room;

View File

@ -1,787 +0,0 @@
use std::collections::BTreeMap;
use std::convert::TryInto;
use futures::future::{Future, BoxFuture};
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use async_std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct InMemoryGatewayTransaction {
working_gateway: InMemoryGateway,
original_gateway: InMemoryGateway,
}
fn copy_if_needed<K, V>(working_table: &mut BTreeMap<K, V>, original_table: &BTreeMap<K, V>, key: K) -> Option<()>
where
K: Ord + Copy,
V: Clone,
{
if let std::collections::btree_map::Entry::Vacant(e) = working_table.entry(key) {
e.insert(original_table.get(&key)?.clone());
}
Some(())
}
// functions here have been skipped as they are not used in transactions, add as needed
#[async_trait::async_trait]
impl EntityGateway for InMemoryGatewayTransaction {
type Transaction<'t> = InMemoryGatewayTransaction where Self: 't;
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
self.working_gateway.create_user(user).await
}
async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
match self.working_gateway.get_user_by_id(id).await {
Ok(user) => Ok(user),
Err(_) => {
self.original_gateway.get_user_by_id(id).await
}
}
}
async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> {
match self.working_gateway.get_user_by_name(username.clone()).await {
Ok(user) => Ok(user),
Err(_) => {
self.original_gateway.get_user_by_name(username).await
}
}
}
async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> {
self.working_gateway.save_user(user).await
}
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> {
self.working_gateway.create_user_settings(settings).await
}
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
match self.working_gateway.get_user_settings_by_user(user).await {
Ok(user) => Ok(user),
Err(_) => {
self.original_gateway.get_user_settings_by_user(user).await
}
}
}
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,
char.id
);
self.working_gateway.save_character(char).await
}
async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
self.working_gateway.create_item(item).await
}
async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> {
self.working_gateway.add_item_note(item_id, item_note).await
}
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> {
self.working_gateway.feed_mag(mag_item_id, tool_item_id).await
}
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> {
self.working_gateway.change_mag_owner(mag_item_id, character).await
}
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> {
self.working_gateway.use_mag_cell(mag_item_id, mag_cell_id).await
}
async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> {
self.working_gateway.add_weapon_modifier(item_id, modifier).await
}
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
match self.working_gateway.get_character_inventory(char_id).await {
Ok(inventory) => Ok(inventory),
Err(_) => {
self.original_gateway.get_character_inventory(char_id).await
}
}
}
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
match self.working_gateway.get_character_bank(char_id, bank_identifier).await {
Ok(bank) => Ok(bank),
Err(_) => {
self.original_gateway.get_character_bank(char_id, bank_identifier).await
}
}
}
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
self.working_gateway.set_character_inventory(char_id, inventory).await
}
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
self.working_gateway.set_character_bank(char_id, bank, bank_identifier).await
}
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
match self.working_gateway.get_character_equips(char_id).await {
Ok(equips) => Ok(equips),
Err(_) => {
self.original_gateway.get_character_equips(char_id).await
}
}
}
async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equipped: &EquippedEntity) -> Result<(), GatewayError> {
self.working_gateway.set_character_equips(char_id, equipped).await
}
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
self.working_gateway.set_character_meseta(char_id, meseta).await
}
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
match self.working_gateway.get_character_meseta(char_id).await {
Ok(meseta) => Ok(meseta),
Err(_) => {
self.original_gateway.get_character_meseta(char_id).await
}
}
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
self.working_gateway.set_bank_meseta(char_id, bank_identifier, meseta).await
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
match self.working_gateway.get_bank_meseta(char_id, bank_identifier).await {
Ok(meseta) => Ok(meseta),
Err(_) => {
self.original_gateway.get_bank_meseta(char_id, bank_identifier).await
}
}
}
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
self.working_gateway.create_trade(char_id1, char_id2).await
}
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
copy_if_needed(&mut *self.working_gateway.characters.lock().await,
&*self.original_gateway.characters.lock().await,
*char_id
);
self.working_gateway.set_character_playtime(char_id, playtime).await
}
}
#[async_trait::async_trait]
impl EntityGatewayTransaction for InMemoryGatewayTransaction {
type ParentGateway = InMemoryGatewayTransaction;
fn gateway(&mut self) -> &mut Self::ParentGateway {
self
}
async fn commit(mut self) -> Result<(), GatewayError> {
self.original_gateway.users.lock().await.extend(self.working_gateway.users.lock().await.clone());
self.original_gateway.user_settings.lock().await.extend(self.working_gateway.user_settings.lock().await.clone());
self.original_gateway.characters.lock().await.extend(self.working_gateway.characters.lock().await.clone());
self.original_gateway.character_meseta.lock().await.extend(self.working_gateway.character_meseta.lock().await.clone());
self.original_gateway.bank_meseta.lock().await.extend(self.working_gateway.bank_meseta.lock().await.clone());
self.original_gateway.shared_bank_meseta.lock().await.extend(self.working_gateway.shared_bank_meseta.lock().await.clone());
self.original_gateway.items.lock().await.extend(self.working_gateway.items.lock().await.clone());
self.original_gateway.inventories.lock().await.extend(self.working_gateway.inventories.lock().await.clone());
self.original_gateway.character_banks.lock().await.extend(self.working_gateway.character_banks.lock().await.clone());
self.original_gateway.shared_banks.lock().await.extend(self.working_gateway.shared_banks.lock().await.clone());
self.original_gateway.equips.lock().await.extend(self.working_gateway.equips.lock().await.clone());
self.original_gateway.mag_modifiers.lock().await.extend(self.working_gateway.mag_modifiers.lock().await.clone());
self.original_gateway.weapon_modifiers.lock().await.extend(self.working_gateway.weapon_modifiers.lock().await.clone());
self.original_gateway.trades.lock().await.extend(self.working_gateway.trades.lock().await.clone());
Ok(())
}
}
#[derive(Clone)]
enum InventoryItemElement {
Individual(ItemEntityId),
Stacked(Vec<ItemEntityId>),
}
#[derive(Clone)]
pub struct InMemoryGateway {
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
bank_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
shared_bank_meseta: Arc<Mutex<BTreeMap<(UserAccountId, BankName), Meseta>>>,
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, Vec<InventoryItemElement>>>>,
character_banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
shared_banks: Arc<Mutex<BTreeMap<(UserAccountId, BankName), BankEntity>>>,
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
trades: Arc<Mutex<Vec<TradeEntity>>>,
}
impl Default for InMemoryGateway {
fn default() -> InMemoryGateway {
InMemoryGateway {
users: Arc::new(Mutex::new(BTreeMap::new())),
user_settings: Arc::new(Mutex::new(BTreeMap::new())),
characters: Arc::new(Mutex::new(BTreeMap::new())),
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
shared_bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
items: Arc::new(Mutex::new(BTreeMap::new())),
inventories: Arc::new(Mutex::new(BTreeMap::new())),
character_banks: Arc::new(Mutex::new(BTreeMap::new())),
shared_banks: Arc::new(Mutex::new(BTreeMap::new())),
equips: Arc::new(Mutex::new(BTreeMap::new())),
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
trades: Arc::new(Mutex::new(Vec::new())),
}
}
}
fn apply_modifiers(items: &BTreeMap<ItemEntityId, ItemEntity>,
weapon_modifiers: &BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>,
mag_modifiers: &BTreeMap<ItemEntityId, Vec<mag::MagModifier>>,
inventory: InventoryEntity ) -> InventoryEntity {
let inventory_items = inventory.items.into_iter()
.map(|item| {
item.map_individual(|mut item| {
item.item = match item.item {
ItemDetail::Weapon(mut weapon) => {
if let Some(weapon_modifiers) = weapon_modifiers.get(&item.id) {
for weapon_modifier in weapon_modifiers.iter() {
weapon.apply_modifier(weapon_modifier);
}
}
ItemDetail::Weapon(weapon)
},
ItemDetail::Mag(mag) => {
let mut mag = mag::Mag::baby_mag(mag.color as u16);
if let Some(mag_modifiers) = mag_modifiers.get(&item.id) {
for mag_modifier in mag_modifiers.iter() {
match mag_modifier {
mag::MagModifier::FeedMag {food} => {
if let Some(mag_feed) = items.get(food) {
if let ItemDetail::Tool(mag_feed) = mag_feed.item {
mag.feed(mag_feed.tool)
}
}
},
mag::MagModifier::OwnerChange(class, section_id) => {
mag.change_owner(*class, *section_id)
},
mag::MagModifier::MagCell(mag_cell_id) => {
if let Some(mag_cell) = items.get(mag_cell_id) {
if let ItemDetail::Tool(mag_cell) = mag_cell.item {
mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()).unwrap()
}
}
},
_ => {}
}
}
}
ItemDetail::Mag(mag)
}
_ => {
item.item
}
};
item
})
})
.collect();
InventoryEntity::new(inventory_items)
}
#[async_trait::async_trait]
impl EntityGateway for InMemoryGateway {
type Transaction<'t> = InMemoryGatewayTransaction where Self: 't;
fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
where
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
R: Send,
{
Box::pin(async move {
let users = self.users.lock().await.clone();
let user_settings = self.user_settings.lock().await.clone();
let characters = self.characters.lock().await.clone();
let character_meseta = self.character_meseta.lock().await.clone();
let bank_meseta = self.bank_meseta.lock().await.clone();
let shared_bank_meseta = self.shared_bank_meseta.lock().await.clone();
let items = self.items.lock().await.clone();
let inventories = self.inventories.lock().await.clone();
let character_banks = self.character_banks.lock().await.clone();
let shared_banks = self.shared_banks.lock().await.clone();
let equips = self.equips.lock().await.clone();
let mag_modifiers = self.mag_modifiers.lock().await.clone();
let weapon_modifiers = self.weapon_modifiers.lock().await.clone();
let trades = self.trades.lock().await.clone();
let working_gateway = InMemoryGateway {
users: Arc::new(Mutex::new(users)),
user_settings: Arc::new(Mutex::new(user_settings)),
characters: Arc::new(Mutex::new(characters)),
character_meseta: Arc::new(Mutex::new(character_meseta)),
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
shared_bank_meseta: Arc::new(Mutex::new(shared_bank_meseta)),
items: Arc::new(Mutex::new(items)),
inventories: Arc::new(Mutex::new(inventories)),
character_banks: Arc::new(Mutex::new(character_banks)),
shared_banks: Arc::new(Mutex::new(shared_banks)),
equips: Arc::new(Mutex::new(equips)),
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
trades: Arc::new(Mutex::new(trades)),
};
let transaction = InMemoryGatewayTransaction {
working_gateway,
original_gateway: self.clone(),
};
let (transaction, result) = func(transaction).await?;
transaction.commit().await?;
Ok(result)
})
}
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
let mut users = self.users.lock().await;
let id = users
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let user = UserAccountEntity {
id: UserAccountId(id),
username: user.username,
password: user.password,
guildcard: user.guildcard,
team_id: user.team_id,
banned_until: user.banned_until,
muted_until: user.muted_until,
created_at: chrono::Utc::now(),
flags: user.flags,
activated: user.activated,
at_login: false,
at_character: false,
at_ship: false,
};
users.insert(user.id, user.clone());
Ok(user)
}
async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
let users = self.users.lock().await;
users.get(&id).cloned().ok_or(GatewayError::Error)
}
async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> {
let users = self.users.lock().await;
users
.iter()
.find(|(_, k)| k.username == username)
.map(|(_, k)| k.clone())
.ok_or(GatewayError::Error)
}
async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> {
let mut users = self.users.lock().await;
users.insert(user.id, user.clone());
Ok(())
}
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> {
let mut user_settings = self.user_settings.lock().await;
let id = user_settings
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let new_settings = UserSettingsEntity {
id: UserSettingsId(id),
user_id: settings.user_id,
settings: settings.settings,
};
user_settings.insert(new_settings.id, new_settings.clone());
Ok(new_settings)
}
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
let user_settings = self.user_settings.lock().await;
user_settings
.iter()
.find(|(_, k)| k.user_id == user.id)
.map(|(_, k)| k.clone())
.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;
let mut chars = [NONE; 4];
characters
.iter()
.filter(|(_, c)| c.user_id == user.id)
.for_each(|(_, c)| chars[c.slot as usize] = Some(c.clone()));
Ok(chars)
}
async fn create_character(&mut self, character: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
let mut characters = self.characters.lock().await;
let id = characters
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let new_character = CharacterEntity {
id: CharacterEntityId(id),
user_id: character.user_id,
slot: character.slot,
name: character.name,
exp: character.exp,
char_class: character.char_class,
section_id: character.section_id,
appearance: character.appearance,
techs: character.techs,
config: character.config,
info_board: character.info_board,
guildcard: character.guildcard,
materials: character.materials,
tech_menu: character.tech_menu,
option_flags: character.option_flags,
playtime: 0,
};
characters.insert(new_character.id, new_character.clone());
Ok(new_character)
}
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
let mut characters = self.characters.lock().await;
characters.insert(char.id, char.clone());
Ok(())
}
async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
Ok(GuildCardDataEntity::new(user.id))
}
async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
let mut items = self.items.lock().await;
let id = items
.iter()
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
+ 1;
let new_item = ItemEntity {
id: ItemEntityId(id),
item: item.item,
};
items.insert(ItemEntityId(id), new_item.clone());
Ok(new_item)
}
async fn add_item_note(&mut self, _item_id: &ItemEntityId, _item_note: ItemNote) -> Result<(), GatewayError> {
Ok(())
}
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> {
self.mag_modifiers.lock().await
.entry(*mag_item_id)
.or_insert_with(Vec::new)
.push(mag::MagModifier::FeedMag {
food: *tool_item_id
});
Ok(())
}
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> {
self.mag_modifiers.lock().await
.entry(*mag_item_id)
.or_insert_with(Vec::new)
.push(mag::MagModifier::OwnerChange(character.char_class, character.section_id));
Ok(())
}
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> {
self.mag_modifiers.lock().await
.entry(*mag_item_id)
.or_insert_with(Vec::new)
.push(mag::MagModifier::MagCell(*mag_cell_id));
Ok(())
}
async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> {
self.weapon_modifiers.lock().await
.entry(*item_id)
.or_insert_with(Vec::new)
.push(modifier.clone());
Ok(())
}
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
let inventories = self.inventories.lock().await;
let items = self.items.lock().await;
let weapon_modifiers = self.weapon_modifiers.lock().await;
let mag_modifiers = self.mag_modifiers.lock().await;
Ok(inventories
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, inv)| {
InventoryEntity {
items: inv
.iter()
.map(|inv_item_id| {
match inv_item_id {
InventoryItemElement::Individual(individual_id) => {
InventoryItemEntity::Individual(items.get(individual_id).unwrap().clone())
},
InventoryItemElement::Stacked(stacked_ids) => {
InventoryItemEntity::Stacked(
stacked_ids.iter()
.map(|stacked_id| {
items.get(stacked_id).unwrap().clone()
})
.collect()
)
}
}
})
.collect()
}
})
.map(|inv| apply_modifiers(&items, &weapon_modifiers, &mag_modifiers, inv))
.unwrap_or_default())
}
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let character_banks = self.character_banks.lock().await;
Ok(character_banks
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, b)| b.clone())
.unwrap_or_default())
},
BankIdentifier::Shared(bank_name) => {
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
let shared_banks = self.shared_banks.lock().await;
Ok(shared_banks
.iter()
.find(|((id, name), _)| *id == user_id && *name == *bank_name)
.map(|(_, b)| b.clone())
.unwrap_or_default())
}
}
}
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
let mut inventories = self.inventories.lock().await;
inventories.insert(*char_id, inventory.items
.iter()
.map(|inventory_item| {
match inventory_item {
InventoryItemEntity::Individual(individual) => {
InventoryItemElement::Individual(individual.id)
},
InventoryItemEntity::Stacked(stacked) => {
InventoryItemElement::Stacked(
stacked.iter()
.map(|stacked| {
stacked.id
})
.collect()
)
}
}
})
.collect());
Ok(())
}
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let mut character_banks = self.character_banks.lock().await;
character_banks.insert(*char_id, bank.clone());
},
BankIdentifier::Shared(bank_name) => {
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
let mut shared_banks = self.shared_banks.lock().await;
shared_banks.insert((user_id, bank_name.clone()), bank.clone());
}
}
Ok(())
}
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
let equips = self.equips.lock().await;
Ok(equips
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, inv)| inv.clone())
.unwrap_or_default())
}
async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equipped: &EquippedEntity) -> Result<(), GatewayError> {
let mut equips = self.equips.lock().await;
equips.insert(*char_id, equipped.clone());
Ok(())
}
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
let mut character_meseta = self.character_meseta.lock().await;
character_meseta.insert(*char_id, meseta);
Ok(())
}
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
let mut character_meseta = self.character_meseta.lock().await;
if let Some(meseta) = character_meseta.get_mut(char_id) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let mut bank_meseta = self.bank_meseta.lock().await;
bank_meseta.insert(*char_id, meseta);
}
BankIdentifier::Shared(bank_name) => {
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
self.shared_bank_meseta
.lock()
.await
.insert((user_id, bank_name.clone()), meseta);
}
}
Ok(())
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let mut bank_meseta = self.bank_meseta.lock().await;
if let Some(meseta) = bank_meseta.get_mut(char_id) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
},
BankIdentifier::Shared(bank_name) => {
let mut shared_bank_meseta = self.shared_bank_meseta.lock().await;
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
if let Some(meseta) = shared_bank_meseta.get_mut(&(user_id, bank_name.clone())) {
Ok(*meseta)
}
else {
Ok(Meseta(0))
}
}
}
}
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
let mut trades = self.trades.lock().await;
let id = trades.len() as u32;
let new_trade = TradeEntity {
id: TradeId(id),
character1: *char_id1,
character2: *char_id2,
};
trades.push(new_trade.clone());
Ok(new_trade)
}
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
let mut characters = self.characters.lock().await;
if let Some(character) = characters.get_mut(char_id) {
character.playtime = playtime;
Ok(())
}
else {
Err(GatewayError::Error)
}
}
// I do not care to replicate this in testing
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
Ok(RoomEntity {
id: RoomEntityId(0),
name: room.name,
section_id: room.section_id,
episode: room.episode,
difficulty: room.difficulty,
mode: room.mode,
})
}
// I do not care to replicate this in testing
async fn add_room_note(&mut self, _room_id: RoomEntityId, _note: RoomNote) -> Result<(), GatewayError> {
Ok(())
}
}

View File

@ -1,16 +0,0 @@
create table character_meseta (
pchar integer references player_character (id) not null unique,
meseta integer not null
);
create table bank_meseta (
pchar integer references player_character (id) not null,
bank varchar(128) not null,
meseta integer not null,
unique (pchar, bank)
);
alter table player_character
drop column meseta,
drop column bank_meseta;

View File

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

View File

@ -1,2 +0,0 @@
alter table player_character
add playtime integer;

View File

@ -1,3 +0,0 @@
alter table player_character
add keyboard_config bytea not null,
add gamepad_config bytea not null;

View File

@ -1,5 +0,0 @@
alter table player_character
drop column playtime;
alter table player_character
add playtime integer not null;

View File

@ -1,3 +0,0 @@
alter table player_character
drop column keyboard_config,
drop column gamepad_config;

View File

@ -1,2 +0,0 @@
alter table player_character
add created_at timestamptz default current_timestamp not null;

View File

@ -1,16 +0,0 @@
create table shared_bank (
user_account integer references user_accounts (id) not null,
items jsonb not null,
name varchar(128) not null,
unique (user_account, name)
);
create table shared_bank_meseta (
user_account integer references user_accounts (id) not null,
name varchar(128) not null,
meseta integer not null,
unique (user_account, name)
);

View File

@ -1,14 +0,0 @@
create table room (
id serial primary key not null,
name varchar(32) not null,
section_id char not null,
mode char not null,
episode char not null,
difficulty char not null
);
create table room_note (
room integer references room (id) not null,
note jsonb not null,
created_at timestamptz default current_timestamp not null
);

View File

@ -1,17 +0,0 @@
drop table room_note;
drop table room;
create table room (
id serial primary key not null,
name varchar(32) not null,
section_id "char" not null,
mode "char" not null,
episode "char" not null,
difficulty "char" not null
);
create table room_note (
room integer references room (id) not null,
note jsonb not null,
created_at timestamptz default current_timestamp not null
);

View File

@ -1,3 +0,0 @@
use refinery::include_migration_mods;
include_migration_mods!("src/gateway/postgres/migrations");

View File

@ -1,83 +0,0 @@
use serde::{Serialize, Deserialize};
use crate::character::{CharacterEntityId, SectionID};
use maps::room::{Episode, Difficulty};
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct RoomEntityId(pub u32);
#[derive(Debug, Copy, Clone)]
pub enum RoomEntityMode {
Multi,
Single,
Challenge,
Battle,
}
impl From<u8> for RoomEntityMode {
fn from(other: u8) -> RoomEntityMode {
match other {
0 => RoomEntityMode::Multi,
1 => RoomEntityMode::Single,
2 => RoomEntityMode::Challenge,
3 => RoomEntityMode::Battle,
_ => unreachable!()
}
}
}
impl From<RoomEntityMode> for u8 {
fn from(other: RoomEntityMode) -> u8 {
match other {
RoomEntityMode::Multi => 0,
RoomEntityMode::Single => 1,
RoomEntityMode::Challenge => 2,
RoomEntityMode::Battle => 3,
}
}
}
#[derive(Debug, Clone)]
pub struct RoomEntity {
pub id: RoomEntityId,
pub name: String,
pub section_id: SectionID,
pub mode: RoomEntityMode,
pub episode: Episode,
pub difficulty: Difficulty,
}
#[derive(Debug, Clone)]
pub struct NewRoomEntity {
pub name: String,
pub section_id: SectionID,
pub mode: RoomEntityMode,
pub episode: Episode,
pub difficulty: Difficulty,
}
#[derive(Debug, Copy, Clone, Serialize)]
pub enum RoomNote {
Create {
character_id: CharacterEntityId,
},
PlayerJoin {
character_id: CharacterEntityId,
},
PlayerLeave {
character_id: CharacterEntityId,
},
QuestStart {
// quest id
},
QuestComplete {
// quest id
},
}

View File

@ -1,31 +0,0 @@
use serde::{Serialize, Deserialize};
use super::account::UserAccountId;
// [2022-10-23 00:11:18][elseware::common::mainloop::client][WARN] error RecvServerPacket::from_bytes: WrongPacketForServerType(490, [40, 0, 234, 1, 0, 0, 0, 0, 9, 0, 74, 0, 97, 0, 115, 0, 100, 0, 102, 0, 0, 0, 0, 0, 192, 52, 67, 3, 60, 159, 129, 0, 32, 64, 233, 10, 196, 156, 152, 0])
// [2022-10-23 00:20:14][elseware::common::mainloop::client][WARN] error RecvServerPacket::from_bytes: WrongPacketForServerType(490, [40, 0, 234, 1, 0, 0, 0, 0, 9, 0, 74, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 0, 0, 152, 0])
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TeamEntityId(pub u32);
pub struct NewTeamEntity {
pub created_by: UserAccountId,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct TeamEntity {
pub id: TeamEntityId,
pub owner: UserAccountId,
pub name: String,
pub team_flag: [u8; 2048],
}

View File

@ -1,25 +0,0 @@
[package]
name = "items"
version = "0.1.0"
edition = "2021"
[dependencies]
entity = { workspace = true }
maps = { workspace = true }
shops = { workspace = true }
location = { workspace = true }
drops = { workspace = true }
libpso = { workspace = true }
enum-utils = { workspace = true }
derive_more = { workspace = true }
serde = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
async-recursion = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true }

File diff suppressed because it is too large Load Diff

View File

@ -1,561 +0,0 @@
use std::collections::{HashMap, BinaryHeap};
use std::cmp::Reverse;
use async_std::sync::{Arc, RwLock, Mutex};
use futures::stream::{FuturesOrdered, StreamExt};
use anyhow::Context;
use entity::gateway::{EntityGateway, GatewayError};
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankIdentifier};
use entity::item::tool::Tool;
use entity::item::weapon::Weapon;
use entity::item::mag::Mag;
use drops::ItemDrop;
use crate::ClientItemId;
use crate::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState};
use crate::floor::{FloorState, FloorItem, LocalFloor, SharedFloor, FloorType};
use crate::bank::{Bank, BankState, BankItem, BankItemDetail, BankError};
use location::{AreaClient, RoomId};
#[derive(thiserror::Error, Debug)]
pub enum ItemStateError {
#[error("character {0} not found")]
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")]
NotATool(ClientItemId),
#[error("bank item {0} not found")]
NoBankItem(ClientItemId),
#[error("inventory error {0}")]
InventoryError(#[from] InventoryError),
#[error("bank error {0}")]
BankError(#[from] BankError),
#[error("invalid item id {0}")]
InvalidItemId(ClientItemId),
#[error("invalid drop? {0:?} (this shouldn't occur)")]
BadItemDrop(ItemDrop),
#[error("idk")]
Dummy,
#[error("gateway")]
GatewayError(#[from] GatewayError),
#[error("tried to remove more meseta than exists: {0}")]
InvalidMesetaRemoval(u32),
#[error("tried to add meseta when there is no more room")]
FullOfMeseta,
#[error("stacked item")]
StackedItemError(Vec<ItemEntity>),
#[error("apply item {0}")]
ApplyItemError(#[from] crate::apply_item::ApplyItemError),
#[error("item is not a mag {0}")]
NotAMag(ClientItemId),
#[error("item is not mag food {0}")]
NotMagFood(ClientItemId),
#[error("item is not sellable")]
ItemNotSellable,
#[error("could not modify item")]
InvalidModifier,
#[error("wrong item type {0}")]
WrongItemType(ClientItemId),
}
#[derive(Clone, Debug)]
pub struct IndividualItemDetail {
pub entity_id: ItemEntityId,
pub item: ItemDetail,
}
impl IndividualItemDetail {
pub fn as_weapon(&self) -> Option<&Weapon> {
match &self.item {
ItemDetail::Weapon(weapon) => Some(weapon),
_ => None
}
}
pub fn as_mag(&self) -> Option<&Mag> {
match &self.item {
ItemDetail::Mag(mag) => Some(mag),
_ => None
}
}
pub fn as_mag_mut(&mut self) -> Option<&mut Mag> {
match &mut self.item {
ItemDetail::Mag(mag) => Some(mag),
_ => None
}
}
pub fn as_weapon_mut(&mut self) -> Option<&mut Weapon> {
match &mut self.item {
ItemDetail::Weapon(weapon) => Some(weapon),
_ => None
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match &self.item {
ItemDetail::Weapon(w) => w.as_bytes(),
ItemDetail::Armor(a) => a.as_bytes(),
ItemDetail::Shield(s) => s.as_bytes(),
ItemDetail::Unit(u) => u.as_bytes(),
ItemDetail::Tool(t) => t.as_individual_bytes(),
ItemDetail::TechniqueDisk(d) => d.as_bytes(),
ItemDetail::Mag(m) => m.as_bytes(),
ItemDetail::ESWeapon(e) => e.as_bytes(),
}
}
}
#[derive(Clone, Debug)]
pub struct StackedItemDetail {
pub entity_ids: Vec<ItemEntityId>,
pub tool: Tool,
}
impl StackedItemDetail {
pub fn count(&self) -> usize {
self.entity_ids.len()
}
}
#[derive(Clone)]
pub enum AddItemResult {
NewItem,
AddToStack,
Meseta,
}
#[derive(Clone, Debug)]
struct RoomGemItemIdCounter {
inventory: [Arc<Mutex<u32>>; 4],
bank: [Arc<Mutex<u32>>; 4],
}
impl Default for RoomGemItemIdCounter {
fn default() -> RoomGemItemIdCounter {
RoomGemItemIdCounter {
inventory: core::array::from_fn(|gem| Arc::new(Mutex::new(((gem as u32) << 21) | 0x10000))),
bank: core::array::from_fn(|gem| Arc::new(Mutex::new(((gem as u32) << 21) | 0x20000))),
}
}
}
impl RoomGemItemIdCounter {
fn inventory(&self, area_client: &AreaClient) -> Arc<Mutex<u32>> {
self.inventory[area_client.local_client.id() as usize].clone()
}
fn bank(&self, area_client: &AreaClient) -> Arc<Mutex<u32>> {
self.bank[area_client.local_client.id() as usize].clone()
}
}
#[derive(Clone, Debug)]
pub struct ItemState {
character_inventory: Arc<RwLock<HashMap<CharacterEntityId, RwLock<InventoryState>>>>,
character_bank: Arc<RwLock<HashMap<CharacterEntityId, RwLock<BankState>>>>,
character_room: Arc<RwLock<HashMap<CharacterEntityId, RoomId>>>,
character_floor: Arc<RwLock<HashMap<CharacterEntityId, RwLock<LocalFloor>>>>,
room_floor: Arc<RwLock<HashMap<RoomId, RwLock<SharedFloor>>>>,
room_gem_item_ids: Arc<RwLock<HashMap<RoomId, RoomGemItemIdCounter>>>,
room_item_id_counter: Arc<RwLock<u32>>,
}
impl Default for ItemState {
fn default() -> ItemState {
ItemState {
character_inventory: Arc::new(RwLock::new(HashMap::new())),
character_bank: Arc::new(RwLock::new(HashMap::new())),
character_room: Arc::new(RwLock::new(HashMap::new())),
character_floor: Arc::new(RwLock::new(HashMap::new())),
room_floor: Arc::new(RwLock::new(HashMap::new())),
room_gem_item_ids: Arc::new(RwLock::new(HashMap::new())),
room_item_id_counter: Arc::new(RwLock::new(0x00810000)),
}
}
}
impl ItemState {
pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result<InventoryState, anyhow::Error> {
Ok(self.character_inventory
.read()
.await
.get(&character.id)
.ok_or_else(|| ItemStateError::NoCharacter(character.id))?
.read()
.await
.clone())
}
pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result<BankState, anyhow::Error> {
Ok(self.character_bank
.read()
.await
.get(&character.id)
.ok_or_else(|| ItemStateError::NoCharacter(character.id))?
.read()
.await
.clone())
}
}
impl ItemState {
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_inventory<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> {
let inventory = entity_gateway.get_character_inventory(&character.id).await?;
let equipped = entity_gateway.get_character_equips(&character.id).await?;
let inventory_items = inventory.items.into_iter()
.map(|item| -> Result<InventoryItem, anyhow::Error> {
Ok(match item {
InventoryItemEntity::Individual(item) => {
InventoryItem {
item_id: ClientItemId(0),
item: InventoryItemDetail::Individual(IndividualItemDetail {
entity_id: item.id,
item: item.item,
}),
}
},
InventoryItemEntity::Stacked(items) => {
InventoryItem {
item_id: ClientItemId(0),
item: InventoryItemDetail::Stacked(StackedItemDetail {
entity_ids: items.iter().map(|i| i.id).collect(),
tool: items.get(0)
.ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
.item
.clone()
.as_tool()
.ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
})
}
},
})
})
.collect::<Result<Vec<_>, anyhow::Error>>()?;
let character_meseta = entity_gateway.get_character_meseta(&character.id).await?;
let inventory_state = InventoryState {
character_id: character.id,
item_id_counter: Arc::new(Mutex::new(0)),
inventory: Inventory::new(inventory_items),
equipped,
meseta: character_meseta,
};
self.character_inventory
.write()
.await
.insert(character.id, RwLock::new(inventory_state));
Ok(())
}
pub async fn load_character_bank<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, bank_identifier: BankIdentifier) -> Result<(), anyhow::Error> {
let bank = entity_gateway.get_character_bank(&character.id, &bank_identifier).await?;
let bank_items = bank.items
.into_iter()
.map(|item| {
Ok(Reverse(match item {
BankItemEntity::Individual(item) => {
BankItemDetail::Individual(IndividualItemDetail {
entity_id: item.id,
item: item.item,
})
},
BankItemEntity::Stacked(items) => {
BankItemDetail::Stacked(StackedItemDetail {
entity_ids: items.iter().map(|i| i.id).collect(),
tool: items.get(0)
.ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
.item
.clone()
.as_tool()
.ok_or_else(|| ItemStateError::StackedItemError(items.clone()))?
})
}
}))
})
.collect::<Result<BinaryHeap<_>, anyhow::Error>>()?
.into_iter()
.map(|item| {
let mut citem_state = self.clone();
async move {
Ok(BankItem {
item_id: citem_state.new_item_id().await?,
item: item.0,
})
}
})
.collect::<FuturesOrdered<_>>()
.collect::<Vec<_>>()
.await
.into_iter()
.collect::<Result<Vec<_>, anyhow::Error>>()?;
let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &bank_identifier).await?;
let bank_state = BankState::new(character.id, bank_identifier, Bank::new(bank_items), bank_meseta);
self.character_bank
.write()
.await
.insert(character.id, RwLock::new(bank_state));
Ok(())
}
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> {
self.load_character_inventory(entity_gateway, character).await?;
self.load_character_bank(entity_gateway, character, BankIdentifier::Character).await?;
Ok(())
}
pub async fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) {
let mut base_item_ids = self.room_gem_item_ids
.write()
.await;
let base_item_ids = base_item_ids
.entry(room_id)
.or_insert_with(RoomGemItemIdCounter::default);
self.character_inventory
.read()
.await
.get(&character.id)
.unwrap()
.write()
.await
.initialize_item_ids(base_item_ids.inventory(&area_client).clone())
.await;
self.character_bank
.read()
.await
.get(&character.id)
.unwrap()
.write()
.await
.initialize_item_ids(base_item_ids.bank(&area_client))
.await;
self.character_room
.write()
.await
.insert(character.id, room_id);
self.character_floor
.write()
.await
.insert(character.id, RwLock::new(LocalFloor::default()));
self.room_floor
.write()
.await
.entry(room_id)
.or_insert_with(Default::default);
}
pub async fn remove_character_from_room(&mut self, character: &CharacterEntity) {
self.character_inventory
.write()
.await
.remove(&character.id);
self.character_floor
.write()
.await
.remove(&character.id);
let removed = {
self.character_room.write().await.remove(&character.id)
};
if let Some(room) = removed.as_ref() {
// TODO: this looks wrong, .all(r != room) maybe?
if self.character_room.read().await.iter().any(|(_, r)| r == room) {
self.room_floor
.write()
.await
.remove(room);
}
}
}
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;
let local_floor = local_floors
.get(character_id)
.ok_or_else(|| ItemStateError::NoCharacter(*character_id))?
.read()
.await;
let rooms = self.character_room
.read()
.await;
let room = rooms
.get(character_id)
.ok_or_else(||ItemStateError::NoCharacter(*character_id))?;
let shared_floors = self.room_floor
.read()
.await;
let shared_floor = shared_floors
.get(room)
.ok_or_else(||ItemStateError::NoCharacter(*character_id))?
.read()
.await;
local_floor.0
.iter()
.find(|item| item.item_id == *item_id)
.map(|item| (item.clone(), FloorType::Local))
.or_else(|| {
shared_floor.0
.iter()
.find(|item| item.item_id == *item_id)
.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:#?}"))
}
}
#[derive(Default, Clone)]
struct ProxiedItemState {
character_inventory: Arc<Mutex<HashMap<CharacterEntityId, InventoryState>>>,
character_bank: Arc<Mutex<HashMap<CharacterEntityId, BankState>>>,
//character_room: HashMap<CharacterEntityId, RoomId>,
character_floor: Arc<Mutex<HashMap<CharacterEntityId, LocalFloor>>>,
room_floor: Arc<Mutex<HashMap<RoomId, SharedFloor>>>,
}
#[derive(Clone)]
pub struct ItemStateProxy {
item_state: ItemState,
proxied_state: ProxiedItemState,
}
impl ItemStateProxy {
pub async fn commit(self) {
async fn copy_back<K, V>(master: &Arc<RwLock<HashMap<K, RwLock<V>>>>,
proxy: Arc<Mutex<HashMap<K, V>>>)
where
K: Eq + std::hash::Hash,
V: Clone,
{
for (key, value) in proxy.lock().await.iter() {
if let Some(element) = master
.read()
.await
.get(key) {
*element
.write()
.await = value.clone();
}
}
}
copy_back(&self.item_state.character_inventory, self.proxied_state.character_inventory).await;
copy_back(&self.item_state.character_bank, self.proxied_state.character_bank).await;
//copy_back(self.item_state.character_room, self.proxied_state.character_room).await;
copy_back(&self.item_state.character_floor, self.proxied_state.character_floor).await;
copy_back(&self.item_state.room_floor, self.proxied_state.room_floor).await;
}
}
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, anyhow::Error>
where
K: Eq + std::hash::Hash + Copy,
V: Clone
{
let existing_element = master
.read()
.await
.get(&key)
.ok_or_else(|| err(key))?
.read()
.await
.clone();
Ok(proxy
.lock()
.await
.entry(key)
.or_insert_with(|| existing_element)
.clone())
}
impl ItemStateProxy {
pub fn new(item_state: ItemState) -> Self {
ItemStateProxy {
item_state,
proxied_state: Default::default(),
}
}
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,
ItemStateError::NoCharacter).await
}
pub async fn set_inventory(&mut self, inventory: InventoryState) {
self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory);
}
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,
ItemStateError::NoCharacter).await
}
pub async fn set_bank(&mut self, bank: BankState) {
self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank);
}
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)
.ok_or_else(|| anyhow::Error::from(ItemStateError::NoCharacter(*character_id)))
.with_context(|| format!("character {character_id}\nrooms: {:#?}", self.item_state.character_room))?;
Ok(FloorState {
character_id: *character_id,
local: get_or_clone(&self.item_state.character_floor, &self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter).await
.with_context(|| format!("no local_floor state: {character_id:?} {:#?}\nproxy: {:#?}", self.item_state.character_floor, self.proxied_state.character_floor))?,
shared: get_or_clone(&self.item_state.room_floor, &self.proxied_state.room_floor, room_id, ItemStateError::NoRoom).await
.with_context(|| format!("no share_floor state: {character_id:?} {:#?}\nproxy: {:#?}", self.item_state.room_floor, self.proxied_state.room_floor))?,
})
}
pub async fn set_floor(&mut self, floor: FloorState) {
let room_id = *self.item_state.character_room.read().await.get(&floor.character_id)
.ok_or_else(|| anyhow::Error::from(ItemStateError::NoCharacter(floor.character_id)))
.with_context(|| format!("character {}\nrooms: {:#?}", floor.character_id, self.item_state.character_room)).unwrap();
self.proxied_state.character_floor.lock().await.insert(floor.character_id, floor.local);
self.proxied_state.room_floor.lock().await.insert(room_id, floor.shared);
}
pub async fn new_item_id(&mut self) -> Result<ClientItemId, anyhow::Error> {
self.item_state.new_item_id().await
}
}

View File

@ -1,564 +0,0 @@
use futures::future::BoxFuture;
use crate::ClientItemId;
use entity::item::Meseta;
use maps::area::MapArea;
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::gateway::{EntityGateway, EntityGatewayTransaction};
use entity::item::ItemModifier;
use entity::room::RoomEntityId;
use crate::state::{ItemState, ItemStateProxy, IndividualItemDetail};
use crate::itemstateaction::{ItemStateAction, ItemAction};
use crate::inventory::InventoryItem;
use crate::floor::FloorItem;
use shops::ShopItem;
use crate::trade::TradeItem;
use location::AreaClient;
use drops::ItemDrop;
use maps::monster::MonsterType;
use crate::actions;
pub fn pick_up_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
) -> BoxFuture<'a, Result<actions::TriggerCreateItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
EG::Transaction<'a>: Clone,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_floor(character.id, *item_id))
.act(actions::add_floor_item_to_inventory(character))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn drop_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
map_area: MapArea,
drop_position: (f32, f32, f32),
)-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
EG::Transaction<'a>: Clone,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_inventory(character.id, *item_id, 0))
.act(actions::add_inventory_item_to_shared_floor(character.id, map_area, drop_position))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn drop_partial_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
map_area: MapArea,
drop_position: (f32, f32),
amount: u32
) -> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_inventory(character.id, *item_id, amount))
.act(actions::add_inventory_item_to_shared_floor(character.id, map_area, (drop_position.0, 0.0, drop_position.1)))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn drop_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
map_area: MapArea,
drop_position: (f32, f32),
amount: u32,
) -> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_meseta_from_inventory(character.id, amount))
.act(actions::add_meseta_to_shared_floor(character.id, amount, map_area, drop_position))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn withdraw_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
amount: u32,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_meseta_from_bank(character.id, amount))
.act(actions::add_meseta_from_bank_to_inventory(character.id, amount))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn deposit_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
amount: u32,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), _) = ItemStateAction::default()
.act(actions::take_meseta_from_inventory(character.id, amount))
.act(actions::add_meseta_to_bank(character.id, amount))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, ()))
})
}
pub fn withdraw_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
amount: u32,
) -> BoxFuture<'a, Result<InventoryItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_bank(character.id, *item_id, amount))
//.act(bank_item_to_inventory_item)
//.act(add_item_to_inventory)
.act(actions::add_bank_item_to_inventory(character))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn deposit_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
amount: u32,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_inventory(character.id, *item_id, amount))
.act(actions::add_inventory_item_to_bank(character.id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn equip_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
equip_slot: u8,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::equip_inventory_item(character.id, *item_id, equip_slot))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn unequip_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::unequip_inventory_item(character.id, *item_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn sort_inventory<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_ids: Vec<ClientItemId>,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::sort_inventory_items(character.id, item_ids))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn use_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a mut CharacterEntity,
area_client: AreaClient,
item_id: &'a ClientItemId,
amount: u32,
) -> BoxFuture<'a, Result<Vec<actions::CreateItem>, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), (pkts, new_character)) = ItemStateAction::default()
.act(actions::remove_item_from_inventory(character.id, *item_id, amount))
.act(actions::use_consumed_item(character))
.act(actions::fork(
actions::foreach(actions::apply_item_action_packets(character.id, area_client)),
actions::apply_item_action_character(character)
))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
*character = new_character;
Ok((transaction, pkts.into_iter().flatten().collect()))
})
}
pub fn feed_mag<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
mag_item_id: &'a ClientItemId,
tool_item_id: &'a ClientItemId,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), _) = ItemStateAction::default()
.act(actions::take_item_from_inventory(character.id, *tool_item_id, 1))
.act(actions::feed_mag_item(character.clone(), *mag_item_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, ()))
})
}
pub fn buy_shop_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
shop_item: &'a (dyn ShopItem + Send + Sync),
item_id: ClientItemId,
amount: u32,
) -> BoxFuture<'a, Result<InventoryItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
let item_price = shop_item.price() as u32 * amount;
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_meseta_from_inventory(character.id, item_price))
//.act(bought_item_to_inventory_item)
//.act(add_item_to_inventory)
.act(actions::add_bought_item_to_inventory(character.id, shop_item, item_id, amount))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
pub fn sell_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: ClientItemId,
amount: u32,
) -> BoxFuture<'a, Result<InventoryItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_inventory(character.id, item_id, amount))
.act(actions::sell_inventory_item(character.id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}
#[allow(clippy::type_complexity)]
pub fn trade_items<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
p1: (&'a AreaClient, &'a CharacterEntity, &'a Vec<TradeItem>, Meseta),
p2: (&'a AreaClient, &'a CharacterEntity, &'a Vec<TradeItem>, Meseta))
-> BoxFuture<'a, Result<(Vec<InventoryItem>, Vec<InventoryItem>), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
let p1_trade_items = p1.2
.iter()
.map(|item| {
match item {
TradeItem::Individual(item_id) => (*item_id, 1),
TradeItem::Stacked(item_id, amount) => (*item_id, *amount as u32),
}
})
.collect();
let p2_trade_items = p2.2
.iter()
.map(|item| {
match item {
TradeItem::Individual(item_id) => (*item_id, 1),
TradeItem::Stacked(item_id, amount) => (*item_id, *amount as u32),
}
})
.collect();
entity_gateway.with_transaction(move |mut transaction| async move {
let p1_id = p1.1.id;
let p2_id = p2.1.id;
let trade = transaction.gateway().create_trade(&p1_id, &p2_id).await?;
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), p1_removed_items) = ItemStateAction::default()
.act(actions::iterate(p1_trade_items, move |p1_trade_item| actions::take_item_from_inventory(p1_id, p1_trade_item.0, p1_trade_item.1) ))
.act(actions::foreach(actions::assign_new_item_id()))
.commit((item_state_proxy, transaction))
.await?;
let ((item_state_proxy, transaction), p2_removed_items) = ItemStateAction::default()
.act(actions::iterate(p2_trade_items, move |p2_trade_item| actions::take_item_from_inventory(p2_id, p2_trade_item.0, p2_trade_item.1) ))
.act(actions::foreach(actions::assign_new_item_id()))
.commit((item_state_proxy, transaction))
.await?;
let ((item_state_proxy, transaction), p2_new_items) = ItemStateAction::default()
.act(actions::insert(p1_removed_items))
.act(actions::foreach(actions::add_item_to_inventory(p2.1.clone())))
.act(actions::record_trade(trade.id, p1_id, p2_id))
.commit((item_state_proxy, transaction))
.await?;
let ((item_state_proxy, transaction), p1_new_items) = ItemStateAction::default()
.act(actions::insert(p2_removed_items))
.act(actions::foreach(actions::add_item_to_inventory(p1.1.clone())))
.act(actions::record_trade(trade.id, p2_id, p1_id))
.commit((item_state_proxy, transaction))
.await?;
let ((item_state_proxy, transaction), _) = ItemStateAction::default()
.act(actions::take_meseta_from_inventory(p1_id, p1.3.0))
.act(actions::take_meseta_from_inventory(p2_id, p2.3.0))
.act(actions::add_meseta_to_inventory(p1_id, p2.3.0))
.act(actions::add_meseta_to_inventory(p2_id, p1.3.0))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, (p1_new_items, p2_new_items)))
})
}
pub fn take_meseta<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: &'a CharacterEntityId,
meseta: Meseta)
-> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), _) = ItemStateAction::default()
.act(actions::take_meseta_from_inventory(*character_id, meseta.0))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, ()))
})
}
pub fn enemy_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default()
.act(actions::convert_item_drop_to_floor_item(item_drop))
.act(actions::item_note_enemy_drop(character_id, room_id, monster_type))
.act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, floor_item))
})
}
pub fn box_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default()
.act(actions::convert_item_drop_to_floor_item(item_drop))
.act(actions::item_note_box_drop(character_id, room_id))
.act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, floor_item))
})
}
pub fn apply_modifier<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: ClientItemId,
modifier: ItemModifier)
-> BoxFuture<'a, Result<IndividualItemDetail, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), item) = ItemStateAction::default()
.act(actions::take_item_from_inventory(character.id, item_id, 1))
.act(actions::apply_modifier_to_inventory_item(modifier))
.act(actions::add_item_to_inventory(character.clone()))
.act(actions::as_individual_item())
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, item))
})
}
pub fn floor_item_limit_reached<'a, EG> (
item_state: &'a ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
map_area: MapArea
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
EG::Transaction<'a>: Clone,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_floor(character.id, *item_id))
.act(actions::delete_item_from_floor(map_area))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}

View File

@ -1,38 +0,0 @@
use crate::ClientItemId;
#[derive(Debug, Clone)]
pub enum TradeItem {
Individual(ClientItemId),
Stacked(ClientItemId, usize),
}
impl TradeItem {
pub fn stacked(&self) -> Option<(ClientItemId, usize)> {
match self {
TradeItem::Stacked(item_id, amount) => Some((*item_id, *amount)),
_ => None
}
}
pub fn stacked_mut(&mut self) -> Option<(ClientItemId, &mut usize)> {
match self {
TradeItem::Stacked(item_id, ref mut amount) => Some((*item_id, amount)),
_ => None
}
}
pub fn item_id(&self) -> ClientItemId {
match self {
TradeItem::Individual(item_id) => *item_id,
TradeItem::Stacked(item_id, _) => *item_id,
}
}
pub fn amount(&self) -> usize {
match self {
TradeItem::Individual(_) => 1,
TradeItem::Stacked(_, amount) => *amount,
}
}
}

13
src/lib.rs Normal file
View File

@ -0,0 +1,13 @@
#![allow(incomplete_features)]
#![feature(inline_const)]
#![feature(drain_filter)]
#![feature(try_blocks)]
extern crate fix_hidden_lifetime_bug;
pub mod common;
pub mod entity;
pub mod patch;
pub mod login;
pub mod ship;

View File

@ -1,12 +0,0 @@
[package]
name = "location"
version = "0.1.0"
edition = "2021"
[dependencies]
networking = { workspace = true }
async-std = { workspace = true }
derive_more = { workspace = true }
futures= { workspace = true }
thiserror = { workspace = true }

View File

@ -1,676 +0,0 @@
#![allow(dead_code, unused_must_use)]
use std::collections::HashMap;
use std::time::SystemTime;
use thiserror::Error;
use networking::serverstate::ClientId;
use async_std::sync::{Arc, RwLock};
use futures::{stream, StreamExt};
use std::pin::pin;
pub const MAX_ROOMS: usize = 128;
pub enum AreaType {
Room,
Lobby,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct LobbyId(pub usize);
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub struct RoomId(pub usize);
impl LobbyId {
pub fn id(&self) -> u8 {
self.0 as u8
}
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum CreateRoomError {
#[error("no open slots")]
NoOpenSlots,
#[error("client already in area")]
ClientInAreaAlready,
#[error("join error")]
JoinError,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum JoinRoomError {
#[error("room does not exist")]
RoomDoesNotExist,
#[error("room is full")]
RoomFull,
#[error("client already in area")]
ClientInAreaAlready,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum JoinLobbyError {
#[error("lobby does not exist")]
LobbyDoesNotExist,
#[error("lobby is full")]
LobbyFull,
#[error("client already in area")]
ClientInAreaAlready,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum GetAreaError {
#[error("not in a room")]
NotInRoom,
#[error("not in a lobby")]
NotInLobby,
#[error("get area: invalid client")]
InvalidClient,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum ClientRemovalError {
#[error("client removal: client not in area")]
ClientNotInArea,
#[error("client removal: invalid area")]
InvalidArea,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum GetClientsError {
#[error("invalid client")]
InvalidClient,
#[error("invalid area")]
InvalidArea,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum GetNeighborError {
#[error("get neighbor: invalid client")]
InvalidClient,
#[error("get neighbor: invalid area")]
InvalidArea,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum GetLeaderError {
#[error("get leader: invalid client")]
InvalidClient,
#[error("get leader: invalid area")]
InvalidArea,
#[error("get leader: client not in area")]
NoClientInArea,
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum ClientLocationError {
#[error("create room error {0}")]
CreateRoomError(#[from] CreateRoomError),
#[error("join room error {0}")]
JoinRoomError(#[from] JoinRoomError),
#[error("join lobby error {0}")]
JoinLobbyError(#[from] JoinLobbyError),
#[error("get area error {0}")]
GetAreaError(#[from] GetAreaError),
#[error("client removal error {0}")]
ClientRemovalError(#[from] ClientRemovalError),
#[error("get clients error {0}")]
GetClientsError(#[from] GetClientsError),
#[error("get neighbor error {0}")]
GetNeighborError(#[from] GetNeighborError),
#[error("get leader error {0}")]
GetLeaderError(#[from] GetLeaderError)
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct LocalClientId(usize);
impl LocalClientId {
pub fn id(&self) -> u8 {
self.0 as u8
}
}
impl PartialEq<u8> for LocalClientId {
fn eq(&self, other: &u8) -> bool {
self.0 == *other as usize
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AreaClient {
pub client: ClientId,
pub local_client: LocalClientId,
time_join: SystemTime,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Lobby([Option<AreaClient>; 12]);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Room([Option<AreaClient>; 4]);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RoomLobby {
Room(RoomId),
Lobby(LobbyId),
}
#[derive(Clone, Debug)]
pub struct ClientLocation {
lobbies: [Arc<RwLock<Lobby>>; 15],
rooms: [Arc<RwLock<Option<Room>>>; MAX_ROOMS],
client_location: Arc<RwLock<HashMap<ClientId, RoomLobby>>>,
}
impl Default for ClientLocation {
fn default() -> ClientLocation {
ClientLocation {
lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))),
rooms: core::array::from_fn(|_| Arc::new(RwLock::new(None))),
client_location: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl ClientLocation {
pub async fn add_client_to_lobby(&self, id: ClientId, lobby_id: LobbyId) -> Result<(), JoinLobbyError> {
{
let lobby = self.lobbies
.get(lobby_id.0)
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
.read()
.await;
if lobby.0.iter().all(|c| c.is_some()) {
return Err(JoinLobbyError::LobbyFull);
}
}
self.remove_client_from_area(id).await;
let mut lobby = self.lobbies
.get(lobby_id.0)
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
.write()
.await;
let (index, empty_slot) = lobby.0.iter_mut()
.enumerate()
.find(|(_, k)| k.is_none())
.ok_or(JoinLobbyError::LobbyFull)?;
*empty_slot = Some(AreaClient {
client: id,
local_client: LocalClientId(index),
time_join: SystemTime::now(),
});
self.client_location
.write()
.await
.insert(id, RoomLobby::Lobby(lobby_id));
Ok(())
}
pub async fn add_client_to_next_available_lobby(&self, id: ClientId, lobby: LobbyId) -> Result<LobbyId, JoinLobbyError> {
pin!(stream::iter(0..15)
.filter_map(|lobby_index| async move {
let new_lobby = LobbyId((lobby.0 + lobby_index) % 15);
Some((new_lobby, self.add_client_to_lobby(id, new_lobby).await.ok()?))
}))
.next()
.await
.map(|l| l.0)
.ok_or(JoinLobbyError::LobbyFull)
}
pub async fn create_new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
let (index, empty_slot) = Box::pin(stream::iter(self.rooms.iter())
.enumerate()
.filter(|(_, r)| async {r.read().await.is_none()}))
.next()
.await
.ok_or(CreateRoomError::NoOpenSlots)?;
*empty_slot.write().await = Some(Room([None; 4]));
self.add_client_to_room(id, RoomId(index))
.await
.map_err(|_err| CreateRoomError::JoinError)?;
Ok(RoomId(index))
}
pub async fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> {
let mut r = self.rooms.get(room.0)
.ok_or(JoinRoomError::RoomDoesNotExist)?
.as_ref()
.write()
.await;
let r = r.as_mut()
.ok_or(JoinRoomError::RoomDoesNotExist)?;
let (index, empty_slot) = r.0.iter_mut()
.enumerate()
.find(|(_, k)| k.is_none())
.ok_or(JoinRoomError::RoomFull)?;
*empty_slot = Some(AreaClient {
client: id,
local_client: LocalClientId(index),
time_join: SystemTime::now(),
});
self.remove_client_from_area(id).await;
self.client_location
.write()
.await
.insert(id, RoomLobby::Room(room));
Ok(())
}
pub async fn get_all_clients_by_client(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
let area = self.client_location
.read()
.await;
let area = area
.get(&id)
.ok_or(GetNeighborError::InvalidClient)?;
match area {
RoomLobby::Room(room) => {
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
.into_iter()
.collect())
},
RoomLobby::Lobby(lobby) => {
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
.into_iter()
.collect())
}
}
}
pub async fn get_client_neighbors(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
let area = self.client_location
.read()
.await;
let area = area
.get(&id)
.ok_or(GetNeighborError::InvalidClient)?;
match area {
RoomLobby::Room(room) => {
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
.into_iter()
.filter(|c| c.client != id)
.collect())
},
RoomLobby::Lobby(lobby) => {
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
.into_iter()
.filter(|c| c.client != id)
.collect())
}
}
}
pub async fn get_room_leader(&self, room: RoomId) -> Result<AreaClient, GetLeaderError> {
let r = self.rooms[room.0]
.as_ref()
.read()
.await
.ok_or(GetLeaderError::InvalidArea)?;
let mut r = r
.0
.iter()
.flatten()
.collect::<Vec<_>>();
r.sort_by_key(|k| k.time_join);
let c = r.get(0)
.ok_or(GetLeaderError::NoClientInArea)?;
Ok(**c)
}
pub async fn get_lobby_leader(&self, lobby: LobbyId) -> Result<AreaClient, GetLeaderError> {
let l = self.lobbies[lobby.0]
.read()
.await;
let mut l = l
.0
.iter()
.flatten()
.collect::<Vec<_>>();
l.sort_by_key(|k| k.time_join);
let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?;
Ok(**c)
}
pub async fn get_area_leader(&self, roomlobby: RoomLobby) -> Result<AreaClient, GetLeaderError> {
match roomlobby {
RoomLobby::Room(room) => {
self.get_room_leader(room).await
},
RoomLobby::Lobby(lobby) => {
self.get_lobby_leader(lobby).await
}
}
}
pub async fn get_leader_by_client(&self, id: ClientId) -> Result<AreaClient, GetLeaderError> {
let area = self.client_location
.read()
.await;
let area = area
.get(&id)
.ok_or(GetLeaderError::InvalidClient)?;
match area {
RoomLobby::Room(room) => {
self.get_room_leader(*room).await
},
RoomLobby::Lobby(lobby) => {
self.get_lobby_leader(*lobby).await
}
}
}
pub async fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result<Vec<AreaClient>, GetClientsError> {
Ok(self.lobbies
.get(lobby.0)
.ok_or(GetClientsError::InvalidArea)?
.read()
.await
.0
.iter()
.filter_map(|client| {
client.map(|c| {
c
})
}).collect())
}
pub async fn get_clients_in_room(&self, room: RoomId) -> Result<Vec<AreaClient>, GetClientsError> {
Ok(self.rooms.get(room.0)
.ok_or(GetClientsError::InvalidArea)?
.as_ref()
.read()
.await
.ok_or(GetClientsError::InvalidArea)?
.0
.iter()
.filter_map(|client| {
client.map(|c| {
c
})
}).collect())
}
pub async fn get_local_client(&self, id: ClientId) -> Result<AreaClient, GetClientsError> {
let area = self.client_location
.read()
.await;
let area = area
.get(&id)
.ok_or(GetClientsError::InvalidClient)?;
match area {
RoomLobby::Room(room) => {
self.get_clients_in_room(*room)
.await
.map_err(|_| GetClientsError::InvalidArea)?
.into_iter()
.find(|c| c.client == id)
.ok_or(GetClientsError::InvalidClient)
},
RoomLobby::Lobby(lobby) => {
self.get_clients_in_lobby(*lobby)
.await
.map_err(|_| GetClientsError::InvalidArea)?
.into_iter()
.find(|c| c.client == id)
.ok_or(GetClientsError::InvalidClient)
}
}
}
pub async fn get_area(&self, id: ClientId) -> Result<RoomLobby, GetAreaError> {
self.client_location
.read()
.await
.get(&id)
.ok_or(GetAreaError::InvalidClient)
.map(Clone::clone)
}
pub async fn get_room(&self, id: ClientId) -> Result<RoomId, GetAreaError> {
if let RoomLobby::Room(room) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
Ok(*room)
}
else {
Err(GetAreaError::NotInRoom)
}
}
pub async fn get_lobby(&self, id: ClientId) -> Result<LobbyId, GetAreaError> {
if let RoomLobby::Lobby(lobby) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
Ok(*lobby)
}
else {
Err(GetAreaError::NotInLobby)
}
}
pub async fn remove_client_from_area(&self, id: ClientId) -> Result<(), ClientRemovalError> {
fn remove_client<const N: usize>(id: ClientId, client_list : &mut [Option<AreaClient>; N]) {
client_list
.iter_mut()
.filter(|client| {
client.map_or(false, |c| {
c.client == id
})
})
.for_each(|client| {
*client = None
});
}
let area = self.client_location
.read()
.await;
let area = area
.get(&id)
.ok_or(ClientRemovalError::ClientNotInArea)?;
match area {
RoomLobby::Room(room) => {
let mut r = self.rooms.get(room.0)
.ok_or(ClientRemovalError::InvalidArea)?
.as_ref()
.write()
.await;
if let Some(r) = r.as_mut() {
remove_client(id, &mut r.0)
}
else {
return Err(ClientRemovalError::InvalidArea)
}
},
RoomLobby::Lobby(lobby) => {
remove_client(id, &mut self.lobbies[lobby.0].write().await.0)
}
};
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[async_std::test]
async fn test_add_client_to_lobby() {
let cl = ClientLocation::default();
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
cl.add_client_to_lobby(ClientId(13), LobbyId(1)).await.unwrap();
cl.add_client_to_lobby(ClientId(14), LobbyId(0)).await.unwrap();
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.into_iter().flatten().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
(ClientId(12), LocalClientId(0)),
(ClientId(14), LocalClientId(1)),
]);
}
#[async_std::test]
async fn test_add_client_to_full_lobby() {
let cl = ClientLocation::default();
for i in 0..12 {
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
}
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await == Err(JoinLobbyError::LobbyFull));
}
#[async_std::test]
async fn test_add_client_to_next_available_lobby() {
let cl = ClientLocation::default();
for lobby in 1..4 {
for i in 0..12 {
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
}
}
assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await == Ok(LobbyId(4)));
}
#[async_std::test]
async fn test_add_to_lobby_when_all_are_full() {
let cl = ClientLocation::default();
for lobby in 0..15 {
for i in 0..12 {
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
}
}
assert_eq!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await, Err(JoinLobbyError::LobbyFull));
}
#[async_std::test]
async fn test_new_room() {
let mut cl = ClientLocation::default();
assert!(cl.create_new_room(ClientId(12)).await == Ok(RoomId(0)));
}
#[async_std::test]
async fn test_add_client_to_room() {
let mut cl = ClientLocation::default();
let room = cl.create_new_room(ClientId(12)).await.unwrap();
assert!(cl.add_client_to_room(ClientId(234), room).await == Ok(()));
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
(ClientId(12), LocalClientId(0)),
(ClientId(234), LocalClientId(1)),
]);
}
#[async_std::test]
async fn test_no_new_room_slots() {
let mut cl = ClientLocation::default();
for i in 0..128 {
cl.create_new_room(ClientId(i)).await;
}
assert!(cl.create_new_room(ClientId(234)).await == Err(CreateRoomError::NoOpenSlots));
}
#[async_std::test]
async fn test_joining_full_room() {
let mut cl = ClientLocation::default();
let room = cl.create_new_room(ClientId(0)).await.unwrap();
assert!(cl.add_client_to_room(ClientId(1), room).await == Ok(()));
assert!(cl.add_client_to_room(ClientId(2), room).await == Ok(()));
assert!(cl.add_client_to_room(ClientId(3), room).await == Ok(()));
assert!(cl.add_client_to_room(ClientId(234), room).await == Err(JoinRoomError::RoomFull));
}
#[async_std::test]
async fn test_adding_client_to_room_removes_from_lobby() {
let mut cl = ClientLocation::default();
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
let room = cl.create_new_room(ClientId(51)).await.unwrap();
assert!(cl.add_client_to_room(ClientId(93), room).await == Ok(()));
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
(ClientId(23), LocalClientId(1)),
(ClientId(12), LocalClientId(3)),
]);
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
(ClientId(51), LocalClientId(0)),
(ClientId(93), LocalClientId(1)),
]);
}
#[async_std::test]
async fn test_getting_neighbors() {
let cl = ClientLocation::default();
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await.unwrap();
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await.unwrap();
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await.unwrap();
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
assert!(cl.get_client_neighbors(ClientId(23)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
(ClientId(93), LocalClientId(0)),
(ClientId(51), LocalClientId(2)),
(ClientId(12), LocalClientId(3)),
]);
}
#[async_std::test]
async fn test_failing_to_join_lobby_does_not_remove_from_current_area() {
let cl = ClientLocation::default();
for i in 0..12 {
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
}
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(1)).await.is_ok());
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await.is_err());
assert_eq!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().len(), 12);
assert_eq!(
cl.get_clients_in_lobby(LobbyId(1)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>(),
vec![(ClientId(99), LocalClientId(0))]
);
}
#[async_std::test]
async fn test_get_leader() {
let cl = ClientLocation::default();
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
assert!(cl.get_leader_by_client(ClientId(51)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0))));
}
#[async_std::test]
async fn test_remove_client_from_room() {
let mut cl = ClientLocation::default();
let room = cl.create_new_room(ClientId(51)).await.unwrap();
cl.add_client_to_room(ClientId(93), room).await;
cl.add_client_to_room(ClientId(23), room).await;
cl.remove_client_from_area(ClientId(51)).await;
cl.add_client_to_room(ClientId(12), room).await;
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
(ClientId(12), LocalClientId(0)),
(ClientId(93), LocalClientId(1)),
(ClientId(23), LocalClientId(2)),
]);
}
#[async_std::test]
async fn test_leader_changes_on_leader_leaving() {
let mut cl = ClientLocation::default();
let room = cl.create_new_room(ClientId(51)).await.unwrap();
cl.add_client_to_room(ClientId(93), room).await.unwrap();
cl.add_client_to_room(ClientId(23), room).await.unwrap();
cl.remove_client_from_area(ClientId(51)).await.unwrap();
cl.add_client_to_room(ClientId(12), room).await.unwrap();
cl.remove_client_from_area(ClientId(23)).await.unwrap();
cl.add_client_to_room(ClientId(99), room).await.unwrap();
assert!(cl.get_leader_by_client(ClientId(12)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1))));
}
}

View File

@ -2,9 +2,6 @@
use std::io::Read; use std::io::Read;
use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::collections::{BTreeMap, BTreeSet, HashMap};
use async_std::sync::{Arc, RwLock};
use async_std::channel;
use rand::Rng; use rand::Rng;
use crc::{crc32, Hasher32}; use crc::{crc32, Hasher32};
@ -12,45 +9,39 @@ use libpso::packet::login::*;
use libpso::packet::ship::{MenuDetail, SmallLeftDialog}; use libpso::packet::ship::{MenuDetail, SmallLeftDialog};
use libpso::{PacketParseError, PSOPacket}; use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher; use libpso::crypto::bb::PSOBBCipher;
use crate::entity::item;
use libpso::character::character; use libpso::character::character;
use entity::item;
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use networking::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship}; use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
use stats::leveltable::LEVEL_TABLE; use crate::common::leveltable::CharacterLevelTable;
use libpso::util::{utf8_to_array, utf8_to_utf16_array}; use libpso::{utf8_to_array, utf8_to_utf16_array};
use entity::gateway::{EntityGateway, GatewayError}; use crate::entity::gateway::{EntityGateway, GatewayError};
use entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM}; use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
use entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankIdentifier, EquippedEntity, Meseta}; use crate::entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity, Meseta};
use entity::item::weapon::Weapon; use crate::entity::item::weapon::Weapon;
use entity::item::armor::Armor; use crate::entity::item::armor::Armor;
use entity::item::tech::Technique; use crate::entity::item::tech::Technique;
use entity::item::tool::Tool; use crate::entity::item::tool::Tool;
use entity::item::mag::Mag; use crate::entity::item::mag::Mag;
use entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel}; use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
use crate::login::get_login_status; use crate::login::login::{get_login_status};
use networking::interserver::AuthToken; use crate::common::interserver::AuthToken;
use pktbuilder::ship::SHIP_MENU_ID;
pub const CHARACTER_PORT: u16 = 12001; pub const CHARACTER_PORT: u16 = 12001;
pub const SHIP_MENU_ID: u32 = 1;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[error("")]
pub enum CharacterError { pub enum CharacterError {
#[error("invalid menu selection {0} {1}")]
InvalidMenuSelection(u32, u32), InvalidMenuSelection(u32, u32),
#[error("client not found {0}")]
ClientNotFound(ClientId), ClientNotFound(ClientId),
#[error("could not load settings {0}")] CouldNotLoadSettings,
CouldNotLoadSettings(GatewayError),
#[error("could not load characters")]
CouldNotLoadCharacters, CouldNotLoadCharacters,
#[error("could not load guildcard")]
CouldNotLoadGuildcard, CouldNotLoadGuildcard,
#[error("gateway error {0}")]
GatewayError(#[from] GatewayError), GatewayError(#[from] GatewayError),
} }
@ -150,7 +141,7 @@ fn generate_param_data(path: &str) -> (ParamDataHeader, Vec<u8>) {
size: len as u32, size: len as u32,
checksum: crc.sum32(), checksum: crc.sum32(),
offset: buffer.len() as u32, offset: buffer.len() as u32,
filename: utf8_to_array(param.file_name().unwrap().to_str().unwrap()), filename: utf8_to_array!(param.file_name().unwrap().to_str().unwrap(), 0x40),
}); });
buffer.append(&mut filebuf); buffer.append(&mut filebuf);
@ -177,7 +168,7 @@ impl ClientState {
user: None, user: None,
characters: None, characters: None,
guildcard_data_buffer: None, guildcard_data_buffer: None,
session: Session::default(), session: Session::new(),
} }
} }
} }
@ -188,22 +179,22 @@ struct ConnectedClient {
expires: Option<chrono::DateTime<chrono::Utc>>, expires: Option<chrono::DateTime<chrono::Utc>>,
} }
#[derive(Clone)] pub struct CharacterServerState<EG: EntityGateway> {
pub struct CharacterServerState<EG: EntityGateway + Clone> {
entity_gateway: EG, entity_gateway: EG,
param_header: ParamDataHeader, param_header: ParamDataHeader,
param_data: Arc<Vec<u8>>, param_data: Vec<u8>,
clients: Arc<RwLock<HashMap<ClientId, ClientState>>>, clients: HashMap<ClientId, ClientState>,
ships: Arc<RwLock<BTreeMap<ServerId, Ship>>>, ships: BTreeMap<ServerId, Ship>,
level_table: CharacterLevelTable,
auth_token: AuthToken, auth_token: AuthToken,
connected_clients: Arc<RwLock<BTreeMap<UserAccountId, ConnectedClient>>>, connected_clients: BTreeMap<UserAccountId, ConnectedClient>,
authenticated_ships: Arc<RwLock<BTreeSet<ServerId>>>, authenticated_ships: BTreeSet<ServerId>,
ship_sender: Arc<RwLock<BTreeMap<ServerId, channel::Sender<LoginMessage>>>>, ship_sender: BTreeMap<ServerId, Box<dyn Fn(LoginMessage) + Send>>,
} }
async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user: &UserAccountEntity, preview: &CharacterPreview) -> Result<(), anyhow::Error> { async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAccountEntity, preview: &CharacterPreview) -> Result<(), anyhow::Error> {
let mut character = new_character_from_preview(user, preview); let mut character = new_character_from_preview(user, preview);
match character.char_class { match character.char_class {
CharacterClass::FOmar | CharacterClass::FOmarl| CharacterClass::FOnewm | CharacterClass::FOnewearl => character.techs.set_tech(Technique::Foie, TechLevel(1)), CharacterClass::FOmar | CharacterClass::FOmarl| CharacterClass::FOnewm | CharacterClass::FOnewearl => character.techs.set_tech(Technique::Foie, TechLevel(1)),
@ -212,7 +203,6 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
let character = entity_gateway.create_character(character).await?; let character = entity_gateway.create_character(character).await?;
entity_gateway.set_character_meseta(&character.id, Meseta(300)).await?; entity_gateway.set_character_meseta(&character.id, Meseta(300)).await?;
entity_gateway.set_bank_meseta(&character.id, &BankIdentifier::Character, Meseta(0)).await?;
let new_weapon = match character.char_class { let new_weapon = match character.char_class {
CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber, CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber,
@ -230,6 +220,7 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
special: None, special: None,
attrs: [None; 3], attrs: [None; 3],
tekked: true, tekked: true,
kills: None,
})}).await?; })}).await?;
entity_gateway.add_item_note(&weapon.id, ItemNote::CharacterCreation { entity_gateway.add_item_note(&weapon.id, ItemNote::CharacterCreation {
@ -267,8 +258,6 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
character_id: character.id, character_id: character.id,
}).await?; }).await?;
entity_gateway.change_mag_owner(&mag.id, &character).await?;
let mut monomates = Vec::new(); let mut monomates = Vec::new();
for _ in 0..4usize { for _ in 0..4usize {
let monomate = entity_gateway.create_item( let monomate = entity_gateway.create_item(
@ -306,7 +295,7 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
InventoryItemEntity::Stacked(monomates), InventoryItemEntity::Stacked(monofluids)], InventoryItemEntity::Stacked(monomates), InventoryItemEntity::Stacked(monofluids)],
}; };
entity_gateway.set_character_inventory(&character.id, &inventory).await?; entity_gateway.set_character_inventory(&character.id, &inventory).await?;
entity_gateway.set_character_bank(&character.id, &BankEntity::default(), &BankIdentifier::Character).await?; entity_gateway.set_character_bank(&character.id, &BankEntity::default(), &BankName("".into())).await?;
let equipped = EquippedEntity { let equipped = EquippedEntity {
weapon: Some(weapon.id), weapon: Some(weapon.id),
armor: Some(armor.id), armor: Some(armor.id),
@ -318,46 +307,49 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
} }
impl<EG: EntityGateway + Clone> CharacterServerState<EG> { impl<EG: EntityGateway> CharacterServerState<EG> {
pub fn new(entity_gateway: EG, auth_token: AuthToken) -> CharacterServerState<EG> { pub fn new(entity_gateway: EG, auth_token: AuthToken) -> CharacterServerState<EG> {
let (param_header, param_data) = generate_param_data("data/param/"); let (param_header, param_data) = generate_param_data("data/param/");
CharacterServerState { CharacterServerState {
entity_gateway, entity_gateway,
param_header, param_header,
param_data: Arc::new(param_data), param_data,
clients: Default::default(), clients: HashMap::new(),
ships: Default::default(), ships: BTreeMap::new(),
//level_table: CharacterLevelTable::default(), level_table: CharacterLevelTable::default(),
auth_token, auth_token,
authenticated_ships: Default::default(), authenticated_ships: BTreeSet::new(),
ship_sender: Default::default(), ship_sender: BTreeMap::new(),
connected_clients: Default::default(), connected_clients: BTreeMap::new(),
} }
} }
pub fn set_sender(&mut self, server_id: ServerId, sender: Box<dyn Fn(LoginMessage) + Send>) {
self.ship_sender.insert(server_id, sender);
}
async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
match get_login_status(&mut self.entity_gateway, pkt).await { match get_login_status(&mut self.entity_gateway, pkt).await {
Ok(user) => { Ok(user) => {
if let Some(connected_client) = self.connected_clients.read().await.get(&user.id) { if let Some(connected_client) = self.connected_clients.get(&user.id) {
if let Some(expires) = connected_client.expires { if let Some(expires) = connected_client.expires {
if expires > chrono::Utc::now() { if expires > chrono::Utc::now() {
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]); return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
} }
} }
else { else {
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]); return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
} }
} }
let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::default()); let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new());
response.guildcard = user.guildcard; response.guildcard = user.guildcard;
response.team_id = user.team_id.map_or(0, |ti| ti); response.team_id = user.team_id.map_or(0, |ti| ti) as u32;
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
self.connected_clients.write().await.insert(user.id, ConnectedClient { self.connected_clients.insert(user.id, ConnectedClient {
ship_id: None, ship_id: None,
expires: None, //Some(chrono::Utc::now() + chrono::Duration::minutes(1)), expires: None, //Some(chrono::Utc::now() + chrono::Duration::minutes(1)),
}); });
@ -367,34 +359,33 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
Ok(vec![SendCharacterPacket::LoginResponse(response)]) Ok(vec![SendCharacterPacket::LoginResponse(response)])
}, },
Err(err) => { Err(err) => {
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::default()))]) Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::new()))])
} }
} }
} }
async fn send_ship_list(&mut self, _id: ClientId, _pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { fn send_ship_list(&mut self, _id: ClientId, _pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
Ok(vec![SendCharacterPacket::Timestamp(Timestamp::new(chrono::Utc::now())), Ok(vec![SendCharacterPacket::Timestamp(Timestamp::new(chrono::Utc::now())),
SendCharacterPacket::ShipList(ShipList::new(self.ships.read().await.iter().map(|(i, s)| { SendCharacterPacket::ShipList(ShipList::new(self.ships.iter().map(|(i, s)| {
ShipListEntry { ShipListEntry {
menu: SHIP_MENU_ID, menu: SHIP_MENU_ID,
item: i.0 as u32, item: i.0 as u32,
flags: 0, flags: 0,
name: utf8_to_utf16_array(&s.name) name: utf8_to_utf16_array!(s.name, 0x11)
} }
}).collect())) }).collect()))
]) ])
} }
async fn get_settings(&mut self, id: ClientId) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { async fn get_settings(&mut self, id: ClientId) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let user = client.user.as_ref().unwrap(); let user = client.user.as_ref().unwrap();
let settings = match self.entity_gateway.get_user_settings_by_user(user).await { let settings = match self.entity_gateway.get_user_settings_by_user(user).await {
Ok(settings) => settings, Ok(settings) => settings,
Err(_) => { Err(_) => {
let user_settings = NewUserSettingsEntity::new(user.id); let user_settings = NewUserSettingsEntity::new(user.id);
self.entity_gateway.create_user_settings(user_settings).await.map_err(CharacterError::CouldNotLoadSettings)? self.entity_gateway.create_user_settings(user_settings).await.map_err(|_| CharacterError::CouldNotLoadSettings)?
} }
}; };
@ -406,8 +397,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
} }
async fn char_select(&mut self, id: ClientId, select: &CharSelect) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { async fn char_select(&mut self, id: ClientId, select: &CharSelect) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
if client.characters.is_none() { if client.characters.is_none() {
client.characters = Some(self.entity_gateway.get_characters_by_user(client.user.as_ref().unwrap()).await.map_err(|_| CharacterError::CouldNotLoadCharacters)?); client.characters = Some(self.entity_gateway.get_characters_by_user(client.user.as_ref().unwrap()).await.map_err(|_| CharacterError::CouldNotLoadCharacters)?);
} }
@ -415,7 +405,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
if select.reason == 0 { if select.reason == 0 {
let chars = client.characters.as_ref().unwrap(); let chars = client.characters.as_ref().unwrap();
Ok(if let Some(char) = &chars[select.slot as usize] { Ok(if let Some(char) = &chars[select.slot as usize] {
let (level, _stats) = LEVEL_TABLE.get_stats_from_exp(char.char_class, char.exp); let (level, _stats) = self.level_table.get_stats_from_exp(char.char_class, char.exp);
vec![SendCharacterPacket::CharacterPreview(CharacterPreview { vec![SendCharacterPacket::CharacterPreview(CharacterPreview {
slot: select.slot, slot: select.slot,
character: SelectScreenCharacterBuilder::new() character: SelectScreenCharacterBuilder::new()
@ -453,8 +443,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
} }
async fn guildcard_data_header(&mut self, id: ClientId) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { async fn guildcard_data_header(&mut self, id: ClientId) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let guildcard_data = self.entity_gateway.get_guild_card_data_by_user(client.user.as_ref().unwrap()).await.map_err(|_| CharacterError::CouldNotLoadGuildcard)?; let guildcard_data = self.entity_gateway.get_guild_card_data_by_user(client.user.as_ref().unwrap()).await.map_err(|_| CharacterError::CouldNotLoadGuildcard)?;
let bytes = guildcard_data.guildcard.as_bytes(); let bytes = guildcard_data.guildcard.as_bytes();
@ -465,16 +454,15 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
Ok(vec![SendCharacterPacket::GuildcardDataHeader(GuildcardDataHeader::new(bytes.len(), crc.sum32()))]) Ok(vec![SendCharacterPacket::GuildcardDataHeader(GuildcardDataHeader::new(bytes.len(), crc.sum32()))])
} }
async fn guildcard_data_chunk(&mut self, id: ClientId, chunk: u32, again: u32) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { fn guildcard_data_chunk(&mut self, id: ClientId, chunk: u32, again: u32) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
Ok(if again != 0 { Ok(if again != 0 {
let start = chunk as usize * GUILD_CARD_CHUNK_SIZE; let start = chunk as usize * GUILD_CARD_CHUNK_SIZE;
let len = std::cmp::min(GUILD_CARD_CHUNK_SIZE, client.guildcard_data_buffer.as_ref().unwrap().len() - start); let len = std::cmp::min(GUILD_CARD_CHUNK_SIZE, client.guildcard_data_buffer.as_ref().unwrap().len() as usize - start);
let end = start + len; let end = start + len;
let mut buf = [0u8; GUILD_CARD_CHUNK_SIZE]; let mut buf = [0u8; GUILD_CARD_CHUNK_SIZE as usize];
buf[..len].copy_from_slice(&client.guildcard_data_buffer.as_ref().unwrap()[start..end]); buf[..len as usize].copy_from_slice(&client.guildcard_data_buffer.as_ref().unwrap()[start..end]);
vec![SendCharacterPacket::GuildcardDataChunk(Box::new(GuildcardDataChunk::new(chunk, buf, len)))] vec![SendCharacterPacket::GuildcardDataChunk(Box::new(GuildcardDataChunk::new(chunk, buf, len)))]
} else { } else {
@ -483,17 +471,15 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
} }
async fn set_flag(&mut self, id: ClientId, setflag: &SetFlag) -> Result<std::option::IntoIter<SendCharacterPacket>, anyhow::Error> { async fn set_flag(&mut self, id: ClientId, setflag: &SetFlag) -> Result<std::option::IntoIter<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?; let mut user = client.user.as_mut().unwrap();
let user = client.user.as_mut().unwrap();
user.flags = setflag.flags; user.flags = setflag.flags;
self.entity_gateway.save_user(user).await.unwrap(); self.entity_gateway.save_user(user).await.unwrap();
Ok(None.into_iter()) Ok(None.into_iter())
} }
async fn param_data_chunk_request(&mut self, id: ClientId, _request: &ParamDataChunkRequest) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { fn param_data_chunk_request(&mut self, id: ClientId, _request: &ParamDataChunkRequest) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let chunk = client.param_index; let chunk = client.param_index;
client.param_index += 1; client.param_index += 1;
@ -514,9 +500,8 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
// TODO: move USERFLAGS over to SessionAction // TODO: move USERFLAGS over to SessionAction
async fn character_preview(&mut self, id: ClientId, preview: &CharacterPreview) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { async fn character_preview(&mut self, id: ClientId, preview: &CharacterPreview) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?; let mut user = client.user.as_mut().unwrap();
let user = client.user.as_mut().unwrap();
if user.flags == USERFLAG_NEWCHAR { if user.flags == USERFLAG_NEWCHAR {
new_character(&mut self.entity_gateway, user, preview).await? new_character(&mut self.entity_gateway, user, preview).await?
} }
@ -538,30 +523,26 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
]) ])
} }
async fn select_ship(&mut self, id: ClientId, menuselect: &MenuSelect) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { fn select_ship(&mut self, id: ClientId, menuselect: &MenuSelect) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
if menuselect.menu != SHIP_MENU_ID { if menuselect.menu != SHIP_MENU_ID {
return Err(CharacterError::InvalidMenuSelection(menuselect.menu, menuselect.item).into()); return Err(CharacterError::InvalidMenuSelection(menuselect.menu, menuselect.item).into());
} }
if let Some(client) = self.clients.read().await.get(&id) { if let Some(client) = self.clients.get(&id) {
if let Some(user) = &client.user { if let Some(user) = &client.user {
if let Some(cc) = self.connected_clients.write().await.get_mut(&user.id) { if let Some(cc) = self.connected_clients.get_mut(&user.id) {
cc.ship_id = Some(ServerId(menuselect.item as usize)); cc.ship_id = Some(ServerId(menuselect.item as usize));
} }
} }
} }
let ship = self.ships.read().await; let ship = self.ships.get(&ServerId(menuselect.item as usize))
let ship = ship.get(&ServerId(menuselect.item as usize)) .ok_or(CharacterError::InvalidMenuSelection(menuselect.menu, menuselect.item))?;
.ok_or_else(|| CharacterError::InvalidMenuSelection(menuselect.menu, menuselect.item))?;
Ok(vec![SendCharacterPacket::RedirectClient(RedirectClient::new(u32::from_le_bytes(ship.ip.octets()), ship.port))]) Ok(vec![SendCharacterPacket::RedirectClient(RedirectClient::new(u32::from_le_bytes(ship.ip.octets()), ship.port))])
} }
async fn ship_detail(&mut self, menudetail: &MenuDetail) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { fn ship_detail(&mut self, menudetail: &MenuDetail) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let players = self.connected_clients let players = self.connected_clients.iter()
.read()
.await
.iter()
.filter(|(_, client)| { .filter(|(_, client)| {
client.ship_id == Some(ServerId(menudetail.item as usize)) client.ship_id == Some(ServerId(menudetail.item as usize))
}) })
@ -572,14 +553,13 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<EG: EntityGateway + Clone> ServerState for CharacterServerState<EG> { impl<EG: EntityGateway> ServerState for CharacterServerState<EG> {
type SendPacket = SendCharacterPacket; type SendPacket = SendCharacterPacket;
type RecvPacket = RecvCharacterPacket; type RecvPacket = RecvCharacterPacket;
type Cipher = PSOBBCipher;
type PacketError = anyhow::Error; type PacketError = anyhow::Error;
async fn on_connect(&mut self, id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, anyhow::Error> { async fn on_connect(&mut self, id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket>>, anyhow::Error> {
self.clients.write().await.insert(id, ClientState::new()); self.clients.insert(id, ClientState::new());
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -589,66 +569,65 @@ impl<EG: EntityGateway + Clone> ServerState for CharacterServerState<EG> {
rng.fill(&mut client_key[..]); rng.fill(&mut client_key[..]);
Ok(vec![OnConnect::Packet(SendCharacterPacket::LoginWelcome(LoginWelcome::new(server_key, client_key))), Ok(vec![OnConnect::Packet(SendCharacterPacket::LoginWelcome(LoginWelcome::new(server_key, client_key))),
OnConnect::Cipher(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key), OnConnect::Cipher((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key)) Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
//OnConnect::Cipher((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
// Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
]) ])
} }
async fn handle(&mut self, id: ClientId, pkt: RecvCharacterPacket) -> Result<Vec<(ClientId, SendCharacterPacket)>, anyhow::Error> { async fn handle(&mut self, id: ClientId, pkt: &RecvCharacterPacket)
-> Result<Box<dyn Iterator<Item = (ClientId, SendCharacterPacket)> + Send>, anyhow::Error> {
Ok(match pkt { Ok(match pkt {
RecvCharacterPacket::Login(login) => { RecvCharacterPacket::Login(login) => {
if login.session.action == SessionAction::SelectCharacter { if login.session.action == SessionAction::SelectCharacter {
self.send_ship_list(id, &login).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.send_ship_list(id, login)?.into_iter().map(move |pkt| (id, pkt)))
} }
else { else {
self.validate_login(id, &login).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.validate_login(id, login).await?.into_iter().map(move |pkt| (id, pkt)))
} }
}, },
RecvCharacterPacket::RequestSettings(_req) => { RecvCharacterPacket::RequestSettings(_req) => {
self.get_settings(id).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.get_settings(id).await?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::CharSelect(sel) => { RecvCharacterPacket::CharSelect(sel) => {
self.char_select(id, &sel).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.char_select(id, sel).await?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::Checksum(_checksum) => { RecvCharacterPacket::Checksum(_checksum) => {
self.validate_checksum().into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.validate_checksum().into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::GuildcardDataRequest(_request) => { RecvCharacterPacket::GuildcardDataRequest(_request) => {
self.guildcard_data_header(id).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.guildcard_data_header(id).await?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::GuildcardDataChunkRequest(request) => { RecvCharacterPacket::GuildcardDataChunkRequest(request) => {
self.guildcard_data_chunk(id, request.chunk, request.again).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.guildcard_data_chunk(id, request.chunk, request.again)?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::ParamDataRequest(_request) => { RecvCharacterPacket::ParamDataRequest(_request) => {
vec![SendCharacterPacket::ParamDataHeader(self.param_header.clone())].into_iter().map(move |pkt| (id, pkt)).collect() Box::new(vec![SendCharacterPacket::ParamDataHeader(self.param_header.clone())].into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::SetFlag(flag) => { RecvCharacterPacket::SetFlag(flag) => {
self.set_flag(id, &flag).await?.map(move |pkt| (id, pkt)).collect() Box::new(self.set_flag(id, flag).await?.map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::ParamDataChunkRequest(request) => { RecvCharacterPacket::ParamDataChunkRequest(request) => {
self.param_data_chunk_request(id, &request).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.param_data_chunk_request(id, request)?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::CharacterPreview(preview) => { RecvCharacterPacket::CharacterPreview(preview) => {
self.character_preview(id, &preview).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.character_preview(id, preview).await?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::MenuSelect(menuselect) => { RecvCharacterPacket::MenuSelect(menuselect) => {
self.select_ship(id, &menuselect).await?.into_iter().map(move |pkt| (id, pkt)).collect() Box::new(self.select_ship(id, menuselect)?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvCharacterPacket::MenuDetail(menudetail) => { RecvCharacterPacket::MenuDetail(menudetail) => {
match menudetail.menu { match menudetail.menu {
SHIP_MENU_ID => self.ship_detail(&menudetail).await?.into_iter().map(move |pkt| (id, pkt)).collect(), SHIP_MENU_ID => Box::new(self.ship_detail(menudetail)?.into_iter().map(move |pkt| (id, pkt))),
_ => Vec::new() _ => Box::new(Vec::new().into_iter())
} }
} }
}) })
} }
async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, SendCharacterPacket)>, anyhow::Error> { async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, SendCharacterPacket)>, anyhow::Error> {
if let Some(client) = self.clients.write().await.remove(&id) { if let Some(client) = self.clients.remove(&id) {
if let Some(user) = client.user { if let Some(user) = client.user {
self.connected_clients.write().await.remove(&user.id); self.connected_clients.remove(&user.id);
} }
} }
Ok(Vec::new()) Ok(Vec::new())
@ -656,7 +635,7 @@ impl<EG: EntityGateway + Clone> ServerState for CharacterServerState<EG> {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> { impl<EG: EntityGateway> InterserverActor for CharacterServerState<EG> {
type SendMessage = LoginMessage; type SendMessage = LoginMessage;
type RecvMessage = ShipMessage; type RecvMessage = ShipMessage;
type Error = (); type Error = ();
@ -665,26 +644,21 @@ impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
Vec::new() Vec::new()
} }
async fn on_action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error> { async fn action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error> {
dbg!(&id, &msg);
match msg { match msg {
ShipMessage::Authenticate(auth_token) => { ShipMessage::Authenticate(auth_token) => {
if self.auth_token == auth_token { if self.auth_token == auth_token {
self.authenticated_ships.write().await.insert(id); self.authenticated_ships.insert(id);
} }
Ok(Vec::new()) Ok(Vec::new())
}, },
ShipMessage::NewShip(new_ship) => { ShipMessage::NewShip(new_ship) => {
dbg!("adding ship", &id, &new_ship); if self.authenticated_ships.contains(&id) {
if self.authenticated_ships.read().await.contains(&id) { self.ships.insert(id, new_ship);
self.ships.write().await.insert(id, new_ship);
} }
dbg!("ship list", &self.authenticated_ships);
let ships = self.ships.read().await.iter().map(|(_, s)| s).cloned().collect::<Vec<_>>(); let ships = self.ships.iter().map(|(_, s)| s).cloned().collect::<Vec<_>>();
Ok(self.ships Ok(self.ships
.read()
.await
.iter() .iter()
.map(|(id, _)| { .map(|(id, _)| {
(*id, LoginMessage::ShipList{ ships: ships.clone() }) (*id, LoginMessage::ShipList{ ships: ships.clone() })
@ -692,8 +666,8 @@ impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
.collect()) .collect())
}, },
ShipMessage::AddUser(new_user) => { ShipMessage::AddUser(new_user) => {
if self.authenticated_ships.read().await.contains(&id) { if self.authenticated_ships.contains(&id) {
self.connected_clients.write().await.insert(new_user, ConnectedClient { self.connected_clients.insert(new_user, ConnectedClient {
ship_id: Some(id), ship_id: Some(id),
expires: None, expires: None,
}); });
@ -701,18 +675,15 @@ impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
Ok(Vec::new()) Ok(Vec::new())
}, },
ShipMessage::RemoveUser(new_user) => { ShipMessage::RemoveUser(new_user) => {
if self.authenticated_ships.read().await.contains(&id) { if self.authenticated_ships.contains(&id) {
self.connected_clients.write().await.remove(&new_user); self.connected_clients.remove(&new_user);
} }
Ok(Vec::new()) Ok(Vec::new())
}, },
ShipMessage::RequestShipList => { ShipMessage::RequestShipList => {
dbg!("request ship list", &self.authenticated_ships); if self.authenticated_ships.contains(&id) {
if self.authenticated_ships.read().await.contains(&id) {
Ok(vec![(id, LoginMessage::ShipList { Ok(vec![(id, LoginMessage::ShipList {
ships: self.ships ships: self.ships
.read()
.await
.iter() .iter()
.map(|(_, ship)| { .map(|(_, ship)| {
ship ship
@ -732,25 +703,20 @@ impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
} }
async fn on_disconnect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> { async fn on_disconnect(&mut self, id: ServerId) -> Vec<(ServerId, Self::SendMessage)> {
self.ships.write().await.remove(&id); self.ships.remove(&id);
self.ship_sender.write().await.remove(&id); self.ship_sender.remove(&id);
self.connected_clients self.connected_clients = self.connected_clients.clone().into_iter()
.write() .filter(|(_, client)| {
.await
.retain(|_, client| {
client.ship_id != Some(id) client.ship_id != Some(id)
}); })
.collect();
Vec::new() Vec::new()
} }
async fn set_sender(&mut self, server_id: ServerId, sender: channel::Sender<LoginMessage>) {
self.ship_sender.write().await.insert(server_id, sender);
}
} }
fn new_character_from_preview(user: &UserAccountEntity, preview: &CharacterPreview) -> NewCharacterEntity { fn new_character_from_preview(user: &UserAccountEntity, preview: &CharacterPreview) -> NewCharacterEntity {
let mut character = NewCharacterEntity::new(user.id); 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
character.slot = preview.slot; character.slot = preview.slot;
character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into(); character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into();
character.section_id = preview.character.section_id.into(); character.section_id = preview.character.section_id.into();
@ -824,8 +790,8 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
hair_b: character.appearance.hair_b, hair_b: character.appearance.hair_b,
prop_x: character.appearance.prop_x, prop_x: character.appearance.prop_x,
prop_y: character.appearance.prop_y, prop_y: character.appearance.prop_y,
name: utf8_to_utf16_array(&character.name), name: utf8_to_utf16_array!(character.name, 16),
play_time: character.playtime, //play_time: character.play_time,
..character::SelectScreenCharacter::default() ..character::SelectScreenCharacter::default()
} }
} }
@ -835,21 +801,9 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use entity::account::*; use crate::entity::account::*;
use libpso::character::{settings, character}; use libpso::character::{settings, character};
use entity::gateway::{InMemoryGateway, EntityGatewayTransaction, GatewayError}; use crate::entity::gateway::{InMemoryGateway, GatewayError};
#[derive(Clone)]
struct CharTestDb;
impl EntityGateway for CharTestDb {
type Transaction<'t> = CharTestDb where Self: 't;
}
impl EntityGatewayTransaction for CharTestDb {
type ParentGateway = CharTestDb;
}
#[async_std::test] #[async_std::test]
async fn test_option_send() { async fn test_option_send() {
@ -859,7 +813,6 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'a> = CharTestDb where Self: 'a;
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> { async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
Ok(UserSettingsEntity { Ok(UserSettingsEntity {
id: UserSettingsId(0), id: UserSettingsId(0),
@ -886,9 +839,11 @@ mod test {
at_character: false, at_character: false,
at_ship: false, at_ship: false,
}); });
server.clients.write().await.insert(ClientId(5), clientstate); server.clients.insert(ClientId(5), clientstate);
let send = server.handle(ClientId(5), RecvCharacterPacket::RequestSettings(RequestSettings{})).await.unwrap(); let send = server.handle(ClientId(5), &RecvCharacterPacket::RequestSettings(RequestSettings{})).await
.unwrap()
.collect::<Vec<_>>();
assert!(send.len() == 1); assert!(send.len() == 1);
assert!(send[0].0 == ClientId(5)); assert!(send[0].0 == ClientId(5));
@ -901,13 +856,11 @@ mod test {
async fn test_user_checksum() { async fn test_user_checksum() {
#[derive(Clone)] #[derive(Clone)]
struct TestData; struct TestData;
impl EntityGateway for TestData { impl EntityGateway for TestData {}
type Transaction<'a> = CharTestDb where Self: 'a;
}
let mut server = CharacterServerState::new(TestData {}, AuthToken("".into())); let mut server = CharacterServerState::new(TestData {}, AuthToken("".into()));
let send = server.handle(ClientId(1), RecvCharacterPacket::Checksum(Checksum {checksum: 1234, let send = server.handle(ClientId(1), &RecvCharacterPacket::Checksum(Checksum {checksum: 1234,
padding: 0, padding: 0,
})).await.unwrap(); })).await.unwrap().collect::<Vec<_>>();
assert!(send.len() == 1); assert!(send.len() == 1);
let bytes = send[0].1.as_bytes(); let bytes = send[0].1.as_bytes();
@ -935,10 +888,10 @@ mod test {
}); });
let mut server = CharacterServerState::new(test_data.clone(), AuthToken("".into())); let mut server = CharacterServerState::new(test_data.clone(), AuthToken("".into()));
server.clients.write().await.insert(ClientId(1), fake_user.clone()); server.clients.insert(ClientId(1), fake_user.clone());
let mut send = server.handle(ClientId(1), RecvCharacterPacket::SetFlag(SetFlag {flags: 1})).await.unwrap(); let mut send = server.handle(ClientId(1), &RecvCharacterPacket::SetFlag(SetFlag {flags: 1})).await.unwrap().collect::<Vec<_>>();
assert!(test_data.get_user_by_id(UserAccountId(3)).await.unwrap().flags == 1); assert!(test_data.get_user_by_id(UserAccountId(3)).await.unwrap().flags == 1);
send = server.handle(ClientId(1), RecvCharacterPacket::CharacterPreview(CharacterPreview {slot: 1, character: character::SelectScreenCharacter { send = server.handle(ClientId(1), &RecvCharacterPacket::CharacterPreview(CharacterPreview {slot: 1, character: character::SelectScreenCharacter {
exp: 0, exp: 0,
level: 0, level: 0,
guildcard: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], guildcard: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
@ -964,7 +917,7 @@ mod test {
prop_y: 0.0, prop_y: 0.0,
name: [9, 69, 116, 101, 115, 116, 32, 110, 97, 109, 101, 0, 0, 0, 0, 0], // "\tEtest name" name: [9, 69, 116, 101, 115, 116, 32, 110, 97, 109, 101, 0, 0, 0, 0, 0], // "\tEtest name"
play_time: 0, play_time: 0,
} })).await.unwrap(); } })).await.unwrap().collect::<Vec<_>>();
assert!(send.len() == 2); assert!(send.len() == 2);
let chars = test_data.get_characters_by_user(&fake_user.user.unwrap()).await.unwrap(); let chars = test_data.get_characters_by_user(&fake_user.user.unwrap()).await.unwrap();

View File

@ -11,18 +11,18 @@ use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher; use libpso::crypto::bb::PSOBBCipher;
use libpso::util::array_to_utf8; use libpso::util::array_to_utf8;
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
use entity::account::{UserAccountEntity}; use crate::entity::account::{UserAccountEntity};
pub const LOGIN_PORT: u16 = 12000; pub const LOGIN_PORT: u16 = 12000;
pub const COMMUNICATION_PORT: u16 = 12123; pub const COMMUNICATION_PORT: u16 = 12123;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[error("")]
pub enum LoginError { pub enum LoginError {
#[error("dberror")]
DbError DbError
} }
@ -83,16 +83,23 @@ pub async fn get_login_status(entity_gateway: &mut impl EntityGateway, pkt: &Log
pub fn check_if_already_online(user: UserAccountEntity) -> Result<UserAccountEntity, AccountStatus> { pub fn check_if_already_online(user: UserAccountEntity) -> Result<UserAccountEntity, AccountStatus> {
Ok(user) Ok(user)
/*
if user.is_currently_online() {
Err(AccountStatus::PayUp)
}
else {
Ok(user)
}
*/
} }
#[derive(Clone)] pub struct LoginServerState<EG: EntityGateway> {
pub struct LoginServerState<EG: EntityGateway + Clone> {
character_server_ip: net::Ipv4Addr, character_server_ip: net::Ipv4Addr,
entity_gateway: EG, entity_gateway: EG,
clients: HashMap<ClientId, String>, // TODO: this should be arc/mutex'd? clients: HashMap<ClientId, String>,
} }
impl<EG: EntityGateway + Clone> LoginServerState<EG> { impl<EG: EntityGateway> LoginServerState<EG> {
pub fn new(entity_gateway: EG, character_server_ip: net::Ipv4Addr) -> LoginServerState<EG> { pub fn new(entity_gateway: EG, character_server_ip: net::Ipv4Addr) -> LoginServerState<EG> {
LoginServerState { LoginServerState {
entity_gateway, entity_gateway,
@ -111,7 +118,7 @@ impl<EG: EntityGateway + Clone> LoginServerState<EG> {
let response = SendLoginPacket::LoginResponse(LoginResponse::by_status(AccountStatus::Ok, pkt.session)); let response = SendLoginPacket::LoginResponse(LoginResponse::by_status(AccountStatus::Ok, pkt.session));
let ip = u32::from_ne_bytes(self.character_server_ip.octets()); let ip = u32::from_ne_bytes(self.character_server_ip.octets());
Ok(vec![response, Ok(vec![response,
SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::character::CHARACTER_PORT))]) SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::login::character::CHARACTER_PORT))])
}, },
Err(err) => { Err(err) => {
Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))]) Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))])
@ -121,14 +128,13 @@ impl<EG: EntityGateway + Clone> LoginServerState<EG> {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<EG: EntityGateway + Clone> ServerState for LoginServerState<EG> { impl<EG: EntityGateway> ServerState for LoginServerState<EG> {
type SendPacket = SendLoginPacket; type SendPacket = SendLoginPacket;
type RecvPacket = RecvLoginPacket; type RecvPacket = RecvLoginPacket;
type Cipher = PSOBBCipher;
//type PacketError = LoginError; //type PacketError = LoginError;
type PacketError = anyhow::Error; type PacketError = anyhow::Error;
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, anyhow::Error> { async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket>>, anyhow::Error> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let mut server_key = [0u8; 48]; let mut server_key = [0u8; 48];
@ -137,20 +143,20 @@ impl<EG: EntityGateway + Clone> ServerState for LoginServerState<EG> {
rng.fill(&mut client_key[..]); rng.fill(&mut client_key[..]);
Ok(vec![OnConnect::Packet(SendLoginPacket::LoginWelcome(LoginWelcome::new(server_key, client_key))), Ok(vec![OnConnect::Packet(SendLoginPacket::LoginWelcome(LoginWelcome::new(server_key, client_key))),
OnConnect::Cipher(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key), OnConnect::Cipher((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key)) Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
]) ])
} }
async fn handle(&mut self, id: ClientId, pkt: Self::RecvPacket) -> Result<Vec<(ClientId, Self::SendPacket)>, anyhow::Error> { async fn handle(&mut self, id: ClientId, pkt: &Self::RecvPacket)
-> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)> + Send>, anyhow::Error> {
Ok(match pkt { Ok(match pkt {
RecvLoginPacket::Login(login) => { RecvLoginPacket::Login(login) => {
self.validate_login(id, &login).await? Box::new(self.validate_login(id, login).await?
.into_iter() .into_iter()
.map(move |pkt| { .map(move |pkt| {
(id, pkt) (id, pkt)
}) }))
.collect()
} }
}) })
} }
@ -170,8 +176,8 @@ impl<EG: EntityGateway + Clone> ServerState for LoginServerState<EG> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use entity::account::{UserAccountId}; use crate::entity::account::{UserAccountId};
use entity::gateway::{EntityGatewayTransaction, GatewayError}; use crate::entity::gateway::GatewayError;
const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login { const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login {
tag: 65536, tag: 65536,
@ -196,17 +202,6 @@ mod test {
character_slot: 0, character_slot: 0,
} }
}); });
#[derive(Clone)]
struct LoginTestDb;
impl EntityGateway for LoginTestDb {
type Transaction<'t> = LoginTestDb where Self: 't;
}
impl EntityGatewayTransaction for LoginTestDb {
type ParentGateway = LoginTestDb;
}
#[async_std::test] #[async_std::test]
async fn test_correct_login() { async fn test_correct_login() {
@ -216,7 +211,6 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> { async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser"); assert!(name == "testuser");
Ok(UserAccountEntity { Ok(UserAccountEntity {
@ -243,7 +237,7 @@ mod test {
let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap()); let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap());
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap(); let send = server.handle(ClientId(1), &LOGIN_PACKET).await.unwrap().collect::<Vec<_>>();
assert!(send == vec![ assert!(send == vec![
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse { (ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
status: AccountStatus::Ok, status: AccountStatus::Ok,
@ -275,14 +269,13 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, _name: String) -> Result<UserAccountEntity, GatewayError> { async fn get_user_by_name(&mut self, _name: String) -> Result<UserAccountEntity, GatewayError> {
Err(GatewayError::Error) Err(GatewayError::Error)
} }
} }
let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap()); let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap());
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap(); let send = server.handle(ClientId(1), &LOGIN_PACKET).await.unwrap().collect::<Vec<_>>();
assert!(send == vec![ assert!(send == vec![
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse { (ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
@ -310,7 +303,6 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> { async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser"); assert!(name == "testuser");
Ok(UserAccountEntity { Ok(UserAccountEntity {
@ -332,7 +324,7 @@ mod test {
} }
let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap()); let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap());
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap(); let send = server.handle(ClientId(1), &LOGIN_PACKET).await.unwrap().collect::<Vec<_>>();
assert!(send == vec![ assert!(send == vec![
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse { (ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
@ -360,7 +352,6 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> { async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser"); assert!(name == "testuser");
Ok(UserAccountEntity { Ok(UserAccountEntity {
@ -382,7 +373,7 @@ mod test {
} }
let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap()); let mut server = LoginServerState::new(TestData {}, "127.0.0.1".parse().unwrap());
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap(); let send = server.handle(ClientId(1), &LOGIN_PACKET).await.unwrap().collect::<Vec<_>>();
assert!(send == vec![ assert!(send == vec![
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse { (ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {

3
src/login/mod.rs Normal file
View File

@ -0,0 +1,3 @@
#[allow(clippy::module_inception)]
pub mod login;
pub mod character;

92
src/login/models.rs Normal file
View File

@ -0,0 +1,92 @@
use std::time::SystemTime;
use std::io::Write;
//use diesel::sql_types::Timestamp;
use diesel::{Insertable, Queryable, Identifiable, Associations, AsExpression, FromSqlRow};
//use bcrypt::{DEFAULT_COST, hash};
use diesel::pg::Pg;
use diesel::sql_types;
use diesel::deserialize::{self, FromSql};
use diesel::serialize::{self, ToSql, Output, IsNull};
use diesel::backend::Backend;
use libpso::character::settings;
use elseware::schema::*;
//const ELSEWHERE_COST: u32 = bcrypt::DEFAULT_COST;
const ELSEWHERE_COST: u32 = 5;
#[derive(Debug, AsExpression, FromSqlRow)]
#[sql_type="sql_types::Binary"]
pub struct EUserSettings(pub settings::UserSettings);
impl std::ops::Deref for EUserSettings {
type Target = settings::UserSettings;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Queryable, Identifiable, Debug)]
pub struct UserAccount {
pub id: i32,
pub username: String,
pub password: String,
pub guildcard: Option<i32>,
pub team_id: Option<i32>,
pub banned: bool,
pub muted_until: SystemTime,
pub created_at: SystemTime,
}
#[derive(Insertable)]
#[table_name="user_accounts"]
pub struct NewUser {
username: String,
password: String,
}
impl NewUser {
pub fn new(username: String, password: String) -> NewUser {
let crypt_password = bcrypt::hash(password, ELSEWHERE_COST).expect("could not hash password?");
NewUser {
username: username,
password: crypt_password,
}
}
}
#[derive(Queryable, Identifiable, Associations)]
#[belongs_to(UserAccount, foreign_key="user_id")]
#[table_name="user_settings"]
pub struct UserSettings {
pub id: i32,
pub user_id: i32,
//settings: Vec<u8>,
pub settings: EUserSettings,
}
#[derive(Insertable, Debug)]
#[table_name="user_settings"]
pub struct NewUserSettings {
pub user_id: i32,
pub settings: EUserSettings,
}
impl ToSql<sql_types::Binary, Pg> for EUserSettings {
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
out.write_all(&self.0.as_bytes()[..])
.map(|_| IsNull::No)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
}
}
impl FromSql<sql_types::Binary, Pg> for EUserSettings {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
let bytes_vec: Vec<u8> = <Vec<u8> as FromSql<sql_types::Binary, Pg>>::from_sql(bytes)?;
let mut static_bytes = [0u8; 0x1160];
static_bytes[..0x1160].clone_from_slice(&bytes_vec);
Ok(EUserSettings(settings::UserSettings::from_bytes(static_bytes)))
}
}

View File

@ -1,21 +0,0 @@
[package]
name = "login_server"
version = "0.1.0"
edition = "2021"
[dependencies]
entity = { workspace = true }
networking = { workspace = true }
pktbuilder = { workspace = true }
stats = { workspace = true }
libpso = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
anyhow = { workspace = true }
bcrypt = { workspace = true }
crc = { workspace = true }
thiserror = { workspace = true }
chrono = { workspace = true }
rand= { workspace = true }

View File

@ -1,2 +0,0 @@
pub mod login;
pub mod character;

View File

@ -1,14 +0,0 @@
[package]
name = "maps"
version = "0.1.0"
edition = "2021"
[dependencies]
byteorder = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
toml = { workspace = true }
enum-utils = { workspace = true }
derive_more = { workspace = true }

View File

@ -1,59 +0,0 @@
pub mod area;
pub mod enemy;
pub mod object;
pub mod variant;
pub mod maps;
pub mod monster;
pub mod room;
#[derive(Clone, Copy)]
pub enum Holiday {
None,
Christmas,
Valentines,
Easter,
Halloween,
Sonic,
NewYear,
Summer,
White,
Wedding,
Fall,
Spring,
Summer2,
Spring2,
}
impl From<Holiday> for u32 {
fn from(other: Holiday) -> u32 {
u16::from(other) as u32
}
}
impl From<Holiday> for u16 {
fn from(other: Holiday) -> u16 {
u8::from(other) as u16
}
}
impl From<Holiday> for u8 {
fn from(other: Holiday) -> u8 {
match other {
Holiday::None => 0,
Holiday::Christmas => 1,
Holiday::Valentines => 3,
Holiday::Easter => 4,
Holiday::Halloween => 5,
Holiday::Sonic => 6,
Holiday::NewYear => 7,
Holiday::Summer => 8,
Holiday::White => 9,
Holiday::Wedding => 10,
Holiday::Fall => 11,
Holiday::Spring => 12,
Holiday::Summer2 => 13,
Holiday::Spring2 => 14,
}
}
}

View File

@ -1,150 +0,0 @@
#[derive(Debug, Copy, Clone, derive_more::Display)]
pub enum Episode {
#[display(fmt="ep1")]
One,
#[display(fmt="ep2")]
Two,
#[display(fmt="ep4")]
Four,
}
impl TryFrom<u8> for Episode {
type Error = ();
fn try_from(value: u8) -> Result<Episode, ()> {
match value {
1 => Ok(Episode::One),
2 => Ok(Episode::Two),
3 => Ok(Episode::Four),
_ => Err(())
}
}
}
impl From<Episode> for u8 {
fn from(other: Episode) -> u8 {
match other {
Episode::One => 1,
Episode::Two => 2,
Episode::Four => 3,
}
}
}
impl Episode {
pub fn from_quest(value: u8) -> Option<Episode> {
match value {
0 => Some(Episode::One),
1 => Some(Episode::Two),
2 => Some(Episode::Four),
_ => None,
}
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
pub enum Difficulty {
Normal,
Hard,
VeryHard,
Ultimate,
}
impl TryFrom<u8> for Difficulty {
type Error = ();
fn try_from(value: u8) -> Result<Difficulty, ()> {
match value {
0 => Ok(Difficulty::Normal),
1 => Ok(Difficulty::Hard),
2 => Ok(Difficulty::VeryHard),
3 => Ok(Difficulty::Ultimate),
_ => Err(())
}
}
}
impl From<Difficulty> for u8 {
fn from(other: Difficulty) -> u8 {
match other {
Difficulty::Normal => 0,
Difficulty::Hard => 1,
Difficulty::VeryHard => 2,
Difficulty::Ultimate => 3,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum PlayerMode {
Single,
Multi,
}
impl PlayerMode {
pub fn value(&self) -> u8 {
match self {
PlayerMode::Single => 1,
PlayerMode::Multi => 0,
}
}
}
#[derive(Debug, Copy, Clone, derive_more::Display)]
pub enum RoomMode {
#[display(fmt="single")]
Single {
episode: Episode,
difficulty: Difficulty,
},
#[display(fmt="multi")]
Multi {
episode: Episode,
difficulty: Difficulty,
},
#[display(fmt="challenge")]
Challenge {
episode: Episode,
},
#[display(fmt="battle")]
Battle {
episode: Episode,
difficulty: Difficulty,
}
}
impl RoomMode {
pub fn difficulty(&self) -> Difficulty {
match self {
RoomMode::Single {difficulty, ..} => *difficulty,
RoomMode::Multi {difficulty, ..} => *difficulty,
RoomMode::Battle {difficulty, ..} => *difficulty,
RoomMode::Challenge {..} => Difficulty::Normal,
}
}
pub fn episode(&self) -> Episode {
match self {
RoomMode::Single {episode, ..} => *episode,
RoomMode::Multi {episode, ..} => *episode,
RoomMode::Battle {episode, ..} => *episode,
RoomMode::Challenge {episode, ..} => *episode,
}
}
pub fn battle(&self) -> bool {
matches!(self, RoomMode::Battle {..})
}
pub fn challenge(&self) -> bool {
matches!(self, RoomMode::Challenge {..})
}
pub fn player_mode(&self) -> PlayerMode {
match self {
RoomMode::Single {..} => PlayerMode::Single,
_ => PlayerMode::Multi,
}
}
}

View File

@ -1,18 +0,0 @@
[package]
name = "networking"
version = "0.1.0"
edition = "2021"
[dependencies]
entity = { workspace = true }
libpso = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
derive_more = { workspace = true }

View File

@ -1,4 +0,0 @@
pub mod cipherkeys;
pub mod serverstate;
pub mod mainloop;
pub mod interserver;

View File

@ -1,296 +0,0 @@
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, error};
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use libpso::PacketParseError;
use crate::serverstate::ClientId;
use crate::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
#[derive(Debug)]
pub enum NetworkError {
CouldNotSend,
CipherError(CipherError),
PacketParseError(PacketParseError),
IOError(std::io::Error),
DataNotReady,
ClientDisconnected,
}
impl From<CipherError> for NetworkError {
fn from(err: CipherError) -> NetworkError {
NetworkError::CipherError(err)
}
}
impl From<std::io::Error> for NetworkError {
fn from(err: std::io::Error) -> NetworkError {
NetworkError::IOError(err)
}
}
impl From<PacketParseError> for NetworkError {
fn from(err: PacketParseError) -> NetworkError {
NetworkError::PacketParseError(err)
}
}
pub struct PacketReceiver<C: PSOCipher> {
socket: async_std::net::TcpStream,
cipher: C,
recv_buffer: Vec<u8>,
incoming_data: Vec<u8>,
}
impl<C: PSOCipher> PacketReceiver<C> {
pub fn new(socket: async_std::net::TcpStream, cipher: C) -> PacketReceiver<C> {
PacketReceiver {
socket,
cipher,
recv_buffer: Vec::new(),
incoming_data: Vec::new(),
}
}
async fn fill_recv_buffer(&mut self) -> Result<(), NetworkError> {
let mut data = [0u8; 0x8000];
let mut socket = self.socket.clone();
let len = socket.read(&mut data).await?;
if len == 0 {
return Err(NetworkError::ClientDisconnected);
}
self.recv_buffer.extend_from_slice(&data[..len]);
let mut dec_buf = {
//let mut cipher = self.cipher.lock().await;
let block_chunk_len = self.recv_buffer.len() / self.cipher.block_size() * self.cipher.block_size();
let buf = self.recv_buffer.drain(..block_chunk_len).collect::<Vec<_>>();
self.cipher.decrypt(&buf)?
};
self.incoming_data.append(&mut dec_buf);
Ok(())
}
pub async fn recv_pkts<R: RecvServerPacket + std::fmt::Debug>(&mut self) -> Result<Vec<R>, NetworkError> {
self.fill_recv_buffer().await?;
let mut result = Vec::new();
loop {
if self.incoming_data.len() < 2 {
break;
}
let pkt_size = u16::from_le_bytes([self.incoming_data[0], self.incoming_data[1]]) as usize;
let mut pkt_len = pkt_size;
while pkt_len % self.cipher.block_size() != 0 {
pkt_len += 1;
}
if pkt_len > self.incoming_data.len() {
break;
}
let pkt_data = self.incoming_data.drain(..pkt_len).collect::<Vec<_>>();
trace!("[recv buf] {:?}", pkt_data);
let pkt = match R::from_bytes(&pkt_data[..pkt_size]) {
Ok(p) => p,
Err(err) => {
warn!("error RecvServerPacket::from_bytes: {:?}", err);
continue
},
};
result.push(pkt);
}
Ok(result)
}
}
async fn recv_loop<STATE, S, R, C, E>(mut state: STATE,
socket: async_std::net::TcpStream,
client_id: ClientId,
cipher: C,
clients: Arc<RwLock<HashMap<ClientId, channel::Sender<S>>>>)
where
STATE: ServerState<SendPacket=S, RecvPacket=R, Cipher=C, PacketError=E> + Send,
S: SendServerPacket + Debug + Send,
R: RecvServerPacket + Debug + Send,
C: PSOCipher + Send,
E: std::fmt::Debug + Send,
{
let mut pkt_receiver = PacketReceiver::new(socket, cipher);
loop {
match pkt_receiver.recv_pkts::<R>().await {
Ok(pkts) => {
for pkt in pkts {
info!("[recv from {:?}] {:#?}", client_id, pkt);
match state.handle(client_id, pkt).await {
Ok(response) => {
for resp in response {
clients
.read()
.await
.get(&resp.0)
.unwrap()
.send(resp.1)
.await
.unwrap();
}
},
Err(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;
}
}
}
},
Err(err) => {
match err {
NetworkError::ClientDisconnected => {
info!("[client recv {:?}] disconnected", client_id);
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;
}
_ => {
error!("[client {:?} recv error] {:?}", client_id, err);
}
}
}
}
}
}
async fn send_pkt<S, C>(socket: &mut async_std::net::TcpStream,
cipher: &mut C,
pkt: &S)
-> Result<(), NetworkError>
where
S: SendServerPacket + std::fmt::Debug,
C: PSOCipher,
{
let buf = pkt.as_bytes();
let cbuf = cipher.encrypt(&buf)?;
socket.write_all(&cbuf).await?;
Ok(())
}
async fn send_loop<S, C>(mut socket: async_std::net::TcpStream, client_id: ClientId, mut cipher: C, packet_queue: channel::Receiver<S>)
where
S: SendServerPacket + std::fmt::Debug,
C: PSOCipher,
{
loop {
match packet_queue.recv().await {
Ok(pkt) => {
info!("[send to {:?}] {:#?}", client_id, pkt);
if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await {
error!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
}
},
Err(err) => {
info!("send to {:?} failed: {:?}", client_id, err);
break;
}
}
}
}
pub async fn run_server<STATE, S, R, C, E>(mut state: STATE, port: u16)
where
STATE: ServerState<SendPacket=S, RecvPacket=R, Cipher=C, PacketError=E> + Send + 'static,
S: SendServerPacket + std::fmt::Debug + Send + 'static,
R: RecvServerPacket + std::fmt::Debug + Send,
C: PSOCipher + Send + 'static,
E: std::fmt::Debug + Send,
{
let listener = async_std::net::TcpListener::bind(&std::net::SocketAddr::from((std::net::Ipv4Addr::new(0,0,0,0), port))).await.unwrap();
let mut id = 0;
let clients = Arc::new(RwLock::new(HashMap::new()));
loop {
let (mut socket, addr) = listener.accept().await.unwrap();
id += 1;
let client_id = crate::serverstate::ClientId(id);
info!("new client {:?} {:?} {:?}", client_id, socket, addr);
let (client_tx, client_rx) = async_std::channel::unbounded();
clients
.write()
.await
.insert(client_id, client_tx.clone());
let mut cipher_in: Option<C> = None;
let mut cipher_out: Option<C> = None;
for action in state.on_connect(client_id).await.unwrap() {
match action {
OnConnect::Cipher(cin, cout) => {
cipher_in = Some(cin);
cipher_out = Some(cout);
},
OnConnect::Packet(pkt) => {
if let Err(err) = send_pkt(&mut socket, &mut NullCipher {}, &pkt).await {
error!("error sending on_connect packet {:?}", err);
}
}
}
}
let rstate = state.clone();
let rsocket = socket.clone();
let rclients = clients.clone();
async_std::task::spawn(async move {
recv_loop(rstate, rsocket, client_id, cipher_in.unwrap(), rclients).await
});
async_std::task::spawn(async move {
send_loop(socket, client_id, cipher_out.unwrap(), client_rx).await
});
}
}

View File

@ -1,227 +0,0 @@
use std::time::Duration;
use std::pin::Pin;
use futures::future::Future;
use log::{info, warn};
use async_std::sync::{Arc, RwLock};
use async_std::io::prelude::{ReadExt, WriteExt};
use std::collections::HashMap;
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::interserver::{ServerId, InterserverActor};
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use crate::serverstate::{ServerState, SendServerPacket, RecvServerPacket};
use entity::gateway::entitygateway::EntityGateway;
use async_std::channel;
use std::fmt::Debug;
#[derive(Debug)]
enum MessageReceiverError {
//InvalidSize,
InvalidPayload,
//NetworkError(std::io::Error),
Disconnected,
}
struct MessageReceiver {
socket: async_std::net::TcpStream,
}
impl MessageReceiver {
fn new(socket: async_std::net::TcpStream) -> MessageReceiver {
MessageReceiver {
socket,
}
}
async fn recv<R: serde::de::DeserializeOwned + std::fmt::Debug>(&mut self) -> Result<R, MessageReceiverError> {
let mut size_buf = [0u8; 4];
self.socket.read_exact(&mut size_buf).await.map_err(|_| MessageReceiverError::Disconnected)?;
let size = u32::from_le_bytes(size_buf) as usize;
let mut payload = vec![0u8; size];
self.socket.read_exact(&mut payload).await.map_err(|_| MessageReceiverError::Disconnected)?;
let payload = String::from_utf8(payload).map_err(|_| MessageReceiverError::InvalidPayload)?;
let msg = serde_json::from_str(&payload).map_err(|_| MessageReceiverError::InvalidPayload)?;
Ok(msg)
}
}
async fn interserver_recv_loop<STATE, S, R, E>(mut state: STATE, server_id: ServerId, socket: async_std::net::TcpStream, ships: Arc<RwLock<HashMap<ServerId, channel::Sender<S>>>>)
where
STATE: InterserverActor<SendMessage=S, RecvMessage=R, Error=E> + Send,
S: serde::Serialize + Debug + Send,
R: serde::de::DeserializeOwned + Debug + Send,
E: Debug + Send,
{
let mut msg_receiver = MessageReceiver::new(socket);
loop {
match msg_receiver.recv::<R>().await {
Ok(msg) => {
info!("[interserver recv {:?}] {:?}", server_id, msg);
match state.on_action(server_id, msg).await {
Ok(response) => {
for resp in response {
ships
.read()
.await
.get(&resp.0)
.unwrap()
.send(resp.1)
.await
.unwrap();
}
},
Err(err) => {
warn!("[interserver recv {:?}] error {:?}", server_id, err);
}
}
},
Err(err) => {
if let MessageReceiverError::Disconnected = err {
info!("[interserver recv {:?}] disconnected", server_id);
for (_, _sender) in ships.read().await.iter() {
for pkt in state.on_disconnect(server_id).await {
ships
.read()
.await
.get(&pkt.0)
.unwrap()
.send(pkt.1)
.await
.unwrap();
}
}
ships
.write()
.await
.remove(&server_id);
break;
}
info!("[interserver recv {:?}] error {:?}", server_id, err);
}
}
}
}
async fn interserver_send_loop<S>(server_id: ServerId, mut socket: async_std::net::TcpStream, to_send: channel::Receiver<S>)
where
S: serde::Serialize + std::fmt::Debug,
{
loop {
let msg = to_send.recv().await.unwrap();
let payload = serde_json::to_string(&msg);
if let Ok(payload) = payload {
let len_bytes = u32::to_le_bytes(payload.len() as u32);
if let Err(err) = socket.write_all(&len_bytes).await {
warn!("[interserver send {:?}] failed: {:?}", server_id, err);
break;
}
if let Err(err) = socket.write_all(payload.as_bytes()).await {
warn!("[interserver send {:?}] failed: {:?}", server_id, err);
break;
}
}
}
}
pub async fn run_interserver_listen<STATE, S, R, E>(mut state: STATE, port: u16)
where
STATE: InterserverActor<SendMessage=S, RecvMessage=R, Error=E> + Send + 'static,
S: serde::Serialize + Debug + Send + 'static,
R: serde::de::DeserializeOwned + Debug + Send,
E: Debug + Send,
{
let listener = async_std::net::TcpListener::bind(&std::net::SocketAddr::from((std::net::Ipv4Addr::new(0,0,0,0), port))).await.unwrap();
let mut id = 0;
let ships = Arc::new(RwLock::new(HashMap::new()));
loop {
let (socket, addr) = listener.accept().await.unwrap();
info!("[interserver listen] new server: {:?} {:?}", socket, addr);
id += 1;
let server_id = crate::interserver::ServerId(id);
let (client_tx, client_rx) = async_std::channel::unbounded();
state.set_sender(server_id, client_tx.clone()).await;
ships
.write()
.await
.insert(server_id, client_tx.clone());
for msg in state.on_connect(server_id).await {
if let Some(ship_sender) = ships.read().await.get(&msg.0) {
ship_sender.send(msg.1).await.unwrap();
}
}
let rstate = state.clone();
let rsocket = socket.clone();
let rships = ships.clone();
async_std::task::spawn(async move {
interserver_recv_loop(rstate, server_id, rsocket, rships).await;
});
async_std::task::spawn(async move {
interserver_send_loop(server_id, socket, client_rx).await;
});
}
}
pub async fn run_interserver_connect<STATE, S, R, E>(mut state: STATE, ip: std::net::Ipv4Addr, port: u16)
where
STATE: InterserverActor<SendMessage=S, RecvMessage=R, Error=E> + Send + 'static,
S: serde::Serialize + Debug + Send + 'static,
R: serde::de::DeserializeOwned + Debug + Send,
E: Debug + Send,
{
let mut id = 0;
loop {
info!("[interserver connect] trying to connect to server");
let socket = match async_std::net::TcpStream::connect((ip, port)).await {
Ok(socket) => socket,
Err(err) => {
info!("err trying to connect to loginserv {:?}", err);
async_std::task::sleep(std::time::Duration::from_secs(10)).await;
continue;
}
};
id += 1;
let server_id = crate::interserver::ServerId(id);
info!("[interserver connect] found loginserv: {:?} {:?}", server_id, socket);
let (client_tx, client_rx) = async_std::channel::unbounded();
state.set_sender(server_id, client_tx.clone()).await;
for msg in state.on_connect(server_id).await {
client_tx.send(msg.1).await.unwrap();
}
let other_server = vec![(server_id, client_tx.clone())].into_iter().collect();
let rstate = state.clone();
let rsocket = socket.clone();
async_std::task::spawn(async move {
interserver_recv_loop(rstate, server_id, rsocket, Arc::new(RwLock::new(other_server))).await;
});
let ssocket = socket.clone();
async_std::task::spawn(async move {
interserver_send_loop(server_id, ssocket, client_rx).await;
});
let mut buf = [0u8; 1];
loop {
let peek = socket.peek(&mut buf).await;
if let Ok(0) = peek {
break
}
}
}
}

View File

@ -1,6 +0,0 @@
#![allow(unused_imports)]
mod client;
mod interserver;
pub use self::client::*;
pub use self::interserver::*;

2
src/patch/mod.rs Normal file
View File

@ -0,0 +1,2 @@
#[allow(clippy::module_inception)]
pub mod patch;

View File

@ -11,8 +11,8 @@ use libpso::crypto::pc::PSOPCCipher;
use ron::de::from_str; use ron::de::from_str;
use serde::Deserialize; use serde::Deserialize;
use networking::mainloop::{NetworkError}; use crate::common::mainloop::{NetworkError};
use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
#[derive(Debug)] #[derive(Debug)]
pub enum PatchError { pub enum PatchError {
@ -136,7 +136,6 @@ impl SendServerPacket for SendPatchPacket {
} }
#[derive(Clone)]
pub struct PatchServerState { pub struct PatchServerState {
patch_file_tree: PatchFileTree, patch_file_tree: PatchFileTree,
patch_file_lookup: HashMap<u32, PatchFile>, patch_file_lookup: HashMap<u32, PatchFile>,
@ -159,39 +158,33 @@ impl PatchServerState {
impl ServerState for PatchServerState { impl ServerState for PatchServerState {
type SendPacket = SendPatchPacket; type SendPacket = SendPatchPacket;
type RecvPacket = RecvPatchPacket; type RecvPacket = RecvPatchPacket;
type Cipher = PSOPCCipher;
type PacketError = PatchError; type PacketError = PatchError;
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, PatchError> { async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket>>, PatchError> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let key_in: u32 = rng.gen(); let key_in: u32 = rng.gen();
let key_out: u32 = rng.gen(); let key_out: u32 = rng.gen();
Ok(vec![OnConnect::Packet(SendPatchPacket::PatchWelcome(PatchWelcome::new(key_out, key_in))), Ok(vec![OnConnect::Packet(SendPatchPacket::PatchWelcome(PatchWelcome::new(key_out, key_in))),
OnConnect::Cipher(PSOPCCipher::new(key_in), PSOPCCipher::new(key_out)) OnConnect::Cipher((Box::new(PSOPCCipher::new(key_in)), Box::new(PSOPCCipher::new(key_out))))
]) ])
} }
async fn handle(&mut self, id: ClientId, pkt: RecvPatchPacket) -> Result<Vec<(ClientId, SendPatchPacket)>, PatchError> { async fn handle(&mut self, id: ClientId, pkt: &RecvPatchPacket)
-> Result<Box<dyn Iterator<Item = (ClientId, SendPatchPacket)> + Send>, PatchError> {
Ok(match pkt { Ok(match pkt {
RecvPatchPacket::PatchWelcomeReply(_pkt) => { RecvPatchPacket::PatchWelcomeReply(_pkt) => {
vec![SendPatchPacket::RequestLogin(RequestLogin {})] Box::new(vec![SendPatchPacket::RequestLogin(RequestLogin {})].into_iter().map(move |pkt| (id, pkt)))
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
}, },
RecvPatchPacket::LoginReply(_pkt) => { RecvPatchPacket::LoginReply(_pkt) => {
let mut pkts = vec![SendPatchPacket::Message(Message::new(self.patch_motd.clone()))]; let mut p = vec![SendPatchPacket::Message(Message::new(self.patch_motd.clone()))];
pkts.append(&mut get_file_list_packets(&self.patch_file_tree)); p.append(&mut get_file_list_packets(&self.patch_file_tree));
pkts.push(SendPatchPacket::PatchEndList(PatchEndList {})); p.push(SendPatchPacket::PatchEndList(PatchEndList {}));
pkts Box::new(p.into_iter().map(move |pkt| (id, pkt)))
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
}, },
RecvPatchPacket::FileInfoReply(pkt) => { RecvPatchPacket::FileInfoReply(pkt) => {
self.patch_file_info.push(pkt); self.patch_file_info.push(pkt.clone());
Vec::new() Box::new(None.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvPatchPacket::FileInfoListEnd(_pkt) => { RecvPatchPacket::FileInfoListEnd(_pkt) => {
let need_update = self.patch_file_info.iter() let need_update = self.patch_file_info.iter()
@ -201,12 +194,10 @@ impl ServerState for PatchServerState {
let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size); let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size);
let total_files = need_update.len() as u32; let total_files = need_update.len() as u32;
vec![SendPatchPacket::FilesToPatchMetadata(FilesToPatchMetadata::new(total_size, total_files)), let p = vec![SendPatchPacket::FilesToPatchMetadata(FilesToPatchMetadata::new(total_size, total_files)),
SendPatchPacket::PatchStartList(PatchStartList {})] SendPatchPacket::PatchStartList(PatchStartList {})
.into_iter() ];
.chain(SendFileIterator::new(self)) Box::new(p.into_iter().chain(SendFileIterator::new(self)).map(move |pkt| (id, pkt)))
.map(move |pkt| (id, pkt))
.collect()
} }
}) })
} }
@ -341,7 +332,7 @@ impl Iterator for SendFileIterator {
if len == 0 { if len == 0 {
self.current_file = None; self.current_file = None;
self.chunk_num = 0; self.chunk_num = 0;
Some(SendPatchPacket::EndFileSend(EndFileSend::default())) Some(SendPatchPacket::EndFileSend(EndFileSend::new()))
} }
else { else {
let mut crc = crc32::Digest::new(crc32::IEEE); let mut crc = crc32::Digest::new(crc32::IEEE);
@ -395,18 +386,18 @@ pub struct PatchConfig {
pub fn load_config() -> PatchConfig { pub fn load_config() -> PatchConfig {
let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) { let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) {
Err(err) => panic!("Failed to open patch.ron config file. \n{err}"), Err(err) => panic!("Failed to open patch.ron config file. \n{}", err),
Ok(ini_file) => ini_file, Ok(ini_file) => ini_file,
}; };
let mut s = String::new(); let mut s = String::new();
if let Err(err) = (&ini_file).read_to_string(&mut s) { if let Err(err) = (&ini_file).read_to_string(&mut s) {
panic!("Failed to read patch.ron config file. \n{err}"); panic!("Failed to read patch.ron config file. \n{}", err);
} }
let config: PatchConfig = match from_str(s.as_str()) { let config: PatchConfig = match from_str(s.as_str()) {
Ok(config) => config, Ok(config) => config,
Err(err) => panic!("Failed to load values from patch.ron \n{err}"), Err(err) => panic!("Failed to load values from patch.ron \n{}",err),
}; };
config config
} }

View File

@ -1,15 +0,0 @@
[package]
name = "patch_server"
version = "0.1.0"
edition = "2021"
[dependencies]
networking = { workspace = true }
libpso = { workspace = true }
async-trait = { workspace = true }
rand = { workspace = true }
crc = { workspace = true }
ron = { workspace = true }
serde = { workspace = true }

View File

@ -1,22 +0,0 @@
[package]
name = "pktbuilder"
version = "0.1.0"
edition = "2021"
[dependencies]
quests = { workspace = true }
stats = { workspace = true }
location = { workspace = true }
client = { workspace = true }
items = { workspace = true }
networking = { workspace = true }
maps = { workspace = true }
room = { workspace = true }
shops = { workspace = true }
entity = { workspace = true }
libpso = { workspace = true }
anyhow = { workspace = true }
futures = { workspace = true }
thiserror = { workspace = true }

View File

@ -1,94 +0,0 @@
use libpso::packet::ship::*;
use networking::serverstate::ClientId;
use maps::Holiday;
use client::Clients;
use location::{ClientLocation, LobbyId, ClientLocationError};
use crate::player_info;
use items::state::ItemState;
use futures::future::join_all;
pub async fn join_lobby(id: ClientId,
lobby: LobbyId,
client_location: &ClientLocation,
clients: &Clients,
item_state: &ItemState,
event: Holiday)
-> 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(
lobby_clients.into_iter()
.map(|area_client| {
let item_state = item_state.clone();
async move {
clients.with(area_client.client, |client| Box::pin(async move {
let inventory = item_state.get_character_inventory(&client.character).await?;
Ok(player_info(0x100, client, &area_client, &inventory).await)
})).await?
}}))
.await
.into_iter()
.collect::<Result<Vec<_>, anyhow::Error>>()?;
let client_block = clients.with(id, |client| Box::pin(async move {
client.block as u16
})).await?;
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() })?;
Ok(JoinLobby {
client: area_client.local_client.id(),
leader: leader.local_client.id(),
one: 1,
lobby: lobby.id(),
block: client_block,
event: event.into(),
padding: 0,
playerinfo,
})
}
pub async fn add_to_lobby(id: ClientId,
lobby: LobbyId,
client_location: &ClientLocation,
clients: &Clients,
item_state: &ItemState,
event: Holiday)
-> 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| {
let item_state = item_state.clone();
Box::pin(async move {
let inventory = item_state.get_character_inventory(&client.character).await?;
Ok(AddToLobby {
flag: 1,
client: area_client.local_client.id(),
leader: leader.local_client.id(),
one: 1,
lobby: lobby.id(),
block: client.block as u16,
event: event.into(),
padding: 0,
playerinfo: player_info(0x100, client, &area_client, &inventory).await,
})
})}).await?
}
pub async fn remove_from_lobby(id: ClientId,
client_location: &ClientLocation)
-> 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
.get_area(id)
.await
.map_err(|err| -> ClientLocationError { err.into() })?)
.await
.map_err(|err| -> ClientLocationError { err.into() })?.local_client.id();
Ok(LeaveLobby {
client: prev_area_index,
leader: prev_area_leader_index,
_padding: 0,
})
}

View File

@ -1,82 +0,0 @@
use std::convert::TryInto;
use libpso::packet::ship::*;
use networking::serverstate::ClientId;
use maps::Holiday;
use client::{ClientState, Clients};
use location::{ClientLocation, RoomId, AreaClient, ClientLocationError};
use room::RoomState;
use items::state::ItemState;
use crate::{player_header, player_info};
use futures::stream::StreamExt;
pub async fn join_room(id: ClientId,
clients: &Clients,
client_location: &ClientLocation,
room_id: RoomId,
room: &RoomState,
event: Holiday)
-> Result<JoinRoom, anyhow::Error> {
let all_clients = client_location.get_clients_in_room(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?;
#[allow(clippy::manual_try_fold)] // I don't think its even possible to make this work here
let players = futures::stream::iter(all_clients.iter())
.enumerate()
.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| -> ClientLocationError {err.into() })?;
clients.with(c.client, |client| Box::pin(async move {
acc.map(|mut a| {
a[i] = player_header(0x10000, client, &header_area_client);
a
})
})).await?
}).await?;
let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let leader = client_location.get_room_leader(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?;
Ok(JoinRoom {
flag: all_clients.len() as u32,
maps: room.maps.map_headers(),
players,
client: area_client.local_client.id(),
leader: leader.local_client.id(),
one: 1,
difficulty: room.mode.difficulty().into(),
battle: room.mode.battle() as u8,
event: event.into(),
section: room.section_id.into(),
challenge: room.mode.challenge() as u8,
random_seed: room.random_seed,
episode: room.mode.episode().into(),
one2: 1,
single_player: room.mode.player_mode().value(),
unknown: 0,
})
}
pub async fn add_to_room(_id: ClientId,
client: &ClientState,
area_client: &AreaClient,
leader: &AreaClient,
item_state: &ItemState,
event: Holiday)
-> Result<AddToRoom, anyhow::Error> {
let inventory = item_state.get_character_inventory(&client.character).await?;
Ok(AddToRoom {
flag: 1,
client: area_client.local_client.id(),
leader: leader.local_client.id(),
one: 0, // TODO: ????????
lobby: 0xFF,
block: 0,
event: event.into(),
padding: 0,
playerinfo: player_info(0x10000, client, area_client, &inventory).await,
})
}
pub fn build_rare_monster_list(rare_monster_vec: Vec<u16>) -> RareMonsterList {
RareMonsterList {
ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]),
}
}

View File

@ -1,107 +0,0 @@
use futures::stream::{FuturesOrdered, StreamExt};
use libpso::packet::ship::*;
use crate::common::serverstate::ClientId;
use crate::entity::gateway::EntityGateway;
use crate::ship::client::{Clients, ClientState};
use crate::ship::teams::Teams;
use crate::ship::location::ClientLocation;
use crate::ship::ship::ShipError;
use crate::entity::team::TeamEntity;
pub fn client_team_state_changed(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> ClientTeamStateChanged {
ClientTeamStateChanged {
unknown: 0,
guildcard: client.user.guildcard(),
team_id: team.id.0,
unknown2: [0;2],
privilege: 0x40, // TODO: improve
team_name: libpso::utf8_to_utf16_array!(team.name, 14),
unknown3: 0x00986C84, // TODO: what if we omit this?
}
}
fn player_team_info(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> PlayerTeamInfo {
PlayerTeamInfo {
guildcard: client.user.guildcard(),
team_id: team.id.0,
info: 0,
info2: 0,
privilege: 0x40, // TODO: improve
team_name: libpso::utf8_to_utf16_array!(team.name, 14),
unknown: 0x00986C84, // TODO: what if we omit this?
guildcard_again: client.user.guildcard(),
client_id: client_id.0 as u32,
character_name: libpso::utf8_to_utf16_array!(client.character.name, 12),
unknown2: 0,
unknown3: 0,
team_flag: team.team_flag,
}
}
pub fn team_info_individual(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> TeamInfo {
TeamInfo {
clients: vec![player_team_info(client_id, client, team)]
}
}
pub async fn player_team_info_list<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
teams: &Teams<EG>,
) -> Result<Vec<PlayerTeamInfo>, ShipError>
where
EG: EntityGateway + Clone + 'static,
{
Ok(futures::stream::iter(client_location.get_all_clients_by_client(id).await?.into_iter())
.filter_map(|area_client| {
let clients = clients.clone();
async move {
clients.with(area_client.client, |client| {
let mut teams = teams.clone();
Box::pin(async move {
let team = teams.get_team(area_client.client).await.ok()??;
Some(player_team_info(area_client.client, client, &team))
})}).await.ok()?
}})
.collect::<Vec<_>>()
.await)
}
pub async fn team_info<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
teams: &Teams<EG>,
) -> Result<TeamInfo, ShipError>
where
EG: EntityGateway + Clone + 'static,
{
Ok(TeamInfo {
clients: player_team_info_list(id, client_location, clients, teams).await?,
})
}
pub async fn lobby_team_list<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
teams: &Teams<EG>,
) -> Result<TeamLobbyList, ShipError>
where
EG: EntityGateway + Clone + 'static,
{
Ok(TeamLobbyList {
clients: player_team_info_list(id, client_location, clients, teams).await?,
})
}
pub fn team_invitation_info(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> TeamInvitationInfo {
TeamInvitationInfo {
guildcard: client.user.guildcard(),
team_id: team.id.0,
unknown: [0; 2],
team_name: libpso::utf8_to_utf16_array!(team.name, 14),
unknown2: 0x00986C84, // TODO: what if we omit this?
team_flag: team.team_flag,
}
}

View File

@ -1,18 +0,0 @@
[package]
name = "quests"
version = "0.1.0"
edition = "2021"
[dependencies]
maps = { workspace = true }
libpso = { workspace = true }
async-std = { workspace = true }
ages-prs = { workspace = true }
log = { workspace = true }
byteorder = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
toml = { workspace = true }
serde = { workspace = true }

View File

@ -1,17 +0,0 @@
[package]
name = "room"
version = "0.1.0"
edition = "2021"
[dependencies]
maps = { workspace = true }
entity = { workspace = true }
quests = { workspace = true }
location = { workspace = true }
drops = { workspace = true }
rand = { workspace = true }
async-std = { workspace = true }
futures = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true }

View File

@ -1,274 +0,0 @@
use std::collections::HashMap;
use std::convert::{From, Into};
use async_std::sync::{Arc, RwLock, RwLockReadGuard};
use futures::future::BoxFuture;
use futures::stream::{FuturesOrdered, Stream};
use thiserror::Error;
use rand::Rng;
use quests::{QuestList, QuestLoadError};
use maps::maps::Maps;
use drops::DropTable;
use entity::character::SectionID;
use entity::room::{RoomEntityId, RoomEntityMode};
use maps::monster::{load_monster_stats_table, MonsterType, MonsterStats};
use maps::area::MapAreaLookup;
use maps::Holiday;
use location::{MAX_ROOMS, RoomId};
use maps::room::{Episode, Difficulty, RoomMode};
#[derive(Error, Debug)]
pub enum RoomError {
#[error("invalid room id {0}")]
Invalid(u32),
}
#[derive(Clone)]
pub struct Rooms([Arc<RwLock<Option<RoomState>>>; MAX_ROOMS]);
impl Default for Rooms {
fn default() -> Rooms {
Rooms(core::array::from_fn(|_| Arc::new(RwLock::new(None))))
}
}
impl Rooms {
pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> {
*self.0
.get(room_id.0)
.ok_or(RoomError::Invalid(room_id.0 as u32))?
.write()
.await = Some(room);
Ok(())
}
pub async fn remove(&self, room_id: RoomId) {
if let Some(room) = self.0.get(room_id.0) {
*room
.write()
.await = None;
}
}
pub async fn exists(&self, room_id: RoomId) -> bool {
match self.0.get(room_id.0) {
Some(room) => {
room
.read()
.await
.is_some()
},
None => false,
}
}
pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a
{
let room = self.0
.get(room_id.0)
.ok_or(RoomError::Invalid(room_id.0 as u32))?
.read()
.await;
if let Some(room) = room.as_ref() {
Ok(func(room).await)
}
else {
Err(RoomError::Invalid(room_id.0 as u32).into())
}
}
pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a
{
let mut room = self.0
.get(room_id.0)
.ok_or(RoomError::Invalid(room_id.0 as u32))?
.write()
.await;
if let Some(room) = room.as_mut() {
Ok(func(room).await)
}
else {
Err(RoomError::Invalid(room_id.0 as u32).into())
}
}
pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard<Option<RoomState>> {
self.0
.get(room_id.0)
.unwrap()
.read()
.await
}
pub fn stream(&self) -> impl Stream<Item = RwLockReadGuard<Option<RoomState>>> {
self.0
.iter()
.map(|room| async move {
room
.read()
.await
})
.collect::<FuturesOrdered<_>>()
}
}
#[derive(Debug, Error)]
#[error("")]
pub enum RoomCreationError {
InvalidMode,
InvalidEpisode(u8),
InvalidDifficulty(u8),
CouldNotLoadMonsterStats(RoomMode),
CouldNotLoadQuests,
}
pub enum QuestCategoryType {
Standard,
Government,
}
impl From<usize> for QuestCategoryType {
fn from(f: usize) -> QuestCategoryType {
match f {
0 => QuestCategoryType::Standard,
_ => QuestCategoryType::Government,
}
}
}
impl From<u32> for QuestCategoryType {
fn from(f: u32) -> QuestCategoryType {
match f {
0 => QuestCategoryType::Standard,
_ => QuestCategoryType::Government,
}
}
}
impl QuestCategoryType {
pub fn value(&self) -> usize {
match self {
QuestCategoryType::Standard => 0,
QuestCategoryType::Government => 1,
}
}
}
pub struct RoomState {
pub room_id: RoomEntityId,
pub mode: RoomMode,
pub name: String,
pub password: [u16; 16],
pub maps: Maps,
pub drop_table: Box<dyn DropTable + Send + Sync>,
pub section_id: SectionID,
pub random_seed: u32,
pub bursting: bool,
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
pub map_areas: MapAreaLookup,
pub quest_group: QuestCategoryType,
pub standard_quests: QuestList,
pub government_quests: QuestList,
// enemy info
}
impl RoomState {
pub fn get_flags_for_room_list(&self) -> u8 {
let mut flags = 0u8;
match self.mode {
RoomMode::Single {..} => {flags += 0x04}
RoomMode::Battle {..} => {flags += 0x10},
RoomMode::Challenge {..} => {flags += 0x20},
_ => {flags += 0x40},
};
if self.password[0] > 0 {
flags += 0x02;
}
flags
}
pub fn get_episode_for_room_list(&self) -> u8 {
let episode: u8 = self.mode.episode().into();
match self.mode {
RoomMode::Single {..} => episode + 0x10,
_ => episode + 0x40,
}
}
pub fn get_difficulty_for_room_list(&self) -> u8 {
let difficulty: u8 = self.mode.difficulty().into();
difficulty + 0x22
}
pub fn quests(&self) -> &QuestList {
match self.quest_group {
QuestCategoryType::Standard => &self.standard_quests,
QuestCategoryType::Government => &self.government_quests,
}
}
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub fn new (room_id: RoomEntityId,
mode: RoomEntityMode,
episode: Episode,
difficulty: Difficulty,
section_id: SectionID,
name: String,
password: [u16; 16],
event: Holiday,
map_builder: Arc<Box<dyn Fn(RoomMode, Holiday) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> Box<dyn DropTable + Send + Sync> + Send + Sync>>,
standard_quest_builder: Arc<Box<dyn Fn(RoomMode) -> Result<QuestList, QuestLoadError> + Send + Sync>>,
government_quest_builder: Arc<Box<dyn Fn(RoomMode) -> Result<QuestList, QuestLoadError> + Send + Sync>>,
) -> Result<RoomState, anyhow::Error> {
let mode = match mode {
RoomEntityMode::Single => RoomMode::Single {
episode,
difficulty,
},
RoomEntityMode::Multi => RoomMode::Multi {
episode,
difficulty,
},
RoomEntityMode::Challenge => RoomMode::Challenge {
episode,
},
RoomEntityMode::Battle => RoomMode::Battle {
episode,
difficulty,
},
};
Ok(RoomState {
room_id,
monster_stats: Box::new(load_monster_stats_table(&mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(mode))?),
mode,
random_seed: rand::thread_rng().gen(),
name,
password,
maps: map_builder(mode, event),
section_id,
drop_table: drop_table_builder(episode, difficulty, section_id),
bursting: false,
map_areas: MapAreaLookup::new(&episode),
quest_group: QuestCategoryType::Standard,
standard_quests: standard_quest_builder(mode)?,
government_quests: government_quest_builder(mode)?,
})
}
}

View File

@ -1,9 +1,10 @@
use libpso::character::character; use libpso::character::character;
use stats::leveltable::CharacterStats; use crate::common::leveltable::CharacterStats;
use entity::character::CharacterEntity; use crate::entity::character::CharacterEntity;
use items::bank::BankState; //use crate::ship::items::{CharacterInventory, CharacterBank};
use items::inventory::InventoryState; use crate::ship::items::bank::BankState;
use entity::item::Meseta; use crate::ship::items::inventory::InventoryState;
use crate::entity::item::Meseta;
#[derive(Default)] #[derive(Default)]
@ -53,7 +54,7 @@ impl<'a> CharacterBytesBuilder<'a> {
let level = self.level.unwrap(); let level = self.level.unwrap();
let meseta = self.meseta.unwrap(); let meseta = self.meseta.unwrap();
character::Character { character::Character {
name: libpso::util::utf8_to_utf16_array(&character.name), name: libpso::utf8_to_utf16_array!(character.name, 16),
hp: stats.hp, hp: stats.hp,
atp: stats.atp + character.materials.power as u16 * 2, atp: stats.atp + character.materials.power as u16 * 2,
mst: stats.mst + character.materials.mind as u16 * 2, mst: stats.mst + character.materials.mind as u16 * 2,
@ -76,7 +77,7 @@ impl<'a> CharacterBytesBuilder<'a> {
prop_y: character.appearance.prop_y, prop_y: character.appearance.prop_y,
config: character.config.as_bytes(), config: character.config.as_bytes(),
techniques: character.techs.as_bytes(), techniques: character.techs.as_bytes(),
meseta: meseta.0, meseta: meseta.0 as u32,
exp: character.exp, exp: character.exp,
..character::Character::default() ..character::Character::default()
} }

View File

@ -2,17 +2,18 @@
use rand::{Rng}; use rand::{Rng};
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use entity::character::SectionID; use crate::entity::character::SectionID;
use maps::room::{Difficulty, Episode}; use crate::ship::room::{Difficulty, Episode};
use maps::area::MapArea; use crate::ship::map::MapArea;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use maps::object::{MapObject, MapObjectType, FixedBoxDropType}; use crate::ship::map::{MapObject, MapObjectType, FixedBoxDropType};
use crate::rare_drop_table::{RareDropTable, RareDropItem}; use crate::ship::drops::rare_drop_table::{RareDropTable, RareDropItem};
use crate::generic_weapon::GenericWeaponTable; use crate::ship::drops::generic_weapon::GenericWeaponTable;
use crate::generic_armor::GenericArmorTable; use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable; use crate::ship::drops::generic_shield::GenericShieldTable;
use crate::generic_unit::GenericUnitTable; use crate::ship::drops::generic_unit::GenericUnitTable;
use crate::tool_table::ToolTable; use crate::ship::drops::tool_table::ToolTable;
use crate::entity::item::ItemDetail;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct BoxDropRate { struct BoxDropRate {
@ -175,8 +176,8 @@ impl BoxDropTable {
fn random_box_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> { fn random_box_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
self.rare_drop(map_area, rng).or_else(|| { self.rare_drop(map_area, rng).or_else(|| {
let rate = self.box_rates.rates_by_area(map_area); let rate = self.box_rates.rates_by_area(map_area);
let type_weights = WeightedIndex::new([rate.weapon_rate, rate.armor_rate, rate.shield_rate, rate.unit_rate, let type_weights = WeightedIndex::new(&[rate.weapon_rate, rate.armor_rate, rate.shield_rate, rate.unit_rate,
rate.tool_rate, rate.meseta_rate, rate.nothing_rate]).unwrap(); rate.tool_rate, rate.meseta_rate, rate.nothing_rate]).unwrap();
let btype = type_weights.sample(rng); let btype = type_weights.sample(rng);
match btype { match btype {
0 => self.weapon_table.get_drop(map_area, rng), 0 => self.weapon_table.get_drop(map_area, rng),
@ -203,7 +204,7 @@ impl BoxDropTable {
FixedBoxDropType::Specific(value) => { FixedBoxDropType::Specific(value) => {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
buf[0..4].copy_from_slice(&u32::to_be_bytes(value)); buf[0..4].copy_from_slice(&u32::to_be_bytes(value));
ItemDropType::parse_item_from_bytes(buf) ItemDetail::parse_item_from_bytes(buf)
}, },
} }
} }

View File

@ -1,14 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use rand::Rng; use rand::{Rng};
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use entity::character::SectionID; use crate::entity::item::armor::{ArmorType, Armor};
use entity::item::armor::{ArmorType, Armor}; use crate::ship::room::{Difficulty, Episode};
use maps::room::{Difficulty, Episode}; use crate::ship::map::MapArea;
use maps::area::MapArea; use crate::entity::character::SectionID;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use stats::items::{armor_stats, ArmorStats}; use crate::ship::item_stats::{armor_stats, ArmorStats};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -46,8 +46,8 @@ impl GenericArmorTable {
} }
fn armor_type<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> ArmorType { fn armor_type<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> ArmorType {
let rank_weights = WeightedIndex::new([self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2, let rank_weights = WeightedIndex::new(&[self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2,
self.rank_rates.rank3, self.rank_rates.rank4]).unwrap(); self.rank_rates.rank3, self.rank_rates.rank4]).unwrap();
let rank = rank_weights.sample(rng) as i32; let rank = rank_weights.sample(rng) as i32;
let armor_level = std::cmp::max(0i32, self.armor_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32); let armor_level = std::cmp::max(0i32, self.armor_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32);
match armor_level { match armor_level {
@ -80,8 +80,8 @@ impl GenericArmorTable {
} }
pub fn slots<R: Rng>(&self, _area_map: &MapArea, rng: &mut R) -> usize { pub fn slots<R: Rng>(&self, _area_map: &MapArea, rng: &mut R) -> usize {
let slot_weights = WeightedIndex::new([self.slot_rates.slot0, self.slot_rates.slot1, self.slot_rates.slot2, let slot_weights = WeightedIndex::new(&[self.slot_rates.slot0, self.slot_rates.slot1, self.slot_rates.slot2,
self.slot_rates.slot3, self.slot_rates.slot4]).unwrap(); self.slot_rates.slot3, self.slot_rates.slot4]).unwrap();
slot_weights.sample(rng) slot_weights.sample(rng)
} }

View File

@ -1,14 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use rand::Rng; use rand::{Rng};
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use entity::item::shield::{ShieldType, Shield}; use crate::entity::item::shield::{ShieldType, Shield};
use entity::character::SectionID; use crate::ship::room::{Difficulty, Episode};
use maps::room::{Difficulty, Episode}; use crate::ship::map::MapArea;
use maps::area::MapArea; use crate::entity::character::SectionID;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use stats::items::{shield_stats, ShieldStats}; use crate::ship::item_stats::{shield_stats, ShieldStats};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -36,8 +36,8 @@ impl GenericShieldTable {
} }
fn shield_type<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> ShieldType { fn shield_type<R: Rng>(&self, area_map: &MapArea, rng: &mut R) -> ShieldType {
let rank_weights = WeightedIndex::new([self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2, let rank_weights = WeightedIndex::new(&[self.rank_rates.rank0, self.rank_rates.rank1, self.rank_rates.rank2,
self.rank_rates.rank3, self.rank_rates.rank4]).unwrap(); self.rank_rates.rank3, self.rank_rates.rank4]).unwrap();
let rank = rank_weights.sample(rng) as i32; let rank = rank_weights.sample(rng) as i32;
let shield_level = std::cmp::max(0i32, self.shield_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32); let shield_level = std::cmp::max(0i32, self.shield_set as i32 - 3i32 + rank + area_map.drop_area_value().unwrap_or(0) as i32);
match shield_level { match shield_level {

View File

@ -1,14 +1,14 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use rand::Rng; use rand::{Rng};
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
use entity::character::SectionID; use crate::entity::item::unit::{UnitType, Unit, UnitModifier};
use entity::item::unit::{UnitType, Unit, UnitModifier}; use crate::ship::room::{Difficulty, Episode};
use maps::room::{Difficulty, Episode}; use crate::ship::map::MapArea;
use maps::area::MapArea; use crate::entity::character::SectionID;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use stats::items::{unit_stats, UnitStats}; use crate::ship::item_stats::{unit_stats, UnitStats};
@ -89,6 +89,7 @@ impl GenericUnitTable {
ItemDropType::Unit(Unit { ItemDropType::Unit(Unit {
unit: unit_type, unit: unit_type,
modifier: unit_modifier, modifier: unit_modifier,
kills: None,
}) })
}) })
} }
@ -116,6 +117,7 @@ mod test {
assert!(gut.get_drop(&area, &mut rng) == Some(ItemDropType::Unit(Unit { assert!(gut.get_drop(&area, &mut rng) == Some(ItemDropType::Unit(Unit {
unit: unit, unit: unit,
modifier: umod, modifier: umod,
kills: None,
}))); })));
} }
} }

View File

@ -1,14 +1,14 @@
use std::collections::{HashMap, BTreeMap}; use std::collections::{HashMap, BTreeMap};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use rand::Rng; use rand::{Rng};
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use entity::character::SectionID; use crate::entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
use entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial}; use crate::ship::room::{Difficulty, Episode};
use maps::room::{Difficulty, Episode}; use crate::ship::map::MapArea;
use maps::area::MapArea; use crate::entity::character::SectionID;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
@ -240,7 +240,7 @@ impl AttributeTable {
fn generate_attribute<R: Rng>(&self, pattern: &PercentPatternType, rates: &AttributeRate, rng: &mut R) -> Option<WeaponAttribute> { fn generate_attribute<R: Rng>(&self, pattern: &PercentPatternType, rates: &AttributeRate, rng: &mut R) -> Option<WeaponAttribute> {
let attribute_weights = WeightedIndex::new([rates.none, rates.native, rates.abeast, rates.machine, rates.dark, rates.hit]).unwrap(); let attribute_weights = WeightedIndex::new(&[rates.none, rates.native, rates.abeast, rates.machine, rates.dark, rates.hit]).unwrap();
let attr = match attribute_weights.sample(rng) { let attr = match attribute_weights.sample(rng) {
0 => return None, 0 => return None,
1 => Attribute::Native, 1 => Attribute::Native,
@ -253,7 +253,7 @@ impl AttributeTable {
let percents = self.percent_rates.get_by_pattern(pattern); let percents = self.percent_rates.get_by_pattern(pattern);
let value_weights = WeightedIndex::new(percents.as_array()).unwrap(); let value_weights = WeightedIndex::new(&percents.as_array()).unwrap();
let value = value_weights.sample(rng); let value = value_weights.sample(rng);
let percent = ((value + 1) * 5) as i8; let percent = ((value + 1) * 5) as i8;
@ -477,7 +477,7 @@ impl GenericWeaponTable {
let pattern = std::cmp::min(area % ratio.inc, 3); let pattern = std::cmp::min(area % ratio.inc, 3);
let weights = self.grind_rates.grind_rate[pattern as usize]; let weights = self.grind_rates.grind_rate[pattern as usize];
let grind_choice = WeightedIndex::new(weights).unwrap(); let grind_choice = WeightedIndex::new(&weights).unwrap();
grind_choice.sample(rng) grind_choice.sample(rng)
} }
@ -497,6 +497,7 @@ impl GenericWeaponTable {
grind: weapon_grind as u8, grind: weapon_grind as u8,
attrs: weapon_attributes, attrs: weapon_attributes,
tekked: weapon_special.is_none(), tekked: weapon_special.is_none(),
kills: None,
})) }))
} }
} }
@ -518,6 +519,7 @@ mod test {
grind: 0, grind: 0,
attrs: [None, None, None], attrs: [None, None, None],
tekked: true, tekked: true,
kills: None,
}))); })));
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly);
@ -527,6 +529,7 @@ mod test {
grind: 2, grind: 2,
attrs: [None, None, None], attrs: [None, None, None],
tekked: true, tekked: true,
kills: None,
}))); })));
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly);
@ -536,6 +539,7 @@ mod test {
grind: 0, grind: 0,
attrs: [None, None, None], attrs: [None, None, None],
tekked: false, tekked: false,
kills: None,
}))); })));
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly);
@ -545,6 +549,7 @@ mod test {
grind: 0, grind: 0,
attrs: [Some(WeaponAttribute {attr: Attribute::ABeast, value: 30}), Some(WeaponAttribute {attr: Attribute::Dark, value: 30}), None], attrs: [Some(WeaponAttribute {attr: Attribute::ABeast, value: 30}), Some(WeaponAttribute {attr: Attribute::Dark, value: 30}), None],
tekked: true, tekked: true,
kills: None,
}))); })));
} }
} }

222
src/ship/drops/mod.rs Normal file
View File

@ -0,0 +1,222 @@
#![allow(dead_code, unused_must_use)]
// TODO: there is some structure duplication that occurs here:
// the rare and box tables instantiate their own copies of the
// generic drop tables as they need them to apply their modifiers
// to their drops
mod drop_table;
mod rare_drop_table;
mod generic_weapon;
mod generic_armor;
mod generic_shield;
mod generic_unit;
mod tool_table;
mod tech_table;
mod box_drop_table;
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use std::io::Read;
use serde::{Serialize, Deserialize};
use rand::{Rng, SeedableRng};
use crate::ship::monster::MonsterType;
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::generic_weapon::GenericWeaponTable;
use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::ship::drops::generic_shield::GenericShieldTable;
use crate::ship::drops::generic_unit::GenericUnitTable;
use crate::ship::drops::tool_table::ToolTable;
use crate::ship::drops::rare_drop_table::RareDropTable;
use crate::ship::drops::box_drop_table::BoxDropTable;
use crate::ship::map::MapObject;
use crate::entity::item::{weapon, armor, shield, unit, mag, tool, tech};
fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
let mut path = PathBuf::from("data/drops/");
path.push(episode.to_string());
path.push(difficulty.to_string().to_lowercase());
path.push(section_id.to_string().to_lowercase());
path.push(filename);
path
}
pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
let path = data_file_path(episode, difficulty, section_id, filename);
let mut f = File::open(path).unwrap();
let mut s = String::new();
f.read_to_string(&mut s);
toml::from_str::<T>(s.as_str()).unwrap()
}
// this is just copypaste
pub fn load_rare_monster_file<T: serde::de::DeserializeOwned>(episode: Episode) -> T {
// TODO: where does the rare monster toml file actually live
let mut path = PathBuf::from("data/battle_param/");
path.push(episode.to_string().to_lowercase() + "_rare_monster.toml");
let mut f = File::open(path).unwrap();
let mut s = String::new();
f.read_to_string(&mut s);
toml::from_str::<T>(s.as_str()).unwrap()
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub enum MonsterDropType {
#[serde(rename = "weapon")]
Weapon,
#[serde(rename = "armor")]
Armor,
#[serde(rename = "shield")]
Shield,
#[serde(rename = "unit")]
Unit,
#[serde(rename = "none")]
None,
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub struct MonsterDropStats {
pub dar: u32,
pub drop_type: MonsterDropType,
pub min_meseta: u32,
pub max_meseta: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ItemDropType {
Weapon(weapon::Weapon),
Armor(armor::Armor),
Shield(shield::Shield),
Unit(unit::Unit),
Tool(tool::Tool),
//Tools(Vec<tool::Tool>),
TechniqueDisk(tech::TechniqueDisk),
Mag(mag::Mag),
Meseta(u32),
}
#[derive(Clone, Debug)]
pub struct ItemDrop {
pub map_area: MapArea,
pub x: f32,
pub y: f32,
pub z: f32,
pub item: ItemDropType,
}
pub struct DropTable<R: Rng + SeedableRng> {
monster_stats: HashMap<MonsterType, MonsterDropStats>,
rare_table: RareDropTable,
weapon_table: GenericWeaponTable,
armor_table: GenericArmorTable,
shield_table: GenericShieldTable,
unit_table: GenericUnitTable,
tool_table: ToolTable,
box_table: BoxDropTable,
rng: R,
}
impl<R: Rng + SeedableRng> DropTable<R> {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
DropTable {
monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
rare_table: RareDropTable::new(episode, difficulty, section_id),
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
armor_table: GenericArmorTable::new(episode, difficulty, section_id),
shield_table: GenericShieldTable::new(episode, difficulty, section_id),
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(),
}
}
fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option<ItemDropType> {
Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1)))
}
fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
match monster.drop_type {
MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng),
MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng),
MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng),
MonsterDropType::None => None,
}
}
pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
let monster_stat = *self.monster_stats.get(monster)?;
let drop_anything = self.rng.gen_range(0, 100);
if drop_anything > monster_stat.dar {
return None;
}
if let Some(item) = self.rare_table.get_drop(map_area, monster, &mut self.rng) {
return Some(item);
}
let drop_type = self.rng.gen_range(0, 3);
match drop_type {
0 => {
self.generate_meseta(&monster_stat)
},
1 => {
self.tool_table.get_drop(map_area, &mut self.rng)
},
2 => {
self.generate_typed_drop(map_area, &monster_stat)
},
_ => panic!()
}
}
pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
self.box_table.get_drop(map_area, object, &mut self.rng)
}
pub fn get_rare_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
self.rare_table.get_rare_drop(map_area, monster, &mut self.rng)
}
}
#[cfg(test)]
mod test {
use super::*;
use rand::seq::IteratorRandom;
#[test]
fn test_initializing_drop_table() {
let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap();
let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate]
.into_iter().choose(&mut rng).unwrap();
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);
}
#[test]
fn test_sjs_drop() {
let mut drop_table = DropTable::<rand_chacha::ChaCha20Rng>::new(Episode::Two, Difficulty::Ultimate, SectionID::Skyly);
let drop = drop_table.get_rare_drop(&MapArea::Seaside, &MonsterType::GiGue).unwrap();
if let ItemDropType::Weapon(weapon) = drop {
assert!(weapon.weapon == weapon::WeaponType::SealedJSword);
assert!(weapon.kills == Some(0));
}
}
}

View File

@ -1,22 +1,21 @@
use std::collections::HashMap; use std::collections::HashMap;
use rand::Rng; use rand::Rng;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use entity::item::weapon::{Weapon, WeaponType}; use crate::entity::item::weapon::{Weapon, WeaponType};
use entity::item::armor::{Armor, ArmorType}; use crate::entity::item::armor::{Armor, ArmorType};
use entity::item::shield::{Shield, ShieldType}; use crate::entity::item::shield::{Shield, ShieldType};
use entity::item::unit::{Unit, UnitType}; use crate::entity::item::unit::{Unit, UnitType};
use entity::item::tool::{Tool, ToolType}; use crate::entity::item::tool::{Tool, ToolType};
use entity::item::mag::{Mag, MagType}; use crate::entity::item::mag::{Mag, MagType};
use entity::character::SectionID; use crate::entity::character::SectionID;
use maps::monster::MonsterType; use crate::ship::monster::MonsterType;
use maps::room::{Difficulty, Episode}; use crate::ship::room::{Difficulty, Episode};
use maps::area::MapArea; use crate::ship::map::MapArea;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use crate::generic_weapon::AttributeTable; use crate::ship::drops::generic_weapon::AttributeTable;
use crate::generic_armor::GenericArmorTable; use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable; use crate::ship::drops::generic_shield::GenericShieldTable;
type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum RareDropItem { pub enum RareDropItem {
@ -30,7 +29,7 @@ pub enum RareDropItem {
impl RareDropItem { impl RareDropItem {
pub fn from_string(name: String) -> RareDropItem { pub fn from_string(name: String) -> RareDropItem {
let parse_funcs: [ItemParseFn; 6] = [ let parse_funcs: [Box<dyn Fn(&String) -> Option<RareDropItem>>; 6] = [
Box::new(|i| Some(RareDropItem::Weapon(str::parse::<WeaponType>(i).ok()?))), Box::new(|i| Some(RareDropItem::Weapon(str::parse::<WeaponType>(i).ok()?))),
Box::new(|i| Some(RareDropItem::Armor(str::parse::<ArmorType>(i).ok()?))), Box::new(|i| Some(RareDropItem::Armor(str::parse::<ArmorType>(i).ok()?))),
Box::new(|i| Some(RareDropItem::Shield(str::parse::<ShieldType>(i).ok()?))), Box::new(|i| Some(RareDropItem::Shield(str::parse::<ShieldType>(i).ok()?))),
@ -50,9 +49,9 @@ impl RareDropItem {
} }
pub struct RareDropRate { struct RareDropRate {
pub rate: f32, rate: f32,
pub item: RareDropItem item: RareDropItem
} }
@ -71,52 +70,46 @@ pub struct RareDropTable {
shield_stats: GenericShieldTable, shield_stats: GenericShieldTable,
} }
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 { impl RareDropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> 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)
}).collect();
RareDropTable { RareDropTable {
rates: load_default_monster_rates(episode, difficulty, section_id), rates,
attribute_table: AttributeTable::new(episode, difficulty, section_id), attribute_table: AttributeTable::new(episode, difficulty, section_id),
armor_stats: GenericArmorTable::new(episode, difficulty, section_id), armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
shield_stats: GenericShieldTable::new(episode, difficulty, section_id), shield_stats: GenericShieldTable::new(episode, difficulty, section_id),
} }
} }
pub fn builder() -> RareDropTableBuilder {
RareDropTableBuilder {
rates: None,
attribute_table: None,
armor_stats: None,
shield_stats: None,
}
}
pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType { pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
match item { match item {
RareDropItem::Weapon(weapon) => { RareDropItem::Weapon(weapon) => {
ItemDropType::Weapon(Weapon { let mut dropped_weapon = Weapon {
weapon, weapon,
special: None, special: None,
grind: 0, grind: 0,
attrs: self.attribute_table.generate_rare_attributes(map_area, rng), attrs: self.attribute_table.generate_rare_attributes(map_area, rng),
tekked: false, tekked: false,
}) kills: None,
};
if dropped_weapon.weapon.has_counter() {
dropped_weapon.attrs[2] = None;
dropped_weapon.kills = Some(0);
};
ItemDropType::Weapon(dropped_weapon)
}, },
RareDropItem::Armor(armor) => { RareDropItem::Armor(armor) => {
ItemDropType::Armor(Armor { ItemDropType::Armor(Armor {
@ -137,6 +130,13 @@ impl RareDropTable {
ItemDropType::Unit(Unit { ItemDropType::Unit(Unit {
unit, unit,
modifier: None, modifier: None,
kills: {
if unit.has_counter() {
Some(0)
} else {
None
}
},
}) })
}, },
RareDropItem::Tool(tool) => { RareDropItem::Tool(tool) => {
@ -165,47 +165,14 @@ impl RareDropTable {
}).next() }).next()
}) })
} }
}
pub fn get_rare_drop<R: Rng>(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option<ItemDropType> {
pub struct RareDropTableBuilder { self.rates.get(monster)
rates: Option<HashMap<MonsterType, Vec<RareDropRate>>>, .and_then(|drop_rates| {
attribute_table: Option<AttributeTable>, drop_rates.iter()
armor_stats: Option<GenericArmorTable>, .map(|drop_rate| {
shield_stats: Option<GenericShieldTable>, self.apply_item_stats(map_area, drop_rate.item, rng)
} }).next()
})
// 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)),
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More