Compare commits
168 Commits
16a4653360
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bdc5a5ae89 | |||
| 156821cc6e | |||
| b18ab064fa | |||
| c851818813 | |||
| d4913eb6c6 | |||
| 8f9f2b074c | |||
| a71692d62a | |||
| 6caeb0a8d9 | |||
| 96dc459e5e | |||
| 40f5c80e3e | |||
| d30a2bd3a3 | |||
| 8138cb13a2 | |||
| e359501be6 | |||
| d71287ef9a | |||
| d5ddfdb847 | |||
| 95d23fceac | |||
| a7d6d9b5b0 | |||
| aa84768e58 | |||
| 631a0c46c8 | |||
| b42056419a | |||
| 5fc23cd7ca | |||
| 2cede0077a | |||
| 8360e0343e | |||
| ab0a844c8c | |||
| 15c0ac50ee | |||
| c8813866a5 | |||
| 1be61556ab | |||
| 3cd4e9db10 | |||
| d0866f5e83 | |||
| 7b26fdc28d | |||
| af629695a5 | |||
| 183fba9afe | |||
| 7afe44a520 | |||
| 3dc48e9d43 | |||
| 402667d627 | |||
| 2570749fbb | |||
| 9cff5ad088 | |||
| d73a07391b | |||
| 926483b6de | |||
| 5d98fb8cc9 | |||
| 72aa0f7f13 | |||
| 3b28d650ee | |||
| 2c930d4333 | |||
| 6ac3ad50dc | |||
| fc62be05e2 | |||
| 5bf9ef59bf | |||
| fbeaf5e93e | |||
| 622c6e598d | |||
| 16041640c2 | |||
| 1f7dd1eafe | |||
| f5c71ced17 | |||
| aa036ed5d7 | |||
| e989bd939a | |||
| 0c074e45ee | |||
| 99baa03582 | |||
| 23d784e2af | |||
| 044e36d37a | |||
| a00a7d7191 | |||
| 455ce9316b | |||
| 318aeeba2b | |||
| 3ba0cc9c55 | |||
| 580bed3cb0 | |||
| 0f0adb05a3 | |||
| 3749155a7d | |||
| b012692505 | |||
| 00bc001297 | |||
| 602b6e96f7 | |||
| e8494c5bbd | |||
| 4a6b2dfb8f | |||
| 414672252c | |||
| f8943d5bd2 | |||
| 4e4f5e4ee2 | |||
| 1028120048 | |||
| b8f05a0ea5 | |||
| 7c3acc8b3d | |||
| a7c1968a48 | |||
| 1bd88fdaaf | |||
| 9f91ada947 | |||
| fc5d318ac3 | |||
| 8b4eb146ac | |||
| 0bf84db189 | |||
| ddab0ab6a3 | |||
| a63e7fe28c | |||
| 818b92b962 | |||
| d16b2418f3 | |||
| 960e9f367e | |||
| effc82d782 | |||
| 45996e9d01 | |||
| 77f69a9d23 | |||
| 6399cbbb0e | |||
| 85d9fc9ce3 | |||
| 0d3161e1b4 | |||
| aac2e429ed | |||
| 9123c4842b | |||
| cccf385ee9 | |||
| 52da851d8a | |||
| 28c0073dff | |||
| f27c121539 | |||
| 6a2703ed6c | |||
| f3bfa658cd | |||
| 5b8f4fd087 | |||
| 8f6b562c22 | |||
| 42ef2ebcbb | |||
| f3682d0b82 | |||
| 6ef5ea6681 | |||
| 1fb0abce09 | |||
| 7144ede73f | |||
| 939c511f92 | |||
| aa019d4ea9 | |||
| 8157db4c69 | |||
| 13c6592438 | |||
| 7bd385d580 | |||
| f09c73611f | |||
| d495ec97f2 | |||
| 31ebc1af32 | |||
| fe472eaae2 | |||
| 0b641424da | |||
| f5fea8540e | |||
| f80e37c438 | |||
| 58da1f87f6 | |||
| 220d3e7185 | |||
| bbaf39fa0b | |||
| 33b80d7235 | |||
| b831f9b83b | |||
| e5f13b6cb7 | |||
| 62387366e4 | |||
| 08efcce6f7 | |||
| ab8c5e6688 | |||
| a2686e2be8 | |||
| 43579f7058 | |||
| dfd48e1496 | |||
| 4f8a75f9d7 | |||
| fbab8fe5e8 | |||
| dbf73acb8d | |||
| 4c2fa8600a | |||
| db66019f2c | |||
| 2483d2242b | |||
| 2959181873 | |||
| 38070c9dfd | |||
| 40046ae991 | |||
| 5c34612afe | |||
| 0f876d25bb | |||
| e0e68b9ad7 | |||
| e6e080004f | |||
| 32dddfd9bd | |||
| d671a8ae42 | |||
| 6ced59a730 | |||
| 8700987986 | |||
| ed9f951bca | |||
| b42deeede1 | |||
| 6b9a45fb70 | |||
| 4a38d94dbb | |||
| ad694b695e | |||
| 6f0fa05e3b | |||
| 4dc814d244 | |||
| 192ff967e6 | |||
| 4b1ded6f7d | |||
| 9843274bd8 | |||
| 3a50cd7ba1 | |||
| fdce44cdd8 | |||
| 27931adb5a | |||
| 58c26301bf | |||
| 2d880d8cf0 | |||
| 36261663be | |||
| 91a92870dc | |||
| 1f5e94ccfb | |||
| bbc1919309 | |||
| e5ff75e367 |
16
.drone.yml
16
.drone.yml
@ -1,12 +1,24 @@
|
|||||||
---
|
---
|
||||||
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:
|
||||||
@ -33,7 +45,7 @@ steps:
|
|||||||
- name: target-cache
|
- name: target-cache
|
||||||
path: /drone/src/target
|
path: /drone/src/target
|
||||||
commands:
|
commands:
|
||||||
- cargo test
|
- cargo test --jobs 1
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: cache
|
- name: cache
|
||||||
|
|||||||
2411
Cargo.lock
generated
2411
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
77
Cargo.toml
77
Cargo.toml
@ -2,10 +2,48 @@
|
|||||||
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 = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
|
[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"
|
||||||
@ -22,15 +60,38 @@ 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.15"
|
thiserror = "1.0.37"
|
||||||
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.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
|
sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
|
||||||
strum = "0.19.5"
|
strum = "0.19.5"
|
||||||
strum_macros = "0.19"
|
strum_macros = "0.19"
|
||||||
anyhow = { version = "1.0.47", features = ["backtrace"] }
|
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
||||||
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 }
|
||||||
@ -11,7 +11,7 @@ photon_blast = "Pilla"
|
|||||||
|
|
||||||
[Surya]
|
[Surya]
|
||||||
feed_table = 3
|
feed_table = 3
|
||||||
photon_blast = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Golla"
|
photon_blast = "Leilla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[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 = "Leilla"
|
photon_blast = "Golla"
|
||||||
|
|
||||||
[Madhu]
|
[Madhu]
|
||||||
feed_table = 6
|
feed_table = 6
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
use log::{info};
|
use log::{info};
|
||||||
use elseware::entity::gateway::postgres::PostgresGateway;
|
use entity::gateway::postgres::PostgresGateway;
|
||||||
use elseware::login::login::LoginServerState;
|
use login_server::login::LoginServerState;
|
||||||
use elseware::login::character::CharacterServerState;
|
use login_server::character::CharacterServerState;
|
||||||
use elseware::common::mainloop::{login_mainloop, character_mainloop};
|
use networking::interserver::AuthToken;
|
||||||
use elseware::common::interserver::AuthToken;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let colors = fern::colors::ColoredLevelConfig::new()
|
let colors = fern::colors::ColoredLevelConfig::new()
|
||||||
@ -37,15 +36,23 @@ 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 thread_entity_gateway = entity_gateway.clone();
|
let login_state = LoginServerState::new(entity_gateway.clone(), charserv_ip);
|
||||||
let login_state = LoginServerState::new(thread_entity_gateway, charserv_ip);
|
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 char_state = CharacterServerState::new(entity_gateway, AuthToken(shipgate_token));
|
let char_state = CharacterServerState::new(entity_gateway, AuthToken(shipgate_token));
|
||||||
let character_loop = character_mainloop(char_state, elseware::login::character::CHARACTER_PORT, elseware::login::login::COMMUNICATION_PORT);
|
let sub_char_state = char_state.clone();
|
||||||
|
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]).await
|
futures::future::join_all(vec![login_loop, character_loop, inter_character_loop]).await
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
128
src/bin/main.rs
128
src/bin/main.rs
@ -1,19 +1,18 @@
|
|||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use log::{info};
|
use log::{info};
|
||||||
|
|
||||||
use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd};
|
use networking::interserver::AuthToken;
|
||||||
use elseware::login::login::LoginServerState;
|
use login_server::login::LoginServerState;
|
||||||
use elseware::login::character::CharacterServerState;
|
use login_server::character::CharacterServerState;
|
||||||
use elseware::ship::ship::ShipServerStateBuilder;
|
use patch_server::{PatchServerState, generate_patch_tree, load_config, load_motd};
|
||||||
use elseware::entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
|
use ship_server::ShipServerStateBuilder;
|
||||||
#[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 elseware::entity::item;
|
use maps::Holiday;
|
||||||
use elseware::common::mainloop::*;
|
use entity::gateway::{EntityGateway, InMemoryGateway};
|
||||||
|
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()
|
||||||
@ -53,7 +52,7 @@ fn main() {
|
|||||||
|
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
let fake_user = NewUserAccountEntity {
|
let fake_user = NewUserAccountEntity {
|
||||||
email: format!("fake{}@email.com", i),
|
email: format!("fake{i}@email.com"),
|
||||||
username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) },
|
username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) },
|
||||||
password: bcrypt::hash("qwer", 5).unwrap(),
|
password: bcrypt::hash("qwer", 5).unwrap(),
|
||||||
guildcard: i + 1,
|
guildcard: i + 1,
|
||||||
@ -65,18 +64,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, 1);
|
let mut character = NewCharacterEntity::new(fake_user.id);
|
||||||
character.name = format!("Test Char {}", i*2);
|
character.name = format!("Test Char {}", i*2);
|
||||||
let character = entity_gateway.create_character(character).await.unwrap();
|
let character = entity_gateway.create_character(character).await.unwrap();
|
||||||
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
|
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
|
||||||
entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
|
entity_gateway.set_bank_meseta(&character.id, &item::BankIdentifier::Character, item::Meseta(999999)).await.unwrap();
|
||||||
let mut character = NewCharacterEntity::new(fake_user.id, 1);
|
let mut character = NewCharacterEntity::new(fake_user.id);
|
||||||
character.slot = 2;
|
character.slot = 2;
|
||||||
character.name = "ItemRefactor".into();
|
character.name = "ItemRefactor".into();
|
||||||
character.exp = 80000000;
|
character.exp = 80000000;
|
||||||
let character = entity_gateway.create_character(character).await.unwrap();
|
let character = entity_gateway.create_character(character).await.unwrap();
|
||||||
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
|
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
|
||||||
entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
|
entity_gateway.set_bank_meseta(&character.id, &item::BankIdentifier::Character, item::Meseta(999999)).await.unwrap();
|
||||||
|
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
entity_gateway.create_item(
|
entity_gateway.create_item(
|
||||||
@ -325,56 +324,91 @@ 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(), 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()]);
|
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()]);
|
||||||
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::BankName("".into())).await.unwrap();
|
entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), &item::BankIdentifier::Character).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 = Box::new(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 {
|
||||||
|
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 = Box::new(LoginServerState::new(thread_entity_gateway, "127.0.0.1".parse().unwrap()));
|
let login_state = LoginServerState::new(entity_gateway.clone(), "127.0.0.1".parse().unwrap());
|
||||||
let login_loop = login_mainloop(*login_state, elseware::login::login::LOGIN_PORT);
|
let login_loop = async_std::task::spawn(async move {
|
||||||
|
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 = Box::new(CharacterServerState::new(thread_entity_gateway, AuthToken("".into())));
|
let char_state = CharacterServerState::new(entity_gateway.clone(), AuthToken("".into()));
|
||||||
let character_loop = character_mainloop(*char_state, elseware::login::character::CHARACTER_PORT, elseware::login::login::COMMUNICATION_PORT);
|
let sub_char_state = char_state.clone();
|
||||||
|
let character_loop = async_std::task::spawn(async move {
|
||||||
|
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
|
||||||
|
});
|
||||||
|
|
||||||
let thread_entity_gateway = entity_gateway.clone();
|
let sub_char_state = char_state.clone();
|
||||||
info!("[ship] starting server");
|
let inter_character_loop = async_std::task::spawn(async move {
|
||||||
let ship_state = Box::new(ShipServerStateBuilder::default()
|
networking::mainloop::run_interserver_listen(sub_char_state, login_server::login::COMMUNICATION_PORT).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
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(elseware::ship::ship::SHIP_PORT)
|
.port(ship_server::SHIP_PORT)
|
||||||
.gateway(thread_entity_gateway)
|
.event(Holiday::Halloween)
|
||||||
.build());
|
.gateway(entity_gateway.clone())
|
||||||
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);
|
.build();
|
||||||
|
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 thread_entity_gateway = entity_gateway.clone();
|
let ship_state = ShipServerStateBuilder::default()
|
||||||
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(elseware::ship::ship::SHIP_PORT+2000)
|
.port(ship_server::SHIP_PORT+2000)
|
||||||
.gateway(thread_entity_gateway)
|
.event(Holiday::Christmas)
|
||||||
.build());
|
.gateway(entity_gateway.clone())
|
||||||
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);
|
.build();
|
||||||
|
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 thread_entity_gateway = entity_gateway.clone();
|
let ship_state = ShipServerStateBuilder::default()
|
||||||
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(elseware::ship::ship::SHIP_PORT+3000)
|
.port(ship_server::SHIP_PORT+3000)
|
||||||
.gateway(thread_entity_gateway)
|
.gateway(entity_gateway.clone())
|
||||||
.build());
|
.build();
|
||||||
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 sub_ship_state = ship_state.clone();
|
||||||
|
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, ship_loop, ship_loop2, ship_loop3]).await;
|
futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop,
|
||||||
|
ship_loop1, ship_loop2, ship_loop3,
|
||||||
|
inter_ship_loop1, inter_ship_loop2, inter_ship_loop3]).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config_env, load_motd};
|
use patch_server::{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");
|
||||||
@ -8,9 +7,10 @@ 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);
|
|
||||||
|
|
||||||
async_std::task::block_on(async move {
|
let patch_loop = async_std::task::spawn(async move {
|
||||||
patch_loop.await
|
networking::mainloop::run_server(patch_state, patch_config.port).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async_std::task::block_on(patch_loop);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use log::{info};
|
use log::info;
|
||||||
use elseware::entity::gateway::postgres::PostgresGateway;
|
use entity::gateway::postgres::PostgresGateway;
|
||||||
use elseware::ship::ship::ShipServerStateBuilder;
|
use ship_server::ShipServerStateBuilder;
|
||||||
use elseware::common::mainloop::ship_mainloop;
|
use networking::interserver::AuthToken;
|
||||||
use elseware::common::interserver::AuthToken;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let colors = fern::colors::ColoredLevelConfig::new()
|
let colors = fern::colors::ColoredLevelConfig::new()
|
||||||
@ -37,19 +36,27 @@ 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(elseware::ship::ship::SHIP_PORT)
|
.port(ship_server::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 {
|
||||||
ship_loop.await
|
futures::future::join_all(vec![ship_loop, inter_ship_loop]).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/client/Cargo.toml
Normal file
20
src/client/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[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 }
|
||||||
174
src/client/src/lib.rs
Normal file
174
src/client/src/lib.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1,317 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,260 +0,0 @@
|
|||||||
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 + Clone + '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 + Clone + '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
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
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 + Clone + '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 + Clone + '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 + Clone + '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(|_| ()))
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
17
src/drops/Cargo.toml
Normal file
17
src/drops/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[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 }
|
||||||
@ -2,18 +2,17 @@
|
|||||||
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 crate::entity::character::SectionID;
|
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::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_file};
|
||||||
use crate::ship::map::{MapObject, MapObjectType, FixedBoxDropType};
|
use maps::object::{MapObject, MapObjectType, FixedBoxDropType};
|
||||||
use crate::ship::drops::rare_drop_table::{RareDropTable, RareDropItem};
|
use crate::rare_drop_table::{RareDropTable, RareDropItem};
|
||||||
use crate::ship::drops::generic_weapon::GenericWeaponTable;
|
use crate::generic_weapon::GenericWeaponTable;
|
||||||
use crate::ship::drops::generic_armor::GenericArmorTable;
|
use crate::generic_armor::GenericArmorTable;
|
||||||
use crate::ship::drops::generic_shield::GenericShieldTable;
|
use crate::generic_shield::GenericShieldTable;
|
||||||
use crate::ship::drops::generic_unit::GenericUnitTable;
|
use crate::generic_unit::GenericUnitTable;
|
||||||
use crate::ship::drops::tool_table::ToolTable;
|
use crate::tool_table::ToolTable;
|
||||||
use crate::entity::item::ItemDetail;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct BoxDropRate {
|
struct BoxDropRate {
|
||||||
@ -176,8 +175,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),
|
||||||
@ -204,7 +203,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));
|
||||||
ItemDetail::parse_item_from_bytes(buf)
|
ItemDropType::parse_item_from_bytes(buf)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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 crate::entity::item::armor::{ArmorType, Armor};
|
use entity::character::SectionID;
|
||||||
use crate::ship::room::{Difficulty, Episode};
|
use entity::item::armor::{ArmorType, Armor};
|
||||||
use crate::ship::map::MapArea;
|
use maps::room::{Difficulty, Episode};
|
||||||
use crate::entity::character::SectionID;
|
use maps::area::MapArea;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_file};
|
||||||
use crate::ship::item_stats::{armor_stats, ArmorStats};
|
use stats::items::{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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 crate::entity::item::shield::{ShieldType, Shield};
|
use entity::item::shield::{ShieldType, Shield};
|
||||||
use crate::ship::room::{Difficulty, Episode};
|
use entity::character::SectionID;
|
||||||
use crate::ship::map::MapArea;
|
use maps::room::{Difficulty, Episode};
|
||||||
use crate::entity::character::SectionID;
|
use maps::area::MapArea;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_file};
|
||||||
use crate::ship::item_stats::{shield_stats, ShieldStats};
|
use stats::items::{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 {
|
||||||
@ -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 crate::entity::item::unit::{UnitType, Unit, UnitModifier};
|
use entity::character::SectionID;
|
||||||
use crate::ship::room::{Difficulty, Episode};
|
use entity::item::unit::{UnitType, Unit, UnitModifier};
|
||||||
use crate::ship::map::MapArea;
|
use maps::room::{Difficulty, Episode};
|
||||||
use crate::entity::character::SectionID;
|
use maps::area::MapArea;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_file};
|
||||||
use crate::ship::item_stats::{unit_stats, UnitStats};
|
use stats::items::{unit_stats, UnitStats};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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 crate::entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
|
use entity::character::SectionID;
|
||||||
use crate::ship::room::{Difficulty, Episode};
|
use entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
|
||||||
use crate::ship::map::MapArea;
|
use maps::room::{Difficulty, Episode};
|
||||||
use crate::entity::character::SectionID;
|
use maps::area::MapArea;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{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)
|
||||||
}
|
}
|
||||||
|
|
||||||
298
src/drops/src/lib.rs
Normal file
298
src/drops/src/lib.rs
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
#![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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +1,22 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::entity::item::weapon::{Weapon, WeaponType};
|
use entity::item::weapon::{Weapon, WeaponType};
|
||||||
use crate::entity::item::armor::{Armor, ArmorType};
|
use entity::item::armor::{Armor, ArmorType};
|
||||||
use crate::entity::item::shield::{Shield, ShieldType};
|
use entity::item::shield::{Shield, ShieldType};
|
||||||
use crate::entity::item::unit::{Unit, UnitType};
|
use entity::item::unit::{Unit, UnitType};
|
||||||
use crate::entity::item::tool::{Tool, ToolType};
|
use entity::item::tool::{Tool, ToolType};
|
||||||
use crate::entity::item::mag::{Mag, MagType};
|
use entity::item::mag::{Mag, MagType};
|
||||||
use crate::entity::character::SectionID;
|
use entity::character::SectionID;
|
||||||
use crate::ship::monster::MonsterType;
|
use maps::monster::MonsterType;
|
||||||
use crate::ship::room::{Difficulty, Episode};
|
use maps::room::{Difficulty, Episode};
|
||||||
use crate::ship::map::MapArea;
|
use maps::area::MapArea;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_file};
|
||||||
use crate::ship::drops::generic_weapon::AttributeTable;
|
use crate::generic_weapon::AttributeTable;
|
||||||
use crate::ship::drops::generic_armor::GenericArmorTable;
|
use crate::generic_armor::GenericArmorTable;
|
||||||
use crate::ship::drops::generic_shield::GenericShieldTable;
|
use crate::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 {
|
||||||
@ -29,7 +30,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: [Box<dyn Fn(&String) -> Option<RareDropItem>>; 6] = [
|
let parse_funcs: [ItemParseFn; 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()?))),
|
||||||
@ -49,9 +50,9 @@ impl RareDropItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct RareDropRate {
|
pub struct RareDropRate {
|
||||||
rate: f32,
|
pub rate: f32,
|
||||||
item: RareDropItem
|
pub item: RareDropItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,30 +71,41 @@ 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,
|
rates: load_default_monster_rates(episode, difficulty, section_id),
|
||||||
attribute_table: AttributeTable::new(episode, difficulty, section_id),
|
attribute_table: AttributeTable::new(episode, difficulty, section_id),
|
||||||
armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
|
armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
|
||||||
shield_stats: GenericShieldTable::new(episode, difficulty, section_id),
|
shield_stats: GenericShieldTable::new(episode, difficulty, section_id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn builder() -> RareDropTableBuilder {
|
||||||
|
RareDropTableBuilder {
|
||||||
|
rates: None,
|
||||||
|
attribute_table: None,
|
||||||
|
armor_stats: None,
|
||||||
|
shield_stats: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
|
pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
|
||||||
match item {
|
match item {
|
||||||
RareDropItem::Weapon(weapon) => {
|
RareDropItem::Weapon(weapon) => {
|
||||||
@ -154,3 +166,46 @@ impl RareDropTable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct RareDropTableBuilder {
|
||||||
|
rates: Option<HashMap<MonsterType, Vec<RareDropRate>>>,
|
||||||
|
attribute_table: Option<AttributeTable>,
|
||||||
|
armor_stats: Option<GenericArmorTable>,
|
||||||
|
shield_stats: Option<GenericShieldTable>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: add the rest of these later I just need these ones right now
|
||||||
|
impl RareDropTableBuilder {
|
||||||
|
pub fn rates(mut self, rates: HashMap<MonsterType, Vec<RareDropRate>>) -> RareDropTableBuilder {
|
||||||
|
self.rates = Some(rates);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn rate(mut self, monster_type: MonsterType, drop_rate: RareDropRate) -> RareDropTableBuilder {
|
||||||
|
match &mut self.rates {
|
||||||
|
Some(rates) => {
|
||||||
|
rates.entry(monster_type)
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(drop_rate);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let mut rates = HashMap::default();
|
||||||
|
rates.insert(monster_type, vec![drop_rate]);
|
||||||
|
self.rates = Some(rates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
|
||||||
|
RareDropTable {
|
||||||
|
rates: self.rates.unwrap_or_else(|| load_default_monster_rates(episode, difficulty, section_id)),
|
||||||
|
attribute_table: self.attribute_table.unwrap_or_else(|| AttributeTable::new(episode, difficulty, section_id)),
|
||||||
|
armor_stats: self.armor_stats.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)),
|
||||||
|
shield_stats: self.shield_stats.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,11 +3,11 @@ use serde::{Serialize, Deserialize};
|
|||||||
use rand::{Rng};
|
use rand::{Rng};
|
||||||
use rand::distributions::{WeightedIndex, Distribution};
|
use rand::distributions::{WeightedIndex, Distribution};
|
||||||
|
|
||||||
use crate::entity::item::tech::{Technique, TechniqueDisk};
|
use entity::item::tech::{Technique, TechniqueDisk};
|
||||||
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 entity::character::SectionID;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_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::distributions::{WeightedIndex, Distribution};
|
use rand::distributions::{WeightedIndex, Distribution};
|
||||||
|
|
||||||
use crate::entity::item::tool::{Tool, ToolType};
|
use entity::item::tool::{Tool, ToolType};
|
||||||
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 entity::character::SectionID;
|
||||||
use crate::ship::drops::{ItemDropType, load_data_file};
|
use crate::{ItemDropType, load_data_file};
|
||||||
use crate::ship::drops::tech_table::TechniqueTable;
|
use crate::tech_table::TechniqueTable;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, enum_utils::FromStr)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, enum_utils::FromStr)]
|
||||||
23
src/entity/Cargo.toml
Normal file
23
src/entity/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[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 }
|
||||||
@ -1,15 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
create table trades (
|
|
||||||
id serial primary key not null,
|
|
||||||
character1 integer references character (id) not null,
|
|
||||||
character2 integer references character (id) not null,
|
|
||||||
);
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
use refinery::include_migration_mods;
|
|
||||||
|
|
||||||
include_migration_mods!("src/entity/gateway/postgres/migrations");
|
|
||||||
@ -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, KeyboardConfig, GamepadConfig};
|
use libpso::packet::ship::{UpdateConfig, WriteInfoboard};
|
||||||
use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU, DEFAULT_KEYBOARD_CONFIG1, DEFAULT_KEYBOARD_CONFIG2, DEFAULT_KEYBOARD_CONFIG3, DEFAULT_KEYBOARD_CONFIG4, DEFAULT_GAMEPAD_CONFIG};
|
use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU};
|
||||||
use crate::entity::item::tech::Technique;
|
use crate::item::tech::Technique;
|
||||||
use crate::entity::account::UserAccountId;
|
use crate::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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct TechLevel(pub u8);
|
pub struct TechLevel(pub u8);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@ -167,16 +167,14 @@ pub struct CharacterTechniques {
|
|||||||
|
|
||||||
impl CharacterTechniques {
|
impl CharacterTechniques {
|
||||||
pub fn set_tech(&mut self, tech: Technique, level: TechLevel) {
|
pub fn set_tech(&mut self, tech: Technique, level: TechLevel) {
|
||||||
self.techs.insert(tech, TechLevel(level.0 - 1));
|
self.techs.insert(tech, TechLevel(level.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// from_bytes
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> [u8; 20] {
|
pub fn as_bytes(&self) -> [u8; 20] {
|
||||||
self.techs.iter()
|
self.techs.iter()
|
||||||
.fold([0xFF; 20], |mut techlist, (tech, level)| {
|
.fold([0xFF; 20], |mut techlist, (tech, level)| {
|
||||||
let index = tech.as_value();
|
let index = tech.as_value();
|
||||||
techlist[index as usize] = level.0;
|
techlist[index as usize] = level.0 - 1;
|
||||||
techlist
|
techlist
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -225,7 +223,7 @@ impl CharacterInfoboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_infoboard(&mut self, new_board: &WriteInfoboard) {
|
pub fn update_infoboard(&mut self, new_board: &WriteInfoboard) {
|
||||||
self.board = libpso::utf8_to_utf16_array!(new_board.message, 172);
|
self.board = libpso::util::utf8_to_utf16_array(&new_board.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,82 +262,6 @@ pub struct CharacterMaterials {
|
|||||||
pub tp: u32,
|
pub tp: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CharacterKeyboardConfig {
|
|
||||||
pub keyboard_config: [u8; 0x16C],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CharacterKeyboardConfig {
|
|
||||||
fn default() -> CharacterKeyboardConfig {
|
|
||||||
CharacterKeyboardConfig {
|
|
||||||
keyboard_config: DEFAULT_KEYBOARD_CONFIG1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CharacterKeyboardConfig {
|
|
||||||
fn new(preset: usize) -> CharacterKeyboardConfig {
|
|
||||||
match preset {
|
|
||||||
1 => {
|
|
||||||
CharacterKeyboardConfig {
|
|
||||||
keyboard_config: DEFAULT_KEYBOARD_CONFIG1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
2 => {
|
|
||||||
CharacterKeyboardConfig {
|
|
||||||
keyboard_config: DEFAULT_KEYBOARD_CONFIG2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
3 => {
|
|
||||||
CharacterKeyboardConfig {
|
|
||||||
keyboard_config: DEFAULT_KEYBOARD_CONFIG3,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
4 => {
|
|
||||||
CharacterKeyboardConfig {
|
|
||||||
keyboard_config: DEFAULT_KEYBOARD_CONFIG4,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
CharacterKeyboardConfig {
|
|
||||||
keyboard_config: DEFAULT_KEYBOARD_CONFIG1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, new_config: &KeyboardConfig) {
|
|
||||||
self.keyboard_config = new_config.keyboard_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> [u8; 0x16C] {
|
|
||||||
self.keyboard_config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CharacterGamepadConfig {
|
|
||||||
pub gamepad_config: [u8; 0x38],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CharacterGamepadConfig {
|
|
||||||
fn default() -> CharacterGamepadConfig {
|
|
||||||
CharacterGamepadConfig {
|
|
||||||
gamepad_config: DEFAULT_GAMEPAD_CONFIG,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CharacterGamepadConfig {
|
|
||||||
pub fn update(&mut self, new_config: &GamepadConfig) {
|
|
||||||
self.gamepad_config = new_config.gamepad_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> [u8; 0x38] {
|
|
||||||
self.gamepad_config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, derive_more::Display)]
|
||||||
pub struct CharacterEntityId(pub u32);
|
pub struct CharacterEntityId(pub u32);
|
||||||
|
|
||||||
@ -363,12 +285,10 @@ pub struct NewCharacterEntity {
|
|||||||
|
|
||||||
pub tech_menu: CharacterTechMenu,
|
pub tech_menu: CharacterTechMenu,
|
||||||
pub option_flags: u32,
|
pub option_flags: u32,
|
||||||
pub keyboard_config: CharacterKeyboardConfig,
|
|
||||||
pub gamepad_config: CharacterGamepadConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewCharacterEntity {
|
impl NewCharacterEntity {
|
||||||
pub fn new(user: UserAccountId, keyboard_config_preset: usize,) -> NewCharacterEntity {
|
pub fn new(user: UserAccountId) -> NewCharacterEntity {
|
||||||
NewCharacterEntity {
|
NewCharacterEntity {
|
||||||
user_id: user,
|
user_id: user,
|
||||||
slot: 0,
|
slot: 0,
|
||||||
@ -384,8 +304,6 @@ impl NewCharacterEntity {
|
|||||||
materials: CharacterMaterials::default(),
|
materials: CharacterMaterials::default(),
|
||||||
tech_menu: CharacterTechMenu::default(),
|
tech_menu: CharacterTechMenu::default(),
|
||||||
option_flags: 0,
|
option_flags: 0,
|
||||||
keyboard_config: CharacterKeyboardConfig::new(keyboard_config_preset),
|
|
||||||
gamepad_config: CharacterGamepadConfig::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,8 +329,6 @@ pub struct CharacterEntity {
|
|||||||
|
|
||||||
pub tech_menu: CharacterTechMenu,
|
pub tech_menu: CharacterTechMenu,
|
||||||
pub option_flags: u32,
|
pub option_flags: u32,
|
||||||
pub keyboard_config: CharacterKeyboardConfig,
|
|
||||||
pub gamepad_config: CharacterGamepadConfig,
|
|
||||||
|
|
||||||
pub playtime: u32,
|
pub playtime: u32,
|
||||||
}
|
}
|
||||||
@ -1,35 +1,31 @@
|
|||||||
use std::convert::From;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use futures::Future;
|
use futures::future::{Future, BoxFuture};
|
||||||
|
|
||||||
use crate::entity::account::*;
|
use crate::account::*;
|
||||||
use crate::entity::character::*;
|
use crate::character::*;
|
||||||
use crate::entity::item::*;
|
use crate::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 {
|
||||||
async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
|
type Transaction<'t>: EntityGatewayTransaction + Clone where Self: 't;
|
||||||
{
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result<R, E>
|
fn with_transaction<'a, F, Fut, R>(&'a mut self, _func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
|
||||||
where
|
where
|
||||||
Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
|
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
|
||||||
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
|
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
|
||||||
R: Send,
|
R: Send,
|
||||||
E: From<GatewayError>,
|
|
||||||
Self: Sized
|
Self: Sized
|
||||||
{
|
{
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
@ -100,7 +96,7 @@ 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!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +104,7 @@ pub trait EntityGateway: Send + Sync {
|
|||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
|
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +112,7 @@ pub trait EntityGateway: Send + Sync {
|
|||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _inventory: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> {
|
async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _bank: &BankEntity, _bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,11 +132,11 @@ pub trait EntityGateway: Send + Sync {
|
|||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName) -> Result<Meseta, GatewayError> {
|
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName, _amount: Meseta) -> Result<(), GatewayError> {
|
async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier, _amount: Meseta) -> Result<(), GatewayError> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,16 +147,26 @@ pub trait EntityGateway: Send + Sync {
|
|||||||
async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> {
|
async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> {
|
||||||
unimplemented!();
|
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 {
|
pub trait EntityGatewayTransaction: Send + Sync + Sized {
|
||||||
fn gateway(&mut self) -> &mut dyn EntityGateway {
|
type ParentGateway: EntityGateway + Clone;
|
||||||
|
|
||||||
|
fn gateway(&mut self) -> &mut Self::ParentGateway {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit(self: Box<Self>) -> Result<(), GatewayError> {
|
async fn commit(self) -> Result<(), GatewayError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,18 +1,19 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use futures::Future;
|
use futures::future::{Future, BoxFuture};
|
||||||
|
|
||||||
use crate::entity::account::*;
|
use crate::account::*;
|
||||||
use crate::entity::character::*;
|
use crate::character::*;
|
||||||
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
||||||
use crate::entity::item::*;
|
use crate::item::*;
|
||||||
|
use crate::room::*;
|
||||||
|
|
||||||
use async_std::sync::{Arc, Mutex};
|
use async_std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
// TODO: implement multiple banks
|
#[derive(Clone)]
|
||||||
pub struct InMemoryGatewayTransaction<'a> {
|
pub struct InMemoryGatewayTransaction {
|
||||||
working_gateway: InMemoryGateway,
|
working_gateway: InMemoryGateway,
|
||||||
original_gateway: &'a mut InMemoryGateway,
|
original_gateway: InMemoryGateway,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -21,8 +22,8 @@ where
|
|||||||
K: Ord + Copy,
|
K: Ord + Copy,
|
||||||
V: Clone,
|
V: Clone,
|
||||||
{
|
{
|
||||||
if !working_table.contains_key(&key) {
|
if let std::collections::btree_map::Entry::Vacant(e) = working_table.entry(key) {
|
||||||
working_table.insert(key, original_table.get(&key)?.clone());
|
e.insert(original_table.get(&key)?.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
@ -30,7 +31,9 @@ where
|
|||||||
|
|
||||||
// functions here have been skipped as they are not used in transactions, add as needed
|
// functions here have been skipped as they are not used in transactions, add as needed
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
impl EntityGateway for InMemoryGatewayTransaction {
|
||||||
|
type Transaction<'t> = InMemoryGatewayTransaction 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> {
|
||||||
self.working_gateway.create_user(user).await
|
self.working_gateway.create_user(user).await
|
||||||
}
|
}
|
||||||
@ -70,6 +73,10 @@ impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
|
||||||
|
self.original_gateway.save_user_settings(settings).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
|
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
|
||||||
copy_if_needed(&mut *self.working_gateway.characters.lock().await,
|
copy_if_needed(&mut *self.working_gateway.characters.lock().await,
|
||||||
&*self.original_gateway.characters.lock().await,
|
&*self.original_gateway.characters.lock().await,
|
||||||
@ -98,7 +105,7 @@ impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
|||||||
self.working_gateway.use_mag_cell(mag_item_id, mag_cell_id).await
|
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> {
|
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
|
self.working_gateway.add_weapon_modifier(item_id, modifier).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,11 +118,11 @@ impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError> {
|
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_name).await {
|
match self.working_gateway.get_character_bank(char_id, bank_identifier).await {
|
||||||
Ok(bank) => Ok(bank),
|
Ok(bank) => Ok(bank),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.original_gateway.get_character_bank(char_id, bank_name).await
|
self.original_gateway.get_character_bank(char_id, bank_identifier).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,11 +131,10 @@ impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
|||||||
self.working_gateway.set_character_inventory(char_id, inventory).await
|
self.working_gateway.set_character_inventory(char_id, inventory).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> {
|
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_name).await
|
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> {
|
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
|
||||||
match self.working_gateway.get_character_equips(char_id).await {
|
match self.working_gateway.get_character_equips(char_id).await {
|
||||||
Ok(equips) => Ok(equips),
|
Ok(equips) => Ok(equips),
|
||||||
@ -155,15 +161,15 @@ impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
|
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, meseta).await
|
self.working_gateway.set_bank_meseta(char_id, bank_identifier, meseta).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
|
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).await {
|
match self.working_gateway.get_bank_meseta(char_id, bank_identifier).await {
|
||||||
Ok(meseta) => Ok(meseta),
|
Ok(meseta) => Ok(meseta),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.original_gateway.get_bank_meseta(char_id, bank).await
|
self.original_gateway.get_bank_meseta(char_id, bank_identifier).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,20 +188,24 @@ impl<'a> EntityGateway for InMemoryGatewayTransaction<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> EntityGatewayTransaction for InMemoryGatewayTransaction<'a> {
|
impl EntityGatewayTransaction for InMemoryGatewayTransaction {
|
||||||
fn gateway(&mut self) -> &mut dyn EntityGateway {
|
type ParentGateway = InMemoryGatewayTransaction;
|
||||||
|
|
||||||
|
fn gateway(&mut self) -> &mut Self::ParentGateway {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit(mut self: Box<Self>) -> Result<(), GatewayError> {
|
async fn commit(mut self) -> Result<(), GatewayError> {
|
||||||
self.original_gateway.users.lock().await.extend(self.working_gateway.users.lock().await.clone());
|
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.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.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.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.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.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.inventories.lock().await.extend(self.working_gateway.inventories.lock().await.clone());
|
||||||
self.original_gateway.banks.lock().await.extend(self.working_gateway.banks.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.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.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.weapon_modifiers.lock().await.extend(self.working_gateway.weapon_modifiers.lock().await.clone());
|
||||||
@ -205,16 +215,24 @@ impl<'a> EntityGatewayTransaction for InMemoryGatewayTransaction<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum InventoryItemElement {
|
||||||
|
Individual(ItemEntityId),
|
||||||
|
Stacked(Vec<ItemEntityId>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct InMemoryGateway {
|
pub struct InMemoryGateway {
|
||||||
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
|
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
|
||||||
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
|
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
|
||||||
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
|
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
|
||||||
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
|
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
|
||||||
bank_meseta: Arc<Mutex<BTreeMap<(CharacterEntityId, BankName), Meseta>>>,
|
bank_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
|
||||||
|
shared_bank_meseta: Arc<Mutex<BTreeMap<(UserAccountId, BankName), Meseta>>>,
|
||||||
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
|
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
|
||||||
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
|
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, Vec<InventoryItemElement>>>>,
|
||||||
banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
|
character_banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
|
||||||
|
shared_banks: Arc<Mutex<BTreeMap<(UserAccountId, BankName), BankEntity>>>,
|
||||||
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
|
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
|
||||||
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
|
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
|
||||||
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
|
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
|
||||||
@ -229,9 +247,11 @@ impl Default for InMemoryGateway {
|
|||||||
characters: Arc::new(Mutex::new(BTreeMap::new())),
|
characters: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
|
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
bank_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())),
|
items: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
inventories: Arc::new(Mutex::new(BTreeMap::new())),
|
inventories: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
banks: 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())),
|
equips: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
@ -298,89 +318,57 @@ fn apply_modifiers(items: &BTreeMap<ItemEntityId, ItemEntity>,
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl EntityGateway for InMemoryGateway {
|
impl EntityGateway for InMemoryGateway {
|
||||||
async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
|
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,
|
||||||
{
|
{
|
||||||
let working_gateway = {
|
Box::pin(async move {
|
||||||
let users = self.users.lock().await.clone();
|
let users = self.users.lock().await.clone();
|
||||||
let user_settings = self.user_settings.lock().await.clone();
|
let user_settings = self.user_settings.lock().await.clone();
|
||||||
let characters = self.characters.lock().await.clone();
|
let characters = self.characters.lock().await.clone();
|
||||||
let character_meseta = self.character_meseta.lock().await.clone();
|
let character_meseta = self.character_meseta.lock().await.clone();
|
||||||
let bank_meseta = self.bank_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 items = self.items.lock().await.clone();
|
||||||
let inventories = self.inventories.lock().await.clone();
|
let inventories = self.inventories.lock().await.clone();
|
||||||
let banks = self.banks.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 equips = self.equips.lock().await.clone();
|
||||||
let mag_modifiers = self.mag_modifiers.lock().await.clone();
|
let mag_modifiers = self.mag_modifiers.lock().await.clone();
|
||||||
let weapon_modifiers = self.weapon_modifiers.lock().await.clone();
|
let weapon_modifiers = self.weapon_modifiers.lock().await.clone();
|
||||||
let trades = self.trades.lock().await.clone();
|
let trades = self.trades.lock().await.clone();
|
||||||
|
|
||||||
InMemoryGateway {
|
let working_gateway = InMemoryGateway {
|
||||||
users: Arc::new(Mutex::new(users)),
|
users: Arc::new(Mutex::new(users)),
|
||||||
user_settings: Arc::new(Mutex::new(user_settings)),
|
user_settings: Arc::new(Mutex::new(user_settings)),
|
||||||
characters: Arc::new(Mutex::new(characters)),
|
characters: Arc::new(Mutex::new(characters)),
|
||||||
character_meseta: Arc::new(Mutex::new(character_meseta)),
|
character_meseta: Arc::new(Mutex::new(character_meseta)),
|
||||||
bank_meseta: Arc::new(Mutex::new(bank_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)),
|
items: Arc::new(Mutex::new(items)),
|
||||||
inventories: Arc::new(Mutex::new(inventories)),
|
inventories: Arc::new(Mutex::new(inventories)),
|
||||||
banks: Arc::new(Mutex::new(banks)),
|
character_banks: Arc::new(Mutex::new(character_banks)),
|
||||||
|
shared_banks: Arc::new(Mutex::new(shared_banks)),
|
||||||
equips: Arc::new(Mutex::new(equips)),
|
equips: Arc::new(Mutex::new(equips)),
|
||||||
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
|
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
|
||||||
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
|
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
|
||||||
trades: Arc::new(Mutex::new(trades)),
|
trades: Arc::new(Mutex::new(trades)),
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Box::new(InMemoryGatewayTransaction {
|
let transaction = InMemoryGatewayTransaction {
|
||||||
working_gateway,
|
working_gateway,
|
||||||
original_gateway: self,
|
original_gateway: self.clone(),
|
||||||
}))
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
let (transaction, result) = func(transaction).await?;
|
||||||
|
|
||||||
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
|
transaction.commit().await?;
|
||||||
where
|
Ok(result)
|
||||||
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().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 items = self.items.lock().await.clone();
|
|
||||||
let inventories = self.inventories.lock().await.clone();
|
|
||||||
let banks = self.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)),
|
|
||||||
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> {
|
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
||||||
@ -452,6 +440,12 @@ impl EntityGateway for InMemoryGateway {
|
|||||||
.ok_or(GatewayError::Error)
|
.ok_or(GatewayError::Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
|
||||||
|
let mut user_settings = self.user_settings.lock().await;
|
||||||
|
user_settings.insert(settings.id, settings.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
|
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
|
||||||
let characters = self.characters.lock().await;
|
let characters = self.characters.lock().await;
|
||||||
const NONE: Option<CharacterEntity> = None;
|
const NONE: Option<CharacterEntity> = None;
|
||||||
@ -486,8 +480,6 @@ impl EntityGateway for InMemoryGateway {
|
|||||||
materials: character.materials,
|
materials: character.materials,
|
||||||
tech_menu: character.tech_menu,
|
tech_menu: character.tech_menu,
|
||||||
option_flags: character.option_flags,
|
option_flags: character.option_flags,
|
||||||
keyboard_config: character.keyboard_config,
|
|
||||||
gamepad_config: character.gamepad_config,
|
|
||||||
playtime: 0,
|
playtime: 0,
|
||||||
};
|
};
|
||||||
characters.insert(new_character.id, new_character.clone());
|
characters.insert(new_character.id, new_character.clone());
|
||||||
@ -548,11 +540,11 @@ impl EntityGateway for InMemoryGateway {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
self.weapon_modifiers.lock().await
|
self.weapon_modifiers.lock().await
|
||||||
.entry(*item_id)
|
.entry(*item_id)
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
.push(modifier);
|
.push(modifier.clone());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,30 +556,106 @@ impl EntityGateway for InMemoryGateway {
|
|||||||
Ok(inventories
|
Ok(inventories
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(id, _)| **id == *char_id)
|
.find(|(id, _)| **id == *char_id)
|
||||||
.map(|(_, inv)| inv.clone())
|
.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))
|
.map(|inv| apply_modifiers(&items, &weapon_modifiers, &mag_modifiers, inv))
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
|
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||||
let banks = self.banks.lock().await;
|
match bank_identifier {
|
||||||
Ok(banks
|
BankIdentifier::Character => {
|
||||||
.iter()
|
let character_banks = self.character_banks.lock().await;
|
||||||
.find(|(id, _)| **id == *char_id)
|
Ok(character_banks
|
||||||
.map(|(_, b)| b.clone())
|
.iter()
|
||||||
.unwrap_or_default())
|
.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> {
|
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
|
||||||
let mut inventories = self.inventories.lock().await;
|
let mut inventories = self.inventories.lock().await;
|
||||||
inventories.insert(*char_id, inventory.clone());
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOOD: impl bank name
|
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> {
|
match bank_identifier {
|
||||||
let mut banks = self.banks.lock().await;
|
BankIdentifier::Character => {
|
||||||
banks.insert(*char_id, bank.clone());
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,19 +690,58 @@ impl EntityGateway for InMemoryGateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
|
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
|
||||||
let mut bank_meseta = self.bank_meseta.lock().await;
|
match bank_identifier {
|
||||||
bank_meseta.insert((*char_id, bank.clone()), meseta);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
|
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
|
||||||
let mut bank_meseta = self.bank_meseta.lock().await;
|
match bank_identifier {
|
||||||
if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank.clone())) {
|
BankIdentifier::Character => {
|
||||||
Ok(*meseta)
|
let mut bank_meseta = self.bank_meseta.lock().await;
|
||||||
}
|
if let Some(meseta) = bank_meseta.get_mut(char_id) {
|
||||||
else {
|
Ok(*meseta)
|
||||||
Err(GatewayError::Error)
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,4 +767,21 @@ impl EntityGateway for InMemoryGateway {
|
|||||||
Err(GatewayError::Error)
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
16
src/entity/src/gateway/postgres/migrations/V0004__meseta.sql
Normal file
16
src/entity/src/gateway/postgres/migrations/V0004__meseta.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
alter table player_character
|
||||||
|
add keyboard_config bytea not null,
|
||||||
|
add gamepad_config bytea not null;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
alter table player_character
|
||||||
|
drop column playtime;
|
||||||
|
|
||||||
|
alter table player_character
|
||||||
|
add playtime integer not null;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
alter table player_character
|
||||||
|
drop column keyboard_config,
|
||||||
|
drop column gamepad_config;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
alter table player_character
|
||||||
|
add created_at timestamptz default current_timestamp not null;
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
14
src/entity/src/gateway/postgres/migrations/V0012__room.sql
Normal file
14
src/entity/src/gateway/postgres/migrations/V0012__room.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
17
src/entity/src/gateway/postgres/migrations/V0013__room2.sql
Normal file
17
src/entity/src/gateway/postgres/migrations/V0013__room2.sql
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
3
src/entity/src/gateway/postgres/migrations/mod.rs
Normal file
3
src/entity/src/gateway/postgres/migrations/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
use refinery::include_migration_mods;
|
||||||
|
|
||||||
|
include_migration_mods!("src/gateway/postgres/migrations");
|
||||||
@ -4,10 +4,13 @@ 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::entity::account::*;
|
use crate::account::*;
|
||||||
use crate::entity::character::*;
|
use crate::character::*;
|
||||||
use crate::entity::item::*;
|
use crate::item::*;
|
||||||
use crate::ship::map::MapArea;
|
use crate::room::*;
|
||||||
|
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 {
|
||||||
@ -49,8 +52,8 @@ pub struct PgUserSettings {
|
|||||||
id: i32,
|
id: i32,
|
||||||
user_account: i32,
|
user_account: i32,
|
||||||
blocked_users: Vec<u8>, //[u32; 0x1E],
|
blocked_users: Vec<u8>, //[u32; 0x1E],
|
||||||
keyboard_config: Vec<u8>, //[u8; 0x16C],
|
key_config: Vec<u8>, //[u8; 0x16C],
|
||||||
gamepad_config: Vec<u8>, //[u8; 0x38],
|
joystick_config: Vec<u8>, //[u8; 0x38],
|
||||||
option_flags: i32,
|
option_flags: i32,
|
||||||
shortcuts: Vec<u8>, //[u8; 0xA40],
|
shortcuts: Vec<u8>, //[u8; 0xA40],
|
||||||
symbol_chats: Vec<u8>, //[u8; 0x4E0],
|
symbol_chats: Vec<u8>, //[u8; 0x4E0],
|
||||||
@ -64,8 +67,8 @@ impl From<PgUserSettings> for UserSettingsEntity {
|
|||||||
user_id: UserAccountId(other.user_account as u32),
|
user_id: UserAccountId(other.user_account as u32),
|
||||||
settings: settings::UserSettings {
|
settings: settings::UserSettings {
|
||||||
blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
|
blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
|
||||||
keyboard_config: vec_to_array(other.keyboard_config),
|
keyboard_config: vec_to_array(other.key_config),
|
||||||
gamepad_config: vec_to_array(other.gamepad_config),
|
gamepad_config: vec_to_array(other.joystick_config),
|
||||||
option_flags: other.option_flags as u32,
|
option_flags: other.option_flags as u32,
|
||||||
shortcuts: vec_to_array(other.shortcuts),
|
shortcuts: vec_to_array(other.shortcuts),
|
||||||
symbol_chats: vec_to_array(other.symbol_chats),
|
symbol_chats: vec_to_array(other.symbol_chats),
|
||||||
@ -217,8 +220,6 @@ pub struct PgCharacter {
|
|||||||
tp: i16,
|
tp: i16,
|
||||||
|
|
||||||
tech_menu: Vec<u8>,
|
tech_menu: Vec<u8>,
|
||||||
keyboard_config: Vec<u8>,
|
|
||||||
gamepad_config: Vec<u8>,
|
|
||||||
|
|
||||||
playtime: i32,
|
playtime: i32,
|
||||||
}
|
}
|
||||||
@ -246,13 +247,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)) ).collect()
|
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t + 1)) ).collect()
|
||||||
},
|
},
|
||||||
config: CharacterConfig {
|
config: CharacterConfig {
|
||||||
raw_data: vec_to_array(other.config)
|
raw_data: vec_to_array(other.config)
|
||||||
},
|
},
|
||||||
info_board: CharacterInfoboard {
|
info_board: CharacterInfoboard {
|
||||||
board: libpso::utf8_to_utf16_array!(other.infoboard, 172),
|
board: libpso::util::utf8_to_utf16_array(other.infoboard),
|
||||||
},
|
},
|
||||||
guildcard: CharacterGuildCard {
|
guildcard: CharacterGuildCard {
|
||||||
description: other.guildcard,
|
description: other.guildcard,
|
||||||
@ -270,12 +271,6 @@ impl From<PgCharacter> for CharacterEntity {
|
|||||||
tech_menu: CharacterTechMenu {
|
tech_menu: CharacterTechMenu {
|
||||||
tech_menu: vec_to_array(other.tech_menu)
|
tech_menu: vec_to_array(other.tech_menu)
|
||||||
},
|
},
|
||||||
keyboard_config: CharacterKeyboardConfig {
|
|
||||||
keyboard_config: vec_to_array(other.keyboard_config)
|
|
||||||
},
|
|
||||||
gamepad_config: CharacterGamepadConfig {
|
|
||||||
gamepad_config: vec_to_array(other.gamepad_config)
|
|
||||||
},
|
|
||||||
playtime: other.playtime as u32,
|
playtime: other.playtime as u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,6 +580,16 @@ 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,
|
||||||
@ -600,14 +605,19 @@ 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,
|
||||||
@ -615,11 +625,14 @@ pub enum PgItemNoteDetail {
|
|||||||
},
|
},
|
||||||
Withdraw {
|
Withdraw {
|
||||||
character_id: u32,
|
character_id: u32,
|
||||||
bank: String,
|
bank: BankIdentifier,
|
||||||
},
|
},
|
||||||
Deposit {
|
Deposit {
|
||||||
character_id: u32,
|
character_id: u32,
|
||||||
bank: String,
|
bank: BankIdentifier,
|
||||||
|
},
|
||||||
|
FloorLimitReached {
|
||||||
|
map_area: MapArea,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,8 +642,16 @@ 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, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
|
ItemNote::EnemyDrop{character_id, room_id, monster_type, 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,
|
||||||
},
|
},
|
||||||
@ -642,14 +663,19 @@ impl From<ItemNote> for PgItemNoteDetail {
|
|||||||
map_area,
|
map_area,
|
||||||
x,y,z,
|
x,y,z,
|
||||||
},
|
},
|
||||||
ItemNote::Consumed => PgItemNoteDetail::Consumed,
|
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed {
|
||||||
ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
|
character_id: character_id.0,
|
||||||
|
},
|
||||||
|
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 => PgItemNoteDetail::SoldToShop,
|
ItemNote::SoldToShop{character_id} => 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,
|
||||||
@ -658,15 +684,20 @@ 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.0,
|
bank,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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.0,
|
bank,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
ItemNote::FloorLimitReached { map_area } => {
|
||||||
|
PgItemNoteDetail::FloorLimitReached {
|
||||||
|
map_area,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -675,41 +706,57 @@ 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 as u32),
|
character_id: CharacterEntityId(character_id),
|
||||||
},
|
},
|
||||||
PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
|
PgItemNoteDetail::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => ItemNote::EnemyDrop {
|
||||||
character_id: CharacterEntityId(character_id as u32),
|
character_id: CharacterEntityId(character_id),
|
||||||
|
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 as u32),
|
character_id: CharacterEntityId(character_id),
|
||||||
},
|
},
|
||||||
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 as u32),
|
character_id: CharacterEntityId(character_id),
|
||||||
map_area,
|
map_area,
|
||||||
x,y,z,
|
x,y,z,
|
||||||
},
|
},
|
||||||
PgItemNoteDetail::Consumed => ItemNote::Consumed,
|
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed {
|
||||||
PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
|
character_id: CharacterEntityId(character_id),
|
||||||
|
},
|
||||||
|
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 => ItemNote::SoldToShop,
|
PgItemNoteDetail::SoldToShop{character_id} => 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 as u32),
|
trade_id: TradeId(trade_id),
|
||||||
character_to: CharacterEntityId(character_to as u32),
|
character_to: CharacterEntityId(character_to),
|
||||||
character_from: CharacterEntityId(character_from as u32),
|
character_from: CharacterEntityId(character_from),
|
||||||
},
|
},
|
||||||
PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw {
|
PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw {
|
||||||
character_id: CharacterEntityId(character_id as u32),
|
character_id: CharacterEntityId(character_id),
|
||||||
bank: BankName(bank),
|
bank,
|
||||||
},
|
},
|
||||||
PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit {
|
PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit {
|
||||||
character_id: CharacterEntityId(character_id as u32),
|
character_id: CharacterEntityId(character_id),
|
||||||
bank: BankName(bank),
|
bank,
|
||||||
|
},
|
||||||
|
PgItemNoteDetail::FloorLimitReached { map_area } => ItemNote::FloorLimitReached {
|
||||||
|
map_area,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -877,3 +924,27 @@ 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
use std::convert::{From, TryFrom, Into};
|
// this lint is currently bugged and suggests incorrect code https://github.com/rust-lang/rust-clippy/issues/9123
|
||||||
use futures::{Future, TryStreamExt};
|
#![allow(clippy::explicit_auto_deref)]
|
||||||
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::entity::account::*;
|
use crate::account::*;
|
||||||
use crate::entity::character::*;
|
use crate::character::*;
|
||||||
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
||||||
use crate::entity::item::*;
|
use crate::item::*;
|
||||||
|
use crate::room::*;
|
||||||
use super::models::*;
|
use super::models::*;
|
||||||
|
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
@ -14,23 +19,30 @@ use sqlx::Connection;
|
|||||||
|
|
||||||
mod embedded {
|
mod embedded {
|
||||||
use refinery::embed_migrations;
|
use refinery::embed_migrations;
|
||||||
embed_migrations!("src/entity/gateway/postgres/migrations");
|
embed_migrations!("src/gateway/postgres/migrations");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct PostgresTransaction<'t> {
|
pub struct PostgresTransaction<'t> {
|
||||||
pgtransaction: sqlx::Transaction<'t, sqlx::Postgres>,
|
pgtransaction: Arc<Mutex<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> {
|
||||||
fn gateway(&mut self) -> &mut dyn EntityGateway {
|
type ParentGateway = PostgresTransaction<'t>;
|
||||||
|
|
||||||
|
fn gateway(&mut self) -> &mut Self::ParentGateway {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit(self: Box<Self>) -> Result<(), GatewayError> {
|
async fn commit(self) -> Result<(), GatewayError> {
|
||||||
self.pgtransaction.commit().await?;
|
Arc::try_unwrap(self.pgtransaction)
|
||||||
|
.unwrap()
|
||||||
|
.into_inner()
|
||||||
|
.commit()
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +65,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://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap()
|
.connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
PostgresGateway {
|
PostgresGateway {
|
||||||
@ -78,7 +90,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| {
|
weapon_modifiers.for_each(|modifier| async move {
|
||||||
if let Ok(modifier) = modifier {
|
if let Ok(modifier) = modifier {
|
||||||
weapon.apply_modifier(&modifier.modifier);
|
weapon.apply_modifier(&modifier.modifier);
|
||||||
}
|
}
|
||||||
@ -86,7 +98,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
|
|||||||
|
|
||||||
ItemDetail::Weapon(weapon)
|
ItemDetail::Weapon(weapon)
|
||||||
},
|
},
|
||||||
ItemDetail::Mag(mut mag) => {
|
ItemDetail::Mag(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)
|
||||||
@ -96,7 +108,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);
|
||||||
|
|
||||||
mag_modifiers.for_each(|modifier| {
|
let mag = mag_modifiers.fold(mag, |mut mag, modifier| async {
|
||||||
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 {
|
||||||
@ -113,6 +125,8 @@ 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)
|
||||||
@ -126,6 +140,31 @@ 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 *;")
|
||||||
@ -140,7 +179,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)
|
.bind(id.0 as i32)
|
||||||
.fetch_one(conn).await?;
|
.fetch_one(conn).await?;
|
||||||
Ok(user.into())
|
Ok(user.into())
|
||||||
}
|
}
|
||||||
@ -159,10 +198,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)
|
.bind(user.flags as i32)
|
||||||
.bind(&user.id.0)
|
.bind(user.id.0 as i32)
|
||||||
.execute(conn).await?;
|
.execute(conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -171,7 +210,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)
|
.bind(settings.user_id.0 as i32)
|
||||||
.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())
|
||||||
@ -186,7 +225,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)
|
.bind(user.id.0 as i32)
|
||||||
.fetch_one(conn).await?;
|
.fetch_one(conn).await?;
|
||||||
Ok(settings.into())
|
Ok(settings.into())
|
||||||
}
|
}
|
||||||
@ -197,25 +236,34 @@ 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)
|
.bind(settings.settings.option_flags as i32)
|
||||||
.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)
|
.bind(settings.id.0 as i32)
|
||||||
.fetch_one(conn).await?;
|
.execute(conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError>
|
async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError>
|
||||||
{
|
{
|
||||||
let q = r#"insert into player_character
|
let q = r#"insert into player_character
|
||||||
(user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
|
(user_account, slot, name, exp, class,
|
||||||
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags)
|
section_id, costume, skin, face, head,
|
||||||
|
hair, hair_r, hair_g, hair_b, prop_x,
|
||||||
|
prop_y, techs, config, infoboard, guildcard,
|
||||||
|
power, mind, def, evade, luck,
|
||||||
|
hp, tp, tech_menu, option_flags, playtime)
|
||||||
values
|
values
|
||||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)
|
($1, $2, $3, $4, $5,
|
||||||
|
$6, $7, $8, $9, $10,
|
||||||
|
$11, $12, $13, $14, $15,
|
||||||
|
$16, $17, $18, $19, $20,
|
||||||
|
$21, $22, $23, $24, $25,
|
||||||
|
$26, $27, $28, $29, $30)
|
||||||
returning *;"#;
|
returning *;"#;
|
||||||
let character = sqlx::query_as::<_, PgCharacter>(q)
|
let character = sqlx::query_as::<_, PgCharacter>(q)
|
||||||
.bind(char.user_id.0)
|
.bind(char.user_id.0 as i32)
|
||||||
.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)
|
||||||
@ -244,6 +292,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
|
|||||||
.bind(char.materials.tp as i16)
|
.bind(char.materials.tp as i16)
|
||||||
.bind(char.tech_menu.tech_menu.to_vec())
|
.bind(char.tech_menu.tech_menu.to_vec())
|
||||||
.bind(char.option_flags as i32)
|
.bind(char.option_flags as i32)
|
||||||
|
.bind(0)
|
||||||
.fetch_one(conn).await?;
|
.fetch_one(conn).await?;
|
||||||
|
|
||||||
Ok(character.into())
|
Ok(character.into())
|
||||||
@ -251,17 +300,17 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
|
|||||||
|
|
||||||
async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError>
|
async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError>
|
||||||
{
|
{
|
||||||
let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot")
|
let stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by created_at;")
|
||||||
.bind(user.id.0)
|
.bind(user.id.0 as i32)
|
||||||
.fetch(conn);
|
.fetch(conn);
|
||||||
const NONE: Option<CharacterEntity> = None;
|
|
||||||
let mut result = [NONE; 4];
|
|
||||||
while let Some(character) = stream.try_next().await? {
|
|
||||||
let index = character.slot as usize;
|
|
||||||
result[index] = Some(character.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| async move {
|
||||||
|
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>
|
||||||
@ -270,9 +319,9 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
|
|||||||
user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12,
|
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, playtime=$30
|
||||||
where id=$31;"#;
|
where id=$31;"#;
|
||||||
sqlx::query(q)
|
sqlx::query(q)
|
||||||
.bind(char.user_id.0) // $1
|
.bind(char.user_id.0 as i32) // $1
|
||||||
.bind(char.slot as i16) // $2
|
.bind(char.slot as i16) // $2
|
||||||
.bind(&char.name) // $3
|
.bind(&char.name) // $3
|
||||||
.bind(char.exp as i32) // $4
|
.bind(char.exp as i32) // $4
|
||||||
@ -301,7 +350,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
|
|||||||
.bind(char.materials.tp as i16) // $27
|
.bind(char.materials.tp as i16) // $27
|
||||||
.bind(char.tech_menu.tech_menu.to_vec()) // $28
|
.bind(char.tech_menu.tech_menu.to_vec()) // $28
|
||||||
.bind(char.option_flags as i32) // $29
|
.bind(char.option_flags as i32) // $29
|
||||||
.bind(char.playtime as i32) // $20
|
.bind(char.playtime as i32) // $30
|
||||||
.bind(char.id.0 as i32) // $31
|
.bind(char.id.0 as i32) // $31
|
||||||
.execute(conn).await?;
|
.execute(conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -322,7 +371,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)
|
.bind(item_id.0 as i32)
|
||||||
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
|
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
|
||||||
.execute(conn).await?;
|
.execute(conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -331,7 +380,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)
|
.bind(mag_item_id.0 as i32)
|
||||||
.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(())
|
||||||
@ -340,7 +389,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)
|
.bind(mag_item_id.0 as i32)
|
||||||
.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(())
|
||||||
@ -349,16 +398,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)
|
.bind(mag_item_id.0 as i32)
|
||||||
.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)
|
.bind(item_id.0 as i32)
|
||||||
.bind(sqlx::types::Json(modifier))
|
.bind(sqlx::types::Json(modifier))
|
||||||
.execute(conn).await?;
|
.execute(conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -366,76 +415,65 @@ 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 mut t = conn.begin().await?;
|
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit
|
||||||
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)
|
.bind(char_id.0 as i32)
|
||||||
.fetch_one(&mut t).await?;
|
.fetch_one(&mut **conn.lock().await).await?;
|
||||||
|
|
||||||
// TODO: inefficient
|
Ok(InventoryEntity::new(
|
||||||
let mut real_inventory = Vec::new();
|
inventory.items.0
|
||||||
for inv_item in inventory.items.0.into_iter() {
|
.into_iter()
|
||||||
match inv_item {
|
.map(move |item| {
|
||||||
PgInventoryItemEntity::Individual(item) => {
|
let conn = conn.clone();
|
||||||
let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
|
async move {
|
||||||
.bind(item)
|
fetch_item(&mut **conn.lock().await, item, InventoryItemEntity::Individual, InventoryItemEntity::Stacked).await
|
||||||
.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()
|
||||||
Ok(InventoryEntity::new(real_inventory))
|
.collect::<Result<Vec<_>, _>>()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError>
|
async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError>
|
||||||
{
|
{
|
||||||
let mut t = conn.begin().await?;
|
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit
|
||||||
let bank = sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1 and name = $2")
|
let bank = match bank_identifier {
|
||||||
.bind(char_id.0)
|
BankIdentifier::Character => {
|
||||||
.bind(bank_name.0.clone())
|
sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1")
|
||||||
.fetch_one(&mut t).await?;
|
.bind(char_id.0 as i32)
|
||||||
// TODO: inefficient
|
.fetch_one(&mut **conn.lock().await).await?
|
||||||
let mut real_bank = Vec::new();
|
},
|
||||||
for bank_item in bank.items.0.into_iter() {
|
BankIdentifier::Shared(bank_name) => {
|
||||||
match bank_item {
|
sqlx::query_as::<_, PgInventoryEntity>("select player_character.id as pchar, shared_bank.items as items from shared_bank
|
||||||
PgInventoryItemEntity::Individual(item) => {
|
join player_character on shared_bank.user_account = player_character.user_account
|
||||||
let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
|
where player_character.id = $1 and shared_bank.name = $2")
|
||||||
.bind(item)
|
.bind(char_id.0 as i32)
|
||||||
.fetch_one(&mut t).await
|
.bind(&bank_name.0)
|
||||||
.map(|item| item.into())
|
.fetch_optional(&mut **conn.lock().await)
|
||||||
.map(|item| apply_item_modifications(&mut t, item))?
|
.await?
|
||||||
.await;
|
.unwrap_or_else(|| PgInventoryEntity {
|
||||||
real_bank.push(BankItemEntity::Individual(entity));
|
pchar: char_id.0 as i32,
|
||||||
},
|
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")
|
|
||||||
.bind(s_item)
|
|
||||||
.fetch_one(&mut t).await
|
|
||||||
.map(|item| item.into())
|
|
||||||
.map(|item| apply_item_modifications(&mut t, item))?
|
|
||||||
.await)
|
|
||||||
}
|
|
||||||
real_bank.push(BankItemEntity::Stacked(stacked_item));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(BankEntity::new(real_bank))
|
Ok(BankEntity::new(
|
||||||
|
bank.items.0
|
||||||
|
.into_iter()
|
||||||
|
.map(move |item| {
|
||||||
|
let conn = conn.clone();
|
||||||
|
async move {
|
||||||
|
fetch_item(&mut **conn.lock().await, item, BankItemEntity::Individual, BankItemEntity::Stacked).await
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<FuturesOrdered<_>>()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<_>, _>>()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
@ -454,15 +492,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)
|
.bind(char_id.0 as i32)
|
||||||
.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 {
|
||||||
@ -476,19 +514,33 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, $3) on conflict (pchar, name) do update set items = $2")
|
match bank_identifier {
|
||||||
.bind(char_id.0)
|
BankIdentifier::Character => {
|
||||||
.bind(sqlx::types::Json(bank))
|
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, '') on conflict (pchar, name) do update set items = $2")
|
||||||
.bind(bank_name.0.clone())
|
.bind(char_id.0 as i32)
|
||||||
.execute(conn)
|
.bind(sqlx::types::Json(bank))
|
||||||
.await?;
|
.execute(conn)
|
||||||
|
.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)
|
.bind(char_id.0 as i32)
|
||||||
.fetch_one(conn)
|
.fetch_one(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -499,7 +551,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)
|
.bind(char_id.0 as i32)
|
||||||
.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))
|
||||||
@ -513,10 +565,11 @@ 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 items = $2")
|
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2")
|
||||||
.bind(char_id.0)
|
.bind(char_id.0 as i32)
|
||||||
.bind(meseta.0 as i32)
|
.bind(meseta.0 as i32)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
@ -527,41 +580,70 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
|
|||||||
{
|
{
|
||||||
#[derive(sqlx::FromRow)]
|
#[derive(sqlx::FromRow)]
|
||||||
struct PgMeseta(i32);
|
struct PgMeseta(i32);
|
||||||
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#)
|
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#)
|
||||||
.bind(char_id.0)
|
.bind(char_id.0 as i32)
|
||||||
.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: &BankName, meseta: Meseta) -> Result<(), GatewayError>
|
async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError>
|
||||||
{
|
{
|
||||||
sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2")
|
match bank_identifier {
|
||||||
.bind(char_id.0)
|
BankIdentifier::Character => {
|
||||||
.bind(meseta.0 as i32)
|
sqlx::query("insert into bank_meseta values ($1, '', $2) on conflict (pchar, bank) do update set meseta = $2")
|
||||||
.bind(bank.0.clone())
|
.bind(char_id.0 as i32)
|
||||||
.execute(conn)
|
.bind(meseta.0 as i32)
|
||||||
.await?;
|
.execute(conn)
|
||||||
|
.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: &BankName) -> Result<Meseta, GatewayError>
|
async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> 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"#)
|
|
||||||
.bind(char_id.0)
|
let meseta = match bank_identifier {
|
||||||
.bind(bank.0.clone())
|
BankIdentifier::Character => {
|
||||||
.fetch_one(conn)
|
sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1"#)
|
||||||
.await?;
|
.bind(char_id.0 as i32)
|
||||||
|
.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)
|
.bind(char_id1.0 as i32)
|
||||||
.bind(char_id2.0)
|
.bind(char_id2.0 as i32)
|
||||||
.fetch_one(conn)
|
.fetch_one(conn)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(trade.into())
|
Ok(trade.into())
|
||||||
@ -569,36 +651,54 @@ async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityI
|
|||||||
|
|
||||||
async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError>
|
async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError>
|
||||||
{
|
{
|
||||||
sqlx::query_as::<_, PgTradeEntity>(r#"update player_character set playtime=$2 where id=$1;"#)
|
sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#)
|
||||||
.bind(char_id.0)
|
.bind(char_id.0 as i32)
|
||||||
.bind(playtime)
|
.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)
|
.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?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl EntityGateway for PostgresGateway {
|
impl EntityGateway for PostgresGateway {
|
||||||
async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
|
type Transaction<'t> = PostgresTransaction<'t> where Self: 't;
|
||||||
{
|
|
||||||
Ok(Box::new(PostgresTransaction {
|
|
||||||
pgtransaction: self.pool.begin().await?,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
|
fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
|
||||||
where
|
where
|
||||||
Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
|
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
|
||||||
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
|
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
|
||||||
R: Send,
|
R: Send,
|
||||||
E: From<GatewayError>,
|
|
||||||
{
|
{
|
||||||
let transaction = Box::new(PostgresTransaction {
|
Box::pin(async move {
|
||||||
pgtransaction: self.pool.begin().await.map_err(|_| ()).unwrap()
|
let transaction = PostgresTransaction {
|
||||||
});
|
pgtransaction: Arc::new(Mutex::new(self.pool.begin().await?))
|
||||||
let (transaction, result) = func(transaction).await.map_err(|_| ()).unwrap();
|
};
|
||||||
transaction.commit().await.map_err(|_| ()).unwrap();
|
let (transaction, result) = func(transaction).await?;
|
||||||
Ok(result)
|
transaction.commit().await?;
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
||||||
@ -669,7 +769,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,16 +777,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_name: &BankName) -> Result<BankEntity, GatewayError> {
|
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||||
get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_name).await
|
get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_identifier).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_name: &BankName) -> Result<(), GatewayError> {
|
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||||
set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_name).await
|
set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_identifier).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> {
|
||||||
@ -705,12 +805,12 @@ 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: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
|
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
|
||||||
set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank, meseta).await
|
set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank_identifier, meseta).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
|
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
|
||||||
get_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank).await
|
get_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank_identifier).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> {
|
||||||
@ -720,49 +820,59 @@ impl EntityGateway for PostgresGateway {
|
|||||||
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
|
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
|
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, user).await
|
create_user(&mut *self.pgtransaction.lock().await, 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, id).await
|
get_user_by_id(&mut *self.pgtransaction.lock().await, 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, username).await
|
get_user_by_name(&mut *self.pgtransaction.lock().await, 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, user).await
|
save_user(&mut *self.pgtransaction.lock().await, 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, settings).await
|
create_user_settings(&mut *self.pgtransaction.lock().await, 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, user).await
|
get_user_settings_by_user(&mut *self.pgtransaction.lock().await, 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, settings).await
|
save_user_settings(&mut *self.pgtransaction.lock().await, 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, char).await
|
create_character(&mut *self.pgtransaction.lock().await, 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, user).await
|
get_characters_by_user(&mut *self.pgtransaction.lock().await, 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, char).await
|
save_character(&mut *self.pgtransaction.lock().await, 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> {
|
||||||
@ -774,75 +884,83 @@ 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, item).await
|
create_item(&mut *self.pgtransaction.lock().await, 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, item_id, item_note).await
|
add_item_note(&mut *self.pgtransaction.lock().await, 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, mag_item_id, tool_item_id).await
|
feed_mag(&mut *self.pgtransaction.lock().await, 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, mag_item_id, character).await
|
change_mag_owner(&mut *self.pgtransaction.lock().await, 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, mag_item_id, mag_cell_id).await
|
use_mag_cell(&mut *self.pgtransaction.lock().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.pgtransaction, item_id, modifier).await
|
add_weapon_modifier(&mut *self.pgtransaction.lock().await, 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, char_id).await
|
get_character_inventory(&mut *self.pgtransaction.lock().await, char_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError> {
|
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||||
get_character_bank(&mut *self.pgtransaction, char_id, bank_name).await
|
get_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank_identifier).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, char_id, inventory).await
|
set_character_inventory(&mut *self.pgtransaction.lock().await, char_id, inventory).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> {
|
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||||
set_character_bank(&mut *self.pgtransaction, char_id, bank, bank_name).await
|
set_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank, bank_identifier).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, char_id).await
|
get_character_equips(&mut *self.pgtransaction.lock().await, 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, char_id, equips).await
|
set_character_equips(&mut *self.pgtransaction.lock().await, 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, char_id, meseta).await
|
set_character_meseta(&mut *self.pgtransaction.lock().await, 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, char_id).await
|
get_character_meseta(&mut *self.pgtransaction.lock().await, char_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
|
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
|
||||||
set_bank_meseta(&mut *self.pgtransaction, char_id, bank, meseta).await
|
set_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank_identifier, meseta).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
|
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
|
||||||
get_bank_meseta(&mut *self.pgtransaction, char_id, bank).await
|
get_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank_identifier).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, char_id1, char_id2).await
|
create_trade(&mut *self.pgtransaction.lock().await, char_id1, char_id2).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
|
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
|
||||||
set_character_playtime(&mut *self.pgtransaction, char_id, playtime).await
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::entity::item::ItemEntityId;
|
use crate::item::ItemEntityId;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum ItemParseError {
|
pub enum ItemParseError {
|
||||||
@ -1,9 +1,9 @@
|
|||||||
use thiserror::Error;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use thiserror::Error;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::entity::item::tool::ToolType;
|
use crate::item::tool::ToolType;
|
||||||
use crate::entity::character::{CharacterClass, SectionID};
|
use crate::character::{CharacterClass, SectionID};
|
||||||
use crate::entity::item::ItemEntityId;
|
use crate::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
|
||||||
@ -9,9 +9,11 @@ pub mod mag;
|
|||||||
pub mod esweapon;
|
pub mod esweapon;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::entity::character::CharacterEntityId;
|
use crate::character::CharacterEntityId;
|
||||||
use crate::ship::map::MapArea;
|
use crate::room::RoomEntityId;
|
||||||
use crate::ship::drops::ItemDropType;
|
use maps::area::MapArea;
|
||||||
|
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);
|
||||||
@ -22,6 +24,12 @@ 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 {
|
||||||
@ -29,8 +37,16 @@ pub enum ItemNote {
|
|||||||
},
|
},
|
||||||
EnemyDrop {
|
EnemyDrop {
|
||||||
character_id: CharacterEntityId,
|
character_id: CharacterEntityId,
|
||||||
//monster_type: MonsterType,
|
room_id: RoomEntityId,
|
||||||
//droprate: f32,
|
monster_type: MonsterType,
|
||||||
|
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,
|
||||||
@ -46,15 +62,19 @@ pub enum ItemNote {
|
|||||||
y: f32,
|
y: f32,
|
||||||
z: f32,
|
z: f32,
|
||||||
},
|
},
|
||||||
Consumed, // TODO: character_id
|
Consumed {
|
||||||
|
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,
|
||||||
@ -62,11 +82,14 @@ pub enum ItemNote {
|
|||||||
},
|
},
|
||||||
Withdraw {
|
Withdraw {
|
||||||
character_id: CharacterEntityId,
|
character_id: CharacterEntityId,
|
||||||
bank: BankName,
|
bank: BankIdentifier,
|
||||||
},
|
},
|
||||||
Deposit {
|
Deposit {
|
||||||
character_id: CharacterEntityId,
|
character_id: CharacterEntityId,
|
||||||
bank: BankName,
|
bank: BankIdentifier,
|
||||||
|
},
|
||||||
|
FloorLimitReached {
|
||||||
|
map_area: MapArea,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,26 +156,6 @@ 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(),
|
||||||
@ -241,6 +244,13 @@ 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)]
|
||||||
@ -212,7 +212,43 @@ 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 {
|
||||||
@ -244,6 +280,41 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -255,6 +326,7 @@ 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 |
|
||||||
@ -264,6 +336,7 @@ 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 |
|
||||||
@ -662,7 +735,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,
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::entity::item::ItemEntityId;
|
use crate::item::ItemEntityId;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@ -1479,44 +1479,52 @@ impl Weapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_modifier(&mut self, modifier: &WeaponModifier) {
|
pub fn apply_modifier(&mut self, modifier: &WeaponModifier) {
|
||||||
if let WeaponModifier::Tekked{special, percent, grind} = modifier {
|
match modifier {
|
||||||
match special {
|
WeaponModifier::Tekked{special, percent, grind} => {
|
||||||
TekSpecialModifier::Plus => {
|
match special {
|
||||||
self.special = self.special.map(|special| {
|
TekSpecialModifier::Plus => {
|
||||||
special.rank_up()
|
self.special = self.special.map(|special| {
|
||||||
});
|
special.rank_up()
|
||||||
},
|
});
|
||||||
TekSpecialModifier::Minus => {
|
},
|
||||||
self.special = self.special.map(|special| {
|
TekSpecialModifier::Minus => {
|
||||||
special.rank_down()
|
self.special = self.special.map(|special| {
|
||||||
});
|
special.rank_down()
|
||||||
},
|
});
|
||||||
TekSpecialModifier::Neutral => {
|
},
|
||||||
},
|
TekSpecialModifier::Neutral => {
|
||||||
}
|
},
|
||||||
for i in 0..3 {
|
}
|
||||||
self.attrs[i] = self.attrs[i].map(|mut attr| {
|
for i in 0..3 {
|
||||||
match percent {
|
self.attrs[i] = self.attrs[i].map(|mut attr| {
|
||||||
TekPercentModifier::PlusPlus => {
|
match percent {
|
||||||
attr.value += 10;
|
TekPercentModifier::PlusPlus => {
|
||||||
},
|
attr.value += 10;
|
||||||
TekPercentModifier::Plus => {
|
},
|
||||||
attr.value += 5;
|
TekPercentModifier::Plus => {
|
||||||
},
|
attr.value += 5;
|
||||||
TekPercentModifier::MinusMinus => {
|
},
|
||||||
attr.value -= 10;
|
TekPercentModifier::MinusMinus => {
|
||||||
},
|
attr.value -= 10;
|
||||||
TekPercentModifier::Minus => {
|
},
|
||||||
attr.value -= 5;
|
TekPercentModifier::Minus => {
|
||||||
},
|
attr.value -= 5;
|
||||||
TekPercentModifier::Neutral => {
|
},
|
||||||
|
TekPercentModifier::Neutral => {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
attr
|
||||||
attr
|
});
|
||||||
});
|
}
|
||||||
|
self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8;
|
||||||
|
self.tekked = true;
|
||||||
|
},
|
||||||
|
WeaponModifier::AddGrind {amount, ..} => {
|
||||||
|
self.grind += *amount as u8;
|
||||||
|
},
|
||||||
|
WeaponModifier::AddPercents {..} => {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8;
|
|
||||||
self.tekked = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2,3 +2,4 @@ pub mod gateway;
|
|||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod character;
|
pub mod character;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
|
pub mod room;
|
||||||
83
src/entity/src/room.rs
Normal file
83
src/entity/src/room.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
31
src/entity/src/team.rs
Normal file
31
src/entity/src/team.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
25
src/items/Cargo.toml
Normal file
25
src/items/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[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 }
|
||||||
1248
src/items/src/actions.rs
Normal file
1248
src/items/src/actions.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,18 @@
|
|||||||
use thiserror::Error;
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use crate::entity::gateway::{EntityGateway, GatewayError};
|
use futures::future::join_all;
|
||||||
use crate::entity::character::CharacterEntity;
|
use thiserror::Error;
|
||||||
use crate::entity::item::mag::{MagCell, MagCellError};
|
use anyhow::Context;
|
||||||
use crate::entity::item::tool::ToolType;
|
use rand::SeedableRng;
|
||||||
use crate::entity::item::{ItemDetail, ItemEntityId};
|
use rand::distributions::{WeightedIndex, Distribution};
|
||||||
use crate::ship::items::state::{ItemStateProxy, ItemStateError};
|
use entity::gateway::{EntityGateway, GatewayError};
|
||||||
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
|
use entity::character::{CharacterEntity, TechLevel};
|
||||||
|
use entity::item::mag::{MagCell, MagCellError};
|
||||||
|
use entity::item::tool::{Tool, ToolType};
|
||||||
|
use entity::item::tech::TechniqueDisk;
|
||||||
|
use entity::item::{ItemDetail, ItemEntityId};
|
||||||
|
use entity::item::weapon::WeaponModifier;
|
||||||
|
use crate::state::ItemStateProxy;
|
||||||
|
use crate::inventory::InventoryItemDetail;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@ -15,65 +21,64 @@ pub enum ApplyItemError {
|
|||||||
NoCharacter,
|
NoCharacter,
|
||||||
#[error("item not equipped")]
|
#[error("item not equipped")]
|
||||||
ItemNotEquipped,
|
ItemNotEquipped,
|
||||||
#[error("invalid item")]
|
#[error("could not use item invalid item")]
|
||||||
InvalidItem,
|
InvalidItem,
|
||||||
|
#[error("invalid tool")]
|
||||||
|
InvalidTool,
|
||||||
#[error("gateway error {0}")]
|
#[error("gateway error {0}")]
|
||||||
GatewayError(#[from] GatewayError),
|
GatewayError(#[from] GatewayError),
|
||||||
|
|
||||||
#[error("itemstate error {0}")]
|
|
||||||
ItemStateError(Box<ItemStateError>),
|
|
||||||
|
|
||||||
#[error("magcell error {0}")]
|
#[error("magcell error {0}")]
|
||||||
MagCellError(#[from] MagCellError),
|
MagCellError(#[from] MagCellError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ItemStateError> for ApplyItemError {
|
#[derive(Debug, Clone)]
|
||||||
fn from(other: ItemStateError) -> ApplyItemError {
|
pub enum ApplyItemAction {
|
||||||
ApplyItemError::ItemStateError(Box::new(other))
|
UpdateCharacter(Box<CharacterEntity>),
|
||||||
}
|
CreateItem(ItemDetail),
|
||||||
|
//TransformItem(ItemDetail),
|
||||||
|
//RemoveItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make all these functions not-pub
|
async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
pub async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
|
||||||
character.materials.power += 1;
|
character.materials.power += 1;
|
||||||
entity_gateway.save_character(character).await?;
|
entity_gateway.save_character(character).await?;
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
character.materials.mind += 1;
|
character.materials.mind += 1;
|
||||||
entity_gateway.save_character(character).await.unwrap();
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
character.materials.evade += 1;
|
character.materials.evade += 1;
|
||||||
entity_gateway.save_character(character).await.unwrap();
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
character.materials.def += 1;
|
character.materials.def += 1;
|
||||||
entity_gateway.save_character(character).await.unwrap();
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
character.materials.luck += 1;
|
character.materials.luck += 1;
|
||||||
entity_gateway.save_character(character).await.unwrap();
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
character.materials.hp += 1;
|
character.materials.hp += 1;
|
||||||
entity_gateway.save_character(character).await.unwrap();
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> {
|
async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||||
character.materials.tp += 1;
|
character.materials.tp += 1;
|
||||||
entity_gateway.save_character(character).await.unwrap();
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
Ok(())
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -98,16 +103,16 @@ async fn mag_cell<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &Consum
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy<'a>,
|
async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy,
|
||||||
entity_gateway: &mut EG,
|
entity_gateway: &mut EG,
|
||||||
character: &CharacterEntity,
|
character: &CharacterEntity,
|
||||||
cell_entity_id: ItemEntityId,
|
cell_entity_id: ItemEntityId,
|
||||||
mag_cell_type: MagCell)
|
mag_cell_type: MagCell)
|
||||||
-> Result<(), ApplyItemError>
|
-> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||||
where
|
where
|
||||||
EG: EntityGateway + ?Sized,
|
EG: EntityGateway + ?Sized,
|
||||||
{
|
{
|
||||||
let mut inventory = item_state.inventory(&character.id)?;
|
let mut inventory = item_state.inventory(&character.id).await?;
|
||||||
|
|
||||||
let (mag_entity_id, mag) = inventory.equipped_mag_mut()
|
let (mag_entity_id, mag) = inventory.equipped_mag_mut()
|
||||||
.ok_or(ApplyItemError::ItemNotEquipped)?;
|
.ok_or(ApplyItemError::ItemNotEquipped)?;
|
||||||
@ -115,9 +120,9 @@ where
|
|||||||
|
|
||||||
entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await?;
|
entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await?;
|
||||||
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
|
||||||
item_state.set_inventory(inventory);
|
item_state.set_inventory(inventory).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -218,12 +223,55 @@ pub async fn liberta_kit<EG: EntityGateway>(entity_gateway: &mut EG, used_cell:
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy<'a>,
|
|
||||||
|
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||||
|
{
|
||||||
|
let mag_rate = WeightedIndex::new([13, 13, 13, 13, 12, 12, 12, 12]).unwrap();
|
||||||
|
let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) {
|
||||||
|
0 => ToolType::CellOfMag502,
|
||||||
|
1 => ToolType::CellOfMag213,
|
||||||
|
2 => ToolType::HeartOfChuChu,
|
||||||
|
3 => ToolType::HeartOfKapuKapu,
|
||||||
|
4 => ToolType::PartsOfRobochao,
|
||||||
|
5 => ToolType::HeartOfOpaOpa,
|
||||||
|
6 => ToolType::HeartOfPian,
|
||||||
|
7 => ToolType::HeartOfChao,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(vec![ApplyItemAction::CreateItem(ItemDetail::Tool(Tool {tool: mag_type}))])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn weapon_grind<'a, EG>(item_state: &mut ItemStateProxy,
|
||||||
|
entity_gateway: &mut EG,
|
||||||
|
character: &mut CharacterEntity,
|
||||||
|
entity_id: ItemEntityId,
|
||||||
|
grind: u32,)
|
||||||
|
-> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||||
|
where
|
||||||
|
EG: EntityGateway + ?Sized,
|
||||||
|
{
|
||||||
|
let modifier = WeaponModifier::AddGrind {
|
||||||
|
amount: grind,
|
||||||
|
grinder: entity_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut inventory = item_state.inventory(&character.id).await?;
|
||||||
|
let (weapon_entity_id, weapon) = inventory.equipped_weapon_mut()
|
||||||
|
.ok_or(ApplyItemError::ItemNotEquipped)?;
|
||||||
|
weapon.apply_modifier(&modifier);
|
||||||
|
entity_gateway.add_weapon_modifier(&weapon_entity_id, &modifier).await?;
|
||||||
|
item_state.set_inventory(inventory).await;
|
||||||
|
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy,
|
||||||
entity_gateway: &mut EG,
|
entity_gateway: &mut EG,
|
||||||
character: &mut CharacterEntity,
|
character: &mut CharacterEntity,
|
||||||
entity_id: ItemEntityId,
|
entity_id: ItemEntityId,
|
||||||
tool: ToolType)
|
tool: ToolType)
|
||||||
-> Result<(), ApplyItemError>
|
-> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||||
where
|
where
|
||||||
EG: EntityGateway + ?Sized,
|
EG: EntityGateway + ?Sized,
|
||||||
{
|
{
|
||||||
@ -235,13 +283,24 @@ where
|
|||||||
ToolType::LuckMaterial => luck_material(entity_gateway, character).await,
|
ToolType::LuckMaterial => luck_material(entity_gateway, character).await,
|
||||||
ToolType::HpMaterial => hp_material(entity_gateway, character).await,
|
ToolType::HpMaterial => hp_material(entity_gateway, character).await,
|
||||||
ToolType::TpMaterial => tp_material(entity_gateway, character).await,
|
ToolType::TpMaterial => tp_material(entity_gateway, character).await,
|
||||||
ToolType::Monomate => Ok(()),
|
ToolType::Monomate => Ok(Vec::new()),
|
||||||
ToolType::Dimate => Ok(()),
|
ToolType::Dimate => Ok(Vec::new()),
|
||||||
ToolType::Trimate => Ok(()),
|
ToolType::Trimate => Ok(Vec::new()),
|
||||||
ToolType::Monofluid => Ok(()),
|
ToolType::Monofluid => Ok(Vec::new()),
|
||||||
ToolType::Difluid => Ok(()),
|
ToolType::Difluid => Ok(Vec::new()),
|
||||||
ToolType::Trifluid => Ok(()),
|
ToolType::Trifluid => Ok(Vec::new()),
|
||||||
ToolType::HuntersReport => Ok(()),
|
ToolType::SolAtomizer => Ok(Vec::new()),
|
||||||
|
ToolType::MoonAtomizer => Ok(Vec::new()),
|
||||||
|
ToolType::StarAtomizer => Ok(Vec::new()),
|
||||||
|
ToolType::Telepipe => Ok(Vec::new()),
|
||||||
|
ToolType::Antidote => Ok(Vec::new()),
|
||||||
|
ToolType::Antiparalysis => Ok(Vec::new()),
|
||||||
|
ToolType::TrapVision => Ok(Vec::new()),
|
||||||
|
ToolType::ScapeDoll => Ok(Vec::new()),
|
||||||
|
ToolType::Monogrinder => weapon_grind(item_state, entity_gateway, character, entity_id, 1).await,
|
||||||
|
ToolType::Digrinder => weapon_grind(item_state, entity_gateway, character, entity_id, 2).await,
|
||||||
|
ToolType::Trigrinder => weapon_grind(item_state, entity_gateway, character, entity_id, 3).await,
|
||||||
|
ToolType::HuntersReport => Ok(Vec::new()),
|
||||||
ToolType::CellOfMag502
|
ToolType::CellOfMag502
|
||||||
| ToolType::CellOfMag213
|
| ToolType::CellOfMag213
|
||||||
| ToolType::PartsOfRobochao
|
| ToolType::PartsOfRobochao
|
||||||
@ -268,26 +327,69 @@ where
|
|||||||
| ToolType::LibertaKit => {
|
| ToolType::LibertaKit => {
|
||||||
mag_cell(item_state, entity_gateway, character, entity_id, tool.try_into()?).await
|
mag_cell(item_state, entity_gateway, character, entity_id, tool.try_into()?).await
|
||||||
}
|
}
|
||||||
|
ToolType::JackOLantern => jack_o_lantern(),
|
||||||
// TODO: rest of these
|
// TODO: rest of these
|
||||||
_ => Err(ApplyItemError::InvalidItem)
|
_ => Err(anyhow::Error::from(ApplyItemError::InvalidTool))
|
||||||
|
.with_context(|| {
|
||||||
|
format!("invalid tool {tool:?}")
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn apply_tech<'a, EG>(_item_state: &mut ItemStateProxy,
|
||||||
|
entity_gateway: &mut EG,
|
||||||
|
character: &mut CharacterEntity,
|
||||||
|
_entity_id: ItemEntityId,
|
||||||
|
tech: TechniqueDisk)
|
||||||
|
-> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||||
|
where
|
||||||
|
EG: EntityGateway + ?Sized,
|
||||||
|
{
|
||||||
|
// TODO: make sure the class can learn that specific tech
|
||||||
|
character.techs.set_tech(tech.tech, TechLevel(tech.level as u8));
|
||||||
|
entity_gateway.save_character(character).await.unwrap();
|
||||||
|
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||||
|
|
||||||
pub async fn apply_item<'a, EG: EntityGateway + ?Sized>(item_state: &mut ItemStateProxy<'a>, entity_gateway: &mut EG, character: &mut CharacterEntity, item: InventoryItem) -> Result<(), ApplyItemError> {
|
}
|
||||||
match item.item {
|
|
||||||
|
pub async fn apply_item<'a, EG>(item_state: &'a mut ItemStateProxy,
|
||||||
|
entity_gateway: &'a mut EG,
|
||||||
|
character: &'a mut CharacterEntity,
|
||||||
|
item: InventoryItemDetail
|
||||||
|
) -> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||||
|
where
|
||||||
|
EG: EntityGateway + ?Sized + Clone + 'a
|
||||||
|
{
|
||||||
|
match item {
|
||||||
InventoryItemDetail::Individual(individual_item) => {
|
InventoryItemDetail::Individual(individual_item) => {
|
||||||
match individual_item.item {
|
match individual_item.item {
|
||||||
ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await,
|
ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await,
|
||||||
_ => Err(ApplyItemError::InvalidItem)
|
ItemDetail::TechniqueDisk(tech) => apply_tech(item_state, entity_gateway, character, individual_item.entity_id, tech).await,
|
||||||
|
_ => Err(anyhow::Error::from(ApplyItemError::InvalidItem))
|
||||||
|
.with_context(|| {
|
||||||
|
format!("item {individual_item:?}")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
InventoryItemDetail::Stacked(stacked_item) => {
|
InventoryItemDetail::Stacked(stacked_item) => {
|
||||||
for entity_id in stacked_item.entity_ids {
|
Ok(join_all(stacked_item.entity_ids.iter()
|
||||||
apply_tool(item_state, entity_gateway, character, entity_id, stacked_item.tool.tool).await?
|
.map(|entity_id| {
|
||||||
}
|
let mut entity_gateway = entity_gateway.clone();
|
||||||
Ok(())
|
let mut character = character.clone();
|
||||||
|
let mut item_state = item_state.clone();
|
||||||
|
async move {
|
||||||
|
apply_tool(&mut item_state, &mut entity_gateway, &mut character, *entity_id, stacked_item.tool.tool).await
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<Vec<_>>, _>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,13 +1,15 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use libpso::character::character;
|
use libpso::character::character;
|
||||||
use crate::ship::items::ClientItemId;
|
use crate::ClientItemId;
|
||||||
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity, BankName};
|
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use async_std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::entity::character::CharacterEntityId;
|
use entity::character::CharacterEntityId;
|
||||||
use crate::ship::items::state::ItemStateError;
|
use entity::item::BankIdentifier;
|
||||||
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
|
use crate::state::ItemStateError;
|
||||||
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
|
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
|
||||||
|
use crate::inventory::{InventoryItem, InventoryItemDetail};
|
||||||
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -21,6 +23,7 @@ pub enum BankError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BankItemDetail {
|
pub enum BankItemDetail {
|
||||||
Individual(IndividualItemDetail),
|
Individual(IndividualItemDetail),
|
||||||
@ -64,10 +67,10 @@ pub struct BankItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BankItem {
|
impl BankItem {
|
||||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||||
where
|
where
|
||||||
F: FnMut(T, ItemEntityId) -> Fut,
|
F: FnMut(T, ItemEntityId) -> Fut,
|
||||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||||
{
|
{
|
||||||
match &self.item {
|
match &self.item {
|
||||||
BankItemDetail::Individual(individual_item) => {
|
BankItemDetail::Individual(individual_item) => {
|
||||||
@ -96,19 +99,27 @@ impl Bank {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BankState {
|
pub struct BankState {
|
||||||
pub character_id: CharacterEntityId,
|
pub character_id: CharacterEntityId,
|
||||||
pub item_id_counter: u32,
|
pub item_id_counter: Arc<Mutex<u32>>,
|
||||||
pub name: BankName,
|
pub identifier: BankIdentifier,
|
||||||
pub bank: Bank,
|
pub bank: Bank,
|
||||||
pub meseta: Meseta,
|
pub meseta: Meseta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn new_item_id(item_id_counter: &Arc<Mutex<u32>>) -> ClientItemId {
|
||||||
|
let mut item_id_counter = item_id_counter.lock().await;
|
||||||
|
let item_id = *item_id_counter;
|
||||||
|
*item_id_counter += 1;
|
||||||
|
|
||||||
|
ClientItemId(item_id)
|
||||||
|
}
|
||||||
|
|
||||||
impl BankState {
|
impl BankState {
|
||||||
pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState {
|
pub fn new(character_id: CharacterEntityId, identifier: BankIdentifier, mut bank: Bank, meseta: Meseta) -> BankState {
|
||||||
bank.0.sort();
|
bank.0.sort();
|
||||||
BankState {
|
BankState {
|
||||||
character_id,
|
character_id,
|
||||||
item_id_counter: 0,
|
item_id_counter: Arc::new(Mutex::new(0)),
|
||||||
name,
|
identifier,
|
||||||
bank,
|
bank,
|
||||||
meseta,
|
meseta,
|
||||||
}
|
}
|
||||||
@ -118,34 +129,37 @@ impl BankState {
|
|||||||
self.bank.0.len()
|
self.bank.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
|
pub async fn initialize_item_ids(&mut self, base_item_id: Arc<Mutex<u32>>) {
|
||||||
|
self.item_id_counter = base_item_id;
|
||||||
|
let mut bitem_id = self.item_id_counter.lock().await;
|
||||||
for (i, item) in self.bank.0.iter_mut().enumerate() {
|
for (i, item) in self.bank.0.iter_mut().enumerate() {
|
||||||
item.item_id = ClientItemId(base_item_id + i as u32);
|
item.item_id = ClientItemId(*bitem_id + i as u32);
|
||||||
}
|
}
|
||||||
self.item_id_counter = base_item_id + self.bank.0.len() as u32;
|
|
||||||
|
*bitem_id += self.bank.0.len() as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||||
if self.meseta.0 + amount > 999999 {
|
if self.meseta.0 + amount > 999999 {
|
||||||
return Err(ItemStateError::FullOfMeseta)
|
return Err(ItemStateError::FullOfMeseta.into())
|
||||||
}
|
}
|
||||||
self.meseta.0 += amount;
|
self.meseta.0 += amount;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||||
if amount > self.meseta.0 {
|
if amount > self.meseta.0 {
|
||||||
return Err(ItemStateError::InvalidMesetaRemoval(amount))
|
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
|
||||||
}
|
}
|
||||||
self.meseta.0 -= amount;
|
self.meseta.0 -= amount;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, BankError> {
|
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, anyhow::Error> {
|
||||||
match item.item {
|
match item.item {
|
||||||
InventoryItemDetail::Individual(iitem) => {
|
InventoryItemDetail::Individual(iitem) => {
|
||||||
if self.bank.0.len() >= 30 {
|
if self.bank.0.len() >= 30 {
|
||||||
Err(BankError::BankFull)
|
Err(BankError::BankFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.bank.0.push(BankItem {
|
self.bank.0.push(BankItem {
|
||||||
@ -166,7 +180,7 @@ impl BankState {
|
|||||||
match existing_stack {
|
match existing_stack {
|
||||||
Some(existing_stack) => {
|
Some(existing_stack) => {
|
||||||
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
||||||
Err(BankError::StackFull)
|
Err(BankError::StackFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
||||||
@ -175,7 +189,7 @@ impl BankState {
|
|||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if self.bank.0.len() >= 30 {
|
if self.bank.0.len() >= 30 {
|
||||||
Err(BankError::BankFull)
|
Err(BankError::BankFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.bank.0.push(BankItem {
|
self.bank.0.push(BankItem {
|
||||||
@ -191,7 +205,7 @@ impl BankState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<BankItem> {
|
pub async fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<BankItem> {
|
||||||
let idx = self.bank.0
|
let idx = self.bank.0
|
||||||
.iter()
|
.iter()
|
||||||
.position(|i| i.item_id == *item_id)?;
|
.position(|i| i.item_id == *item_id)?;
|
||||||
@ -211,9 +225,8 @@ impl BankState {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
||||||
self.item_id_counter += 1;
|
|
||||||
Some(BankItem {
|
Some(BankItem {
|
||||||
item_id: ClientItemId(self.item_id_counter),
|
item_id: new_item_id(&self.item_id_counter).await,
|
||||||
item: BankItemDetail::Stacked(StackedItemDetail {
|
item: BankItemDetail::Stacked(StackedItemDetail {
|
||||||
entity_ids,
|
entity_ids,
|
||||||
tool: stacked_item.tool,
|
tool: stacked_item.tool,
|
||||||
@ -292,12 +305,14 @@ impl BankState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::PartialEq for BankItem {
|
|
||||||
fn eq(&self, other: &BankItem) -> bool {
|
|
||||||
|
impl std::cmp::PartialEq for BankItemDetail {
|
||||||
|
fn eq(&self, other: &BankItemDetail) -> bool {
|
||||||
let mut self_bytes = [0u8; 4];
|
let mut self_bytes = [0u8; 4];
|
||||||
let mut other_bytes = [0u8; 4];
|
let mut other_bytes = [0u8; 4];
|
||||||
self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
|
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
|
||||||
other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]);
|
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
|
||||||
|
|
||||||
let self_value = u32::from_be_bytes(self_bytes);
|
let self_value = u32::from_be_bytes(self_bytes);
|
||||||
let other_value = u32::from_be_bytes(other_bytes);
|
let other_value = u32::from_be_bytes(other_bytes);
|
||||||
@ -306,35 +321,46 @@ impl std::cmp::PartialEq for BankItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::Eq for BankItem {}
|
impl std::cmp::Eq for BankItemDetail {}
|
||||||
|
|
||||||
impl std::cmp::PartialOrd for BankItem {
|
impl std::cmp::PartialOrd for BankItemDetail {
|
||||||
fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &BankItemDetail) -> Option<std::cmp::Ordering> {
|
||||||
let mut self_bytes = [0u8; 4];
|
Some(self.cmp(other))
|
||||||
let mut other_bytes = [0u8; 4];
|
|
||||||
self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
|
|
||||||
other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]);
|
|
||||||
|
|
||||||
|
|
||||||
let self_value = u32::from_be_bytes(self_bytes);
|
|
||||||
let other_value = u32::from_be_bytes(other_bytes);
|
|
||||||
|
|
||||||
self_value.partial_cmp(&other_value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::Ord for BankItem {
|
impl std::cmp::Ord for BankItemDetail {
|
||||||
fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
|
fn cmp(&self, other: &BankItemDetail) -> std::cmp::Ordering {
|
||||||
let mut self_bytes = [0u8; 4];
|
let mut self_bytes = [0u8; 4];
|
||||||
let mut other_bytes = [0u8; 4];
|
let mut other_bytes = [0u8; 4];
|
||||||
self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]);
|
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
|
||||||
other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]);
|
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
|
||||||
|
|
||||||
|
let self_value = u32::from_be_bytes(self_bytes);
|
||||||
let self_value = u32::from_le_bytes(self_bytes);
|
let other_value = u32::from_be_bytes(other_bytes);
|
||||||
let other_value = u32::from_le_bytes(other_bytes);
|
|
||||||
|
|
||||||
self_value.cmp(&other_value)
|
self_value.cmp(&other_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl std::cmp::PartialEq for BankItem {
|
||||||
|
fn eq(&self, other: &BankItem) -> bool {
|
||||||
|
self.item.eq(&other.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::Eq for BankItem {}
|
||||||
|
|
||||||
|
impl std::cmp::PartialOrd for BankItem {
|
||||||
|
fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::Ord for BankItem {
|
||||||
|
fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
|
||||||
|
self.item.cmp(&other.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,14 +1,14 @@
|
|||||||
use crate::ship::items::ClientItemId;
|
use crate::ClientItemId;
|
||||||
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail};
|
use entity::item::{Meseta, ItemEntityId, ItemDetail};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use crate::ship::map::MapArea;
|
use maps::area::MapArea;
|
||||||
use crate::entity::character::CharacterEntityId;
|
use entity::character::CharacterEntityId;
|
||||||
use crate::entity::item::mag::Mag;
|
use entity::item::mag::Mag;
|
||||||
|
|
||||||
use crate::ship::items::state::ItemStateError;
|
use crate::state::ItemStateError;
|
||||||
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail};
|
use crate::state::{IndividualItemDetail, StackedItemDetail};
|
||||||
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
|
use crate::inventory::{InventoryItem, InventoryItemDetail};
|
||||||
|
|
||||||
pub enum FloorType {
|
pub enum FloorType {
|
||||||
Local,
|
Local,
|
||||||
@ -33,7 +33,7 @@ pub struct FloorItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FloorItem {
|
impl FloorItem {
|
||||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||||
where
|
where
|
||||||
F: FnMut(T, ItemEntityId) -> Fut,
|
F: FnMut(T, ItemEntityId) -> Fut,
|
||||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
Fut: Future<Output=Result<T, ItemStateError>>,
|
||||||
@ -53,10 +53,10 @@ impl FloorItem {
|
|||||||
Ok(param)
|
Ok(param)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||||
where
|
where
|
||||||
F: FnMut(T, ItemEntityId, Mag) -> Fut,
|
F: FnMut(T, ItemEntityId, Mag) -> Fut,
|
||||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||||
{
|
{
|
||||||
if let FloorItemDetail::Individual(individual_item) = &self.item {
|
if let FloorItemDetail::Individual(individual_item) = &self.item {
|
||||||
if let ItemDetail::Mag(mag) = &individual_item.item {
|
if let ItemDetail::Mag(mag) = &individual_item.item {
|
||||||
@ -96,13 +96,13 @@ pub struct FloorState {
|
|||||||
impl FloorState {
|
impl FloorState {
|
||||||
pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<FloorItem> {
|
pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<FloorItem> {
|
||||||
let item = self.local.0
|
let item = self.local.0
|
||||||
.drain_filter(|item| {
|
.extract_if(|item| {
|
||||||
item.item_id == *item_id
|
item.item_id == *item_id
|
||||||
})
|
})
|
||||||
.next();
|
.next();
|
||||||
item.or_else(|| {
|
item.or_else(|| {
|
||||||
self.shared.0
|
self.shared.0
|
||||||
.drain_filter(|item| {
|
.extract_if(|item| {
|
||||||
item.item_id == *item_id
|
item.item_id == *item_id
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
@ -1,16 +1,18 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use libpso::character::character;
|
use libpso::character::character;
|
||||||
use crate::ship::items::ClientItemId;
|
use crate::ClientItemId;
|
||||||
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
|
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use async_std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::entity::character::CharacterEntityId;
|
use entity::character::CharacterEntityId;
|
||||||
use crate::entity::item::tool::ToolType;
|
use entity::item::tool::ToolType;
|
||||||
use crate::entity::item::mag::Mag;
|
use entity::item::mag::Mag;
|
||||||
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
|
use entity::item::weapon::Weapon;
|
||||||
use crate::ship::items::state::ItemStateError;
|
use shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
|
||||||
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
|
use crate::state::ItemStateError;
|
||||||
use crate::ship::items::floor::{FloorItem, FloorItemDetail};
|
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
|
||||||
|
use crate::floor::{FloorItem, FloorItemDetail};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum InventoryItemDetail {
|
pub enum InventoryItemDetail {
|
||||||
@ -60,7 +62,7 @@ impl InventoryItemDetail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should probably go somewhere a bit more fundamental like ItemDetail
|
// TODO: this should probably go somewhere a bit more fundamental like ItemDetail
|
||||||
pub fn sell_price(&self) -> Result<u32, ItemStateError> {
|
pub fn sell_price(&self) -> Result<u32, anyhow::Error> {
|
||||||
match self {
|
match self {
|
||||||
InventoryItemDetail::Individual(individual_item) => {
|
InventoryItemDetail::Individual(individual_item) => {
|
||||||
match &individual_item.item {
|
match &individual_item.item {
|
||||||
@ -102,7 +104,7 @@ impl InventoryItemDetail {
|
|||||||
Ok((ToolShopItem::from(d).price() / 8) as u32)
|
Ok((ToolShopItem::from(d).price() / 8) as u32)
|
||||||
},
|
},
|
||||||
ItemDetail::Mag(_m) => {
|
ItemDetail::Mag(_m) => {
|
||||||
Err(ItemStateError::ItemNotSellable)
|
Err(ItemStateError::ItemNotSellable.into())
|
||||||
},
|
},
|
||||||
ItemDetail::ESWeapon(_e) => {
|
ItemDetail::ESWeapon(_e) => {
|
||||||
Ok(10u32)
|
Ok(10u32)
|
||||||
@ -116,22 +118,12 @@ impl InventoryItemDetail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct InventoryItem {
|
|
||||||
pub item_id: ClientItemId,
|
|
||||||
pub item: InventoryItemDetail,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InventoryItem {
|
|
||||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
|
||||||
where
|
where
|
||||||
F: FnMut(T, ItemEntityId) -> Fut,
|
F: FnMut(T, ItemEntityId) -> Fut,
|
||||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||||
{
|
{
|
||||||
match &self.item {
|
match &self {
|
||||||
InventoryItemDetail::Individual(individual_item) => {
|
InventoryItemDetail::Individual(individual_item) => {
|
||||||
param = func(param, individual_item.entity_id).await?;
|
param = func(param, individual_item.entity_id).await?;
|
||||||
},
|
},
|
||||||
@ -144,11 +136,28 @@ impl InventoryItem {
|
|||||||
|
|
||||||
Ok(param)
|
Ok(param)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InventoryItem {
|
||||||
|
pub item_id: ClientItemId,
|
||||||
|
pub item: InventoryItemDetail,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InventoryItem {
|
||||||
|
pub async fn with_entity_id<F, Fut, T>(&self, param: T, func: F) -> Result<T, anyhow::Error>
|
||||||
|
where
|
||||||
|
F: FnMut(T, ItemEntityId) -> Fut,
|
||||||
|
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||||
|
{
|
||||||
|
self.item.with_entity_id(param, func).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||||
where
|
where
|
||||||
F: FnMut(T, ItemEntityId, Mag) -> Fut,
|
F: FnMut(T, ItemEntityId, Mag) -> Fut,
|
||||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||||
{
|
{
|
||||||
if let InventoryItemDetail::Individual(individual_item) = &self.item {
|
if let InventoryItemDetail::Individual(individual_item) = &self.item {
|
||||||
if let ItemDetail::Mag(mag) = &individual_item.item {
|
if let ItemDetail::Mag(mag) = &individual_item.item {
|
||||||
@ -179,37 +188,52 @@ pub enum InventoryError {
|
|||||||
MesetaFull,
|
MesetaFull,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct InventoryState {
|
pub struct InventoryState {
|
||||||
pub character_id: CharacterEntityId,
|
pub character_id: CharacterEntityId,
|
||||||
pub item_id_counter: u32,
|
pub item_id_counter: Arc<Mutex<u32>>,
|
||||||
pub inventory: Inventory,
|
pub inventory: Inventory,
|
||||||
pub equipped: EquippedEntity,
|
pub equipped: EquippedEntity,
|
||||||
pub meseta: Meseta,
|
pub meseta: Meseta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn new_item_id(item_id_counter: &Arc<Mutex<u32>>) -> ClientItemId {
|
||||||
|
let mut item_id_counter = item_id_counter.lock().await;
|
||||||
|
let item_id = *item_id_counter;
|
||||||
|
*item_id_counter += 1;
|
||||||
|
|
||||||
|
ClientItemId(item_id)
|
||||||
|
}
|
||||||
|
|
||||||
impl InventoryState {
|
impl InventoryState {
|
||||||
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
|
pub async fn initialize_item_ids(&mut self, base_item_id: Arc<Mutex<u32>>) {
|
||||||
|
self.item_id_counter = base_item_id;
|
||||||
|
let mut bitem_id = self.item_id_counter.lock().await;
|
||||||
|
|
||||||
for (i, item) in self.inventory.0.iter_mut().enumerate() {
|
for (i, item) in self.inventory.0.iter_mut().enumerate() {
|
||||||
item.item_id = ClientItemId(base_item_id + i as u32);
|
item.item_id = ClientItemId(*bitem_id + i as u32);
|
||||||
}
|
}
|
||||||
self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1;
|
|
||||||
|
*bitem_id += self.inventory.0.len() as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_item_id(&mut self) -> ClientItemId {
|
pub async fn new_item_id(&mut self) -> ClientItemId {
|
||||||
self.item_id_counter += 1;
|
let mut item_id_counter = self.item_id_counter.lock().await;
|
||||||
ClientItemId(self.item_id_counter)
|
let item_id = *item_id_counter;
|
||||||
|
*item_id_counter += 1;
|
||||||
|
|
||||||
|
ClientItemId(item_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.inventory.0.len()
|
self.inventory.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, InventoryError> {
|
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, anyhow::Error> {
|
||||||
match item.item {
|
match item.item {
|
||||||
FloorItemDetail::Individual(iitem) => {
|
FloorItemDetail::Individual(iitem) => {
|
||||||
if self.inventory.0.len() >= 30 {
|
if self.inventory.0.len() >= 30 {
|
||||||
Err(InventoryError::InventoryFull)
|
Err(InventoryError::InventoryFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.inventory.0.push(InventoryItem {
|
self.inventory.0.push(InventoryItem {
|
||||||
@ -229,7 +253,7 @@ impl InventoryState {
|
|||||||
match existing_stack {
|
match existing_stack {
|
||||||
Some(existing_stack) => {
|
Some(existing_stack) => {
|
||||||
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
||||||
Err(InventoryError::StackFull)
|
Err(InventoryError::StackFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
||||||
@ -238,7 +262,7 @@ impl InventoryState {
|
|||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if self.inventory.0.len() >= 30 {
|
if self.inventory.0.len() >= 30 {
|
||||||
Err(InventoryError::InventoryFull)
|
Err(InventoryError::InventoryFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.inventory.0.push(InventoryItem {
|
self.inventory.0.push(InventoryItem {
|
||||||
@ -253,7 +277,7 @@ impl InventoryState {
|
|||||||
},
|
},
|
||||||
FloorItemDetail::Meseta(meseta) => {
|
FloorItemDetail::Meseta(meseta) => {
|
||||||
if self.meseta == Meseta(999999) {
|
if self.meseta == Meseta(999999) {
|
||||||
Err(InventoryError::MesetaFull)
|
Err(InventoryError::MesetaFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999);
|
self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999);
|
||||||
@ -263,11 +287,11 @@ impl InventoryState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> {
|
pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), anyhow::Error> {
|
||||||
match &item.item {
|
match &item.item {
|
||||||
InventoryItemDetail::Individual(_) => {
|
InventoryItemDetail::Individual(_) => {
|
||||||
if self.inventory.0.len() >= 30 {
|
if self.inventory.0.len() >= 30 {
|
||||||
Err(InventoryError::InventoryFull)
|
Err(InventoryError::InventoryFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.inventory.0.push(item);
|
self.inventory.0.push(item);
|
||||||
@ -290,7 +314,7 @@ impl InventoryState {
|
|||||||
match existing_stack {
|
match existing_stack {
|
||||||
Some(existing_stack) => {
|
Some(existing_stack) => {
|
||||||
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
||||||
Err(InventoryError::StackFull)
|
Err(InventoryError::StackFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
||||||
@ -307,7 +331,7 @@ impl InventoryState {
|
|||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if self.inventory.0.len() >= 30 {
|
if self.inventory.0.len() >= 30 {
|
||||||
Err(InventoryError::InventoryFull)
|
Err(InventoryError::InventoryFull.into())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.inventory.0.push(item);
|
self.inventory.0.push(item);
|
||||||
@ -325,7 +349,36 @@ impl InventoryState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<InventoryItem> {
|
pub async fn remove_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<InventoryItemDetail> {
|
||||||
|
let idx = self.inventory.0
|
||||||
|
.iter()
|
||||||
|
.position(|i| i.item_id == *item_id)?;
|
||||||
|
match &mut self.inventory.0[idx].item {
|
||||||
|
InventoryItemDetail::Individual(_individual_item) => {
|
||||||
|
Some(self.inventory.0.remove(idx).item)
|
||||||
|
},
|
||||||
|
InventoryItemDetail::Stacked(stacked_item) => {
|
||||||
|
let remove_all = (amount == 0) || match stacked_item.entity_ids.len().cmp(&(amount as usize)) {
|
||||||
|
Ordering::Equal => true,
|
||||||
|
Ordering::Greater => false,
|
||||||
|
Ordering::Less => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if remove_all {
|
||||||
|
Some(self.inventory.0.remove(idx).item)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
||||||
|
Some(InventoryItemDetail::Stacked(StackedItemDetail {
|
||||||
|
entity_ids,
|
||||||
|
tool: stacked_item.tool,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<InventoryItem> {
|
||||||
let idx = self.inventory.0
|
let idx = self.inventory.0
|
||||||
.iter()
|
.iter()
|
||||||
.position(|i| i.item_id == *item_id)?;
|
.position(|i| i.item_id == *item_id)?;
|
||||||
@ -345,9 +398,8 @@ impl InventoryState {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
||||||
self.item_id_counter += 1;
|
|
||||||
Some(InventoryItem {
|
Some(InventoryItem {
|
||||||
item_id: ClientItemId(self.item_id_counter),
|
item_id: new_item_id(&self.item_id_counter).await,
|
||||||
item: InventoryItemDetail::Stacked(StackedItemDetail {
|
item: InventoryItemDetail::Stacked(StackedItemDetail {
|
||||||
entity_ids,
|
entity_ids,
|
||||||
tool: stacked_item.tool,
|
tool: stacked_item.tool,
|
||||||
@ -370,25 +422,25 @@ impl InventoryState {
|
|||||||
.find(|i| i.item_id == *item_id)
|
.find(|i| i.item_id == *item_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||||
if self.meseta.0 == 999999 {
|
if self.meseta.0 == 999999 {
|
||||||
return Err(ItemStateError::FullOfMeseta)
|
return Err(ItemStateError::FullOfMeseta.into())
|
||||||
}
|
}
|
||||||
self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
|
self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||||
if self.meseta.0 + amount > 999999 {
|
if self.meseta.0 + amount > 999999 {
|
||||||
return Err(ItemStateError::FullOfMeseta)
|
return Err(ItemStateError::FullOfMeseta.into())
|
||||||
}
|
}
|
||||||
self.meseta.0 += amount;
|
self.meseta.0 += amount;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||||
if amount > self.meseta.0 {
|
if amount > self.meseta.0 {
|
||||||
return Err(ItemStateError::InvalidMesetaRemoval(amount))
|
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
|
||||||
}
|
}
|
||||||
self.meseta.0 -= amount;
|
self.meseta.0 -= amount;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -453,6 +505,18 @@ impl InventoryState {
|
|||||||
.find(|(entity_id, _)| *entity_id == mag_id)
|
.find(|(entity_id, _)| *entity_id == mag_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn equipped_weapon_mut(&mut self) -> Option<(ItemEntityId, &mut Weapon)> {
|
||||||
|
let weapon_id = self.equipped.weapon?;
|
||||||
|
self.inventory.0
|
||||||
|
.iter_mut()
|
||||||
|
.filter_map(|i| {
|
||||||
|
let individual = i.item.as_individual_mut()?;
|
||||||
|
let entity_id = individual.entity_id;
|
||||||
|
Some((entity_id, individual.as_weapon_mut()?))
|
||||||
|
})
|
||||||
|
.find(|(entity_id, _)| *entity_id == weapon_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sort(&mut self, item_ids: &[ClientItemId]) {
|
pub fn sort(&mut self, item_ids: &[ClientItemId]) {
|
||||||
self.inventory.0.sort_by(|a, b| {
|
self.inventory.0.sort_by(|a, b| {
|
||||||
let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id);
|
let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id);
|
||||||
@ -34,38 +34,40 @@ where
|
|||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
E: Send + Sync,
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
pub fn act<O, F, Fut>(self, f: F) -> ItemActionStage<O, ItemStateAction<T, S, E>, F, Fut, S, E>
|
pub fn act<'a, O, F, Fut>(self, f: F) -> ItemActionStage<'a, O, ItemStateAction<T, S, E>, F, Fut, S, E>
|
||||||
where
|
where
|
||||||
F: Fn(S, ()) -> Fut + Send + Sync,
|
F: Fn(S, ()) -> Fut + Send + Sync + 'a,
|
||||||
Fut: Future<Output=Result<(S, O), E>> + Send
|
Fut: Future<Output=Result<(S, O), E>> + Send + 'a
|
||||||
{
|
{
|
||||||
ItemActionStage {
|
ItemActionStage {
|
||||||
_s: Default::default(),
|
_s: Default::default(),
|
||||||
_e: std::marker::PhantomData,
|
_e: std::marker::PhantomData,
|
||||||
|
_a: Default::default(),
|
||||||
prev: self,
|
prev: self,
|
||||||
actionf: f,
|
actionf: f,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ItemActionStage<O, P, F, Fut, S, E>
|
pub struct ItemActionStage<'a, O, P, F, Fut, S, E>
|
||||||
where
|
where
|
||||||
P: ItemAction,
|
P: ItemAction,
|
||||||
F: Fn(S, P::Output) -> Fut + Send + Sync,
|
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
|
||||||
Fut: Future<Output=Result<(S, O) , E>> + Send,
|
Fut: Future<Output=Result<(S, O) , E>> + Send + 'a,
|
||||||
{
|
{
|
||||||
_s: std::marker::PhantomData<S>,
|
_s: std::marker::PhantomData<S>,
|
||||||
_e: std::marker::PhantomData<E>,
|
_e: std::marker::PhantomData<E>,
|
||||||
|
_a: std::marker::PhantomData<&'a ()>,
|
||||||
prev: P,
|
prev: P,
|
||||||
actionf: F,
|
actionf: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<O, P: ItemAction, F, Fut, S, E> ItemAction for ItemActionStage<O, P, F, Fut, S, E>
|
impl<'a, O, P: ItemAction, F, Fut, S, E> ItemAction for ItemActionStage<'a, O, P, F, Fut, S, E>
|
||||||
where
|
where
|
||||||
P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
|
P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
|
||||||
F: Fn(S, P::Output) -> Fut + Send + Sync,
|
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
|
||||||
Fut: Future<Output=Result<(S, O), E>> + Send,
|
Fut: Future<Output=Result<(S, O), E>> + Send + 'a,
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
P::Output: Send + Sync,
|
P::Output: Send + Sync,
|
||||||
E: Send + Sync,
|
E: Send + Sync,
|
||||||
@ -87,11 +89,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<O, P: ItemAction, F, Fut, S, E> ItemActionStage<O, P, F, Fut, S, E>
|
impl<'a, O, P: ItemAction, F, Fut, S, E> ItemActionStage<'a, O, P, F, Fut, S, E>
|
||||||
where
|
where
|
||||||
P: ItemAction<Start = S, Error = E> + Send + Sync,
|
P: ItemAction<Start = S, Error = E> + Send + Sync,
|
||||||
F: Fn(S, P::Output) -> Fut + Send + Sync,
|
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
|
||||||
Fut: Future<Output=Result<(S, O), E>> + Send,
|
Fut: Future<Output=Result<(S, O), E>> + Send + 'a,
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
P::Output: Send + Sync,
|
P::Output: Send + Sync,
|
||||||
E: Send + Sync,
|
E: Send + Sync,
|
||||||
@ -99,16 +101,17 @@ where
|
|||||||
P::Error: Send + Sync,
|
P::Error: Send + Sync,
|
||||||
{
|
{
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn act<O2, G, GFut>(self, g: G) -> ItemActionStage<O2, ItemActionStage<O, P, F, Fut, S, E>, G, GFut, S, E>
|
pub fn act<'b, O2, G, GFut>(self, g: G) -> ItemActionStage<'b, O2, ItemActionStage<'a, O, P, F, Fut, S, E>, G, GFut, S, E>
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
G: Fn(S, <ItemActionStage<O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync,
|
G: Fn(S, <ItemActionStage<'a, O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync + 'b,
|
||||||
GFut: Future<Output=Result<(S, O2), E>> + Send,
|
GFut: Future<Output=Result<(S, O2), E>> + Send + 'b,
|
||||||
O2: Send + Sync,
|
O2: Send + Sync,
|
||||||
{
|
{
|
||||||
ItemActionStage {
|
ItemActionStage {
|
||||||
_s: Default::default(),
|
_s: Default::default(),
|
||||||
_e: Default::default(),
|
_e: Default::default(),
|
||||||
|
_a: Default::default(),
|
||||||
prev: self,
|
prev: self,
|
||||||
actionf: g,
|
actionf: g,
|
||||||
}
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
#![feature(extract_if)]
|
||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod apply_item;
|
pub mod apply_item;
|
||||||
@ -6,6 +8,7 @@ pub mod inventory;
|
|||||||
pub mod floor;
|
pub mod floor;
|
||||||
pub mod bank;
|
pub mod bank;
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
|
pub mod trade;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)]
|
||||||
pub struct ClientItemId(pub u32);
|
pub struct ClientItemId(pub u32);
|
||||||
561
src/items/src/state.rs
Normal file
561
src/items/src/state.rs
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
564
src/items/src/tasks.rs
Normal file
564
src/items/src/tasks.rs
Normal file
@ -0,0 +1,564 @@
|
|||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
38
src/items/src/trade.rs
Normal file
38
src/items/src/trade.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
13
src/lib.rs
@ -1,13 +0,0 @@
|
|||||||
#![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;
|
|
||||||
12
src/location/Cargo.toml
Normal file
12
src/location/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[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 }
|
||||||
676
src/location/src/lib.rs
Normal file
676
src/location/src/lib.rs
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
#![allow(dead_code, unused_must_use)]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use thiserror::Error;
|
||||||
|
use networking::serverstate::ClientId;
|
||||||
|
|
||||||
|
use async_std::sync::{Arc, RwLock};
|
||||||
|
use futures::{stream, StreamExt};
|
||||||
|
use std::pin::pin;
|
||||||
|
|
||||||
|
pub const MAX_ROOMS: usize = 128;
|
||||||
|
|
||||||
|
pub enum AreaType {
|
||||||
|
Room,
|
||||||
|
Lobby,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LobbyId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||||
|
pub struct RoomId(pub usize);
|
||||||
|
|
||||||
|
impl LobbyId {
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
self.0 as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum CreateRoomError {
|
||||||
|
#[error("no open slots")]
|
||||||
|
NoOpenSlots,
|
||||||
|
#[error("client already in area")]
|
||||||
|
ClientInAreaAlready,
|
||||||
|
#[error("join error")]
|
||||||
|
JoinError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum JoinRoomError {
|
||||||
|
#[error("room does not exist")]
|
||||||
|
RoomDoesNotExist,
|
||||||
|
#[error("room is full")]
|
||||||
|
RoomFull,
|
||||||
|
#[error("client already in area")]
|
||||||
|
ClientInAreaAlready,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum JoinLobbyError {
|
||||||
|
#[error("lobby does not exist")]
|
||||||
|
LobbyDoesNotExist,
|
||||||
|
#[error("lobby is full")]
|
||||||
|
LobbyFull,
|
||||||
|
#[error("client already in area")]
|
||||||
|
ClientInAreaAlready,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetAreaError {
|
||||||
|
#[error("not in a room")]
|
||||||
|
NotInRoom,
|
||||||
|
#[error("not in a lobby")]
|
||||||
|
NotInLobby,
|
||||||
|
#[error("get area: invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ClientRemovalError {
|
||||||
|
#[error("client removal: client not in area")]
|
||||||
|
ClientNotInArea,
|
||||||
|
#[error("client removal: invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetClientsError {
|
||||||
|
#[error("invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
#[error("invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetNeighborError {
|
||||||
|
#[error("get neighbor: invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
#[error("get neighbor: invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum GetLeaderError {
|
||||||
|
#[error("get leader: invalid client")]
|
||||||
|
InvalidClient,
|
||||||
|
#[error("get leader: invalid area")]
|
||||||
|
InvalidArea,
|
||||||
|
#[error("get leader: client not in area")]
|
||||||
|
NoClientInArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ClientLocationError {
|
||||||
|
#[error("create room error {0}")]
|
||||||
|
CreateRoomError(#[from] CreateRoomError),
|
||||||
|
#[error("join room error {0}")]
|
||||||
|
JoinRoomError(#[from] JoinRoomError),
|
||||||
|
#[error("join lobby error {0}")]
|
||||||
|
JoinLobbyError(#[from] JoinLobbyError),
|
||||||
|
#[error("get area error {0}")]
|
||||||
|
GetAreaError(#[from] GetAreaError),
|
||||||
|
#[error("client removal error {0}")]
|
||||||
|
ClientRemovalError(#[from] ClientRemovalError),
|
||||||
|
#[error("get clients error {0}")]
|
||||||
|
GetClientsError(#[from] GetClientsError),
|
||||||
|
#[error("get neighbor error {0}")]
|
||||||
|
GetNeighborError(#[from] GetNeighborError),
|
||||||
|
#[error("get leader error {0}")]
|
||||||
|
GetLeaderError(#[from] GetLeaderError)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LocalClientId(usize);
|
||||||
|
|
||||||
|
impl LocalClientId {
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
self.0 as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<u8> for LocalClientId {
|
||||||
|
fn eq(&self, other: &u8) -> bool {
|
||||||
|
self.0 == *other as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AreaClient {
|
||||||
|
pub client: ClientId,
|
||||||
|
pub local_client: LocalClientId,
|
||||||
|
time_join: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
struct Lobby([Option<AreaClient>; 12]);
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
struct Room([Option<AreaClient>; 4]);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum RoomLobby {
|
||||||
|
Room(RoomId),
|
||||||
|
Lobby(LobbyId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ClientLocation {
|
||||||
|
lobbies: [Arc<RwLock<Lobby>>; 15],
|
||||||
|
rooms: [Arc<RwLock<Option<Room>>>; MAX_ROOMS],
|
||||||
|
client_location: Arc<RwLock<HashMap<ClientId, RoomLobby>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientLocation {
|
||||||
|
fn default() -> ClientLocation {
|
||||||
|
ClientLocation {
|
||||||
|
lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))),
|
||||||
|
rooms: core::array::from_fn(|_| Arc::new(RwLock::new(None))),
|
||||||
|
client_location: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ClientLocation {
|
||||||
|
pub async fn add_client_to_lobby(&self, id: ClientId, lobby_id: LobbyId) -> Result<(), JoinLobbyError> {
|
||||||
|
{
|
||||||
|
let lobby = self.lobbies
|
||||||
|
.get(lobby_id.0)
|
||||||
|
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
if lobby.0.iter().all(|c| c.is_some()) {
|
||||||
|
return Err(JoinLobbyError::LobbyFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.remove_client_from_area(id).await;
|
||||||
|
|
||||||
|
let mut lobby = self.lobbies
|
||||||
|
.get(lobby_id.0)
|
||||||
|
.ok_or(JoinLobbyError::LobbyDoesNotExist)?
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
let (index, empty_slot) = lobby.0.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, k)| k.is_none())
|
||||||
|
.ok_or(JoinLobbyError::LobbyFull)?;
|
||||||
|
*empty_slot = Some(AreaClient {
|
||||||
|
client: id,
|
||||||
|
local_client: LocalClientId(index),
|
||||||
|
time_join: SystemTime::now(),
|
||||||
|
});
|
||||||
|
self.client_location
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(id, RoomLobby::Lobby(lobby_id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_client_to_next_available_lobby(&self, id: ClientId, lobby: LobbyId) -> Result<LobbyId, JoinLobbyError> {
|
||||||
|
pin!(stream::iter(0..15)
|
||||||
|
.filter_map(|lobby_index| async move {
|
||||||
|
let new_lobby = LobbyId((lobby.0 + lobby_index) % 15);
|
||||||
|
Some((new_lobby, self.add_client_to_lobby(id, new_lobby).await.ok()?))
|
||||||
|
}))
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.map(|l| l.0)
|
||||||
|
.ok_or(JoinLobbyError::LobbyFull)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_new_room(&mut self, id: ClientId) -> Result<RoomId, CreateRoomError> {
|
||||||
|
let (index, empty_slot) = Box::pin(stream::iter(self.rooms.iter())
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, r)| async {r.read().await.is_none()}))
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.ok_or(CreateRoomError::NoOpenSlots)?;
|
||||||
|
*empty_slot.write().await = Some(Room([None; 4]));
|
||||||
|
self.add_client_to_room(id, RoomId(index))
|
||||||
|
.await
|
||||||
|
.map_err(|_err| CreateRoomError::JoinError)?;
|
||||||
|
Ok(RoomId(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> {
|
||||||
|
let mut r = self.rooms.get(room.0)
|
||||||
|
.ok_or(JoinRoomError::RoomDoesNotExist)?
|
||||||
|
.as_ref()
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
let r = r.as_mut()
|
||||||
|
.ok_or(JoinRoomError::RoomDoesNotExist)?;
|
||||||
|
let (index, empty_slot) = r.0.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, k)| k.is_none())
|
||||||
|
.ok_or(JoinRoomError::RoomFull)?;
|
||||||
|
*empty_slot = Some(AreaClient {
|
||||||
|
client: id,
|
||||||
|
local_client: LocalClientId(index),
|
||||||
|
time_join: SystemTime::now(),
|
||||||
|
});
|
||||||
|
self.remove_client_from_area(id).await;
|
||||||
|
self.client_location
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(id, RoomLobby::Room(room));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_clients_by_client(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetNeighborError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.collect())
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_client_neighbors(&self, id: ClientId) -> Result<Vec<AreaClient>, GetNeighborError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetNeighborError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| c.client != id)
|
||||||
|
.collect())
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| c.client != id)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_room_leader(&self, room: RoomId) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
let r = self.rooms[room.0]
|
||||||
|
.as_ref()
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.ok_or(GetLeaderError::InvalidArea)?;
|
||||||
|
let mut r = r
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
r.sort_by_key(|k| k.time_join);
|
||||||
|
let c = r.get(0)
|
||||||
|
.ok_or(GetLeaderError::NoClientInArea)?;
|
||||||
|
Ok(**c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_lobby_leader(&self, lobby: LobbyId) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
let l = self.lobbies[lobby.0]
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let mut l = l
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
l.sort_by_key(|k| k.time_join);
|
||||||
|
let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?;
|
||||||
|
Ok(**c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_area_leader(&self, roomlobby: RoomLobby) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
match roomlobby {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
self.get_room_leader(room).await
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
self.get_lobby_leader(lobby).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_leader_by_client(&self, id: ClientId) -> Result<AreaClient, GetLeaderError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetLeaderError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
self.get_room_leader(*room).await
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
self.get_lobby_leader(*lobby).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result<Vec<AreaClient>, GetClientsError> {
|
||||||
|
Ok(self.lobbies
|
||||||
|
.get(lobby.0)
|
||||||
|
.ok_or(GetClientsError::InvalidArea)?
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|client| {
|
||||||
|
client.map(|c| {
|
||||||
|
c
|
||||||
|
})
|
||||||
|
}).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_clients_in_room(&self, room: RoomId) -> Result<Vec<AreaClient>, GetClientsError> {
|
||||||
|
Ok(self.rooms.get(room.0)
|
||||||
|
.ok_or(GetClientsError::InvalidArea)?
|
||||||
|
.as_ref()
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.ok_or(GetClientsError::InvalidArea)?
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|client| {
|
||||||
|
client.map(|c| {
|
||||||
|
c
|
||||||
|
})
|
||||||
|
}).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_local_client(&self, id: ClientId) -> Result<AreaClient, GetClientsError> {
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetClientsError::InvalidClient)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
self.get_clients_in_room(*room)
|
||||||
|
.await
|
||||||
|
.map_err(|_| GetClientsError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|c| c.client == id)
|
||||||
|
.ok_or(GetClientsError::InvalidClient)
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
self.get_clients_in_lobby(*lobby)
|
||||||
|
.await
|
||||||
|
.map_err(|_| GetClientsError::InvalidArea)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|c| c.client == id)
|
||||||
|
.ok_or(GetClientsError::InvalidClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_area(&self, id: ClientId) -> Result<RoomLobby, GetAreaError> {
|
||||||
|
self.client_location
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(GetAreaError::InvalidClient)
|
||||||
|
.map(Clone::clone)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_room(&self, id: ClientId) -> Result<RoomId, GetAreaError> {
|
||||||
|
if let RoomLobby::Room(room) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
|
||||||
|
Ok(*room)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(GetAreaError::NotInRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_lobby(&self, id: ClientId) -> Result<LobbyId, GetAreaError> {
|
||||||
|
if let RoomLobby::Lobby(lobby) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? {
|
||||||
|
Ok(*lobby)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(GetAreaError::NotInLobby)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_client_from_area(&self, id: ClientId) -> Result<(), ClientRemovalError> {
|
||||||
|
fn remove_client<const N: usize>(id: ClientId, client_list : &mut [Option<AreaClient>; N]) {
|
||||||
|
client_list
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|client| {
|
||||||
|
client.map_or(false, |c| {
|
||||||
|
c.client == id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.for_each(|client| {
|
||||||
|
*client = None
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let area = self.client_location
|
||||||
|
.read()
|
||||||
|
.await;
|
||||||
|
let area = area
|
||||||
|
.get(&id)
|
||||||
|
.ok_or(ClientRemovalError::ClientNotInArea)?;
|
||||||
|
match area {
|
||||||
|
RoomLobby::Room(room) => {
|
||||||
|
let mut r = self.rooms.get(room.0)
|
||||||
|
.ok_or(ClientRemovalError::InvalidArea)?
|
||||||
|
.as_ref()
|
||||||
|
.write()
|
||||||
|
.await;
|
||||||
|
if let Some(r) = r.as_mut() {
|
||||||
|
remove_client(id, &mut r.0)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Err(ClientRemovalError::InvalidArea)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RoomLobby::Lobby(lobby) => {
|
||||||
|
remove_client(id, &mut self.lobbies[lobby.0].write().await.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_lobby() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(13), LobbyId(1)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(14), LobbyId(0)).await.unwrap();
|
||||||
|
|
||||||
|
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.into_iter().flatten().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(12), LocalClientId(0)),
|
||||||
|
(ClientId(14), LocalClientId(1)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_full_lobby() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
|
||||||
|
}
|
||||||
|
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await == Err(JoinLobbyError::LobbyFull));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_next_available_lobby() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for lobby in 1..4 {
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await == Ok(LobbyId(4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_to_lobby_when_all_are_full() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for lobby in 0..15 {
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await, Err(JoinLobbyError::LobbyFull));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_new_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
assert!(cl.create_new_room(ClientId(12)).await == Ok(RoomId(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_add_client_to_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(12)).await.unwrap();
|
||||||
|
assert!(cl.add_client_to_room(ClientId(234), room).await == Ok(()));
|
||||||
|
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(12), LocalClientId(0)),
|
||||||
|
(ClientId(234), LocalClientId(1)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_no_new_room_slots() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
for i in 0..128 {
|
||||||
|
cl.create_new_room(ClientId(i)).await;
|
||||||
|
}
|
||||||
|
assert!(cl.create_new_room(ClientId(234)).await == Err(CreateRoomError::NoOpenSlots));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_joining_full_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(0)).await.unwrap();
|
||||||
|
assert!(cl.add_client_to_room(ClientId(1), room).await == Ok(()));
|
||||||
|
assert!(cl.add_client_to_room(ClientId(2), room).await == Ok(()));
|
||||||
|
assert!(cl.add_client_to_room(ClientId(3), room).await == Ok(()));
|
||||||
|
assert!(cl.add_client_to_room(ClientId(234), room).await == Err(JoinRoomError::RoomFull));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_adding_client_to_room_removes_from_lobby() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
|
||||||
|
|
||||||
|
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
||||||
|
assert!(cl.add_client_to_room(ClientId(93), room).await == Ok(()));
|
||||||
|
assert!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(23), LocalClientId(1)),
|
||||||
|
(ClientId(12), LocalClientId(3)),
|
||||||
|
]);
|
||||||
|
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(51), LocalClientId(0)),
|
||||||
|
(ClientId(93), LocalClientId(1)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_getting_neighbors() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await.unwrap();
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap();
|
||||||
|
|
||||||
|
assert!(cl.get_client_neighbors(ClientId(23)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(93), LocalClientId(0)),
|
||||||
|
(ClientId(51), LocalClientId(2)),
|
||||||
|
(ClientId(12), LocalClientId(3)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_failing_to_join_lobby_does_not_remove_from_current_area() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
for i in 0..12 {
|
||||||
|
cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap();
|
||||||
|
}
|
||||||
|
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(1)).await.is_ok());
|
||||||
|
assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await.is_err());
|
||||||
|
assert_eq!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().len(), 12);
|
||||||
|
assert_eq!(
|
||||||
|
cl.get_clients_in_lobby(LobbyId(1)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>(),
|
||||||
|
vec![(ClientId(99), LocalClientId(0))]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_get_leader() {
|
||||||
|
let cl = ClientLocation::default();
|
||||||
|
cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await;
|
||||||
|
cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await;
|
||||||
|
|
||||||
|
assert!(cl.get_leader_by_client(ClientId(51)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_remove_client_from_room() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(93), room).await;
|
||||||
|
cl.add_client_to_room(ClientId(23), room).await;
|
||||||
|
cl.remove_client_from_area(ClientId(51)).await;
|
||||||
|
cl.add_client_to_room(ClientId(12), room).await;
|
||||||
|
|
||||||
|
assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::<Vec<_>>() == vec![
|
||||||
|
(ClientId(12), LocalClientId(0)),
|
||||||
|
(ClientId(93), LocalClientId(1)),
|
||||||
|
(ClientId(23), LocalClientId(2)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_leader_changes_on_leader_leaving() {
|
||||||
|
let mut cl = ClientLocation::default();
|
||||||
|
let room = cl.create_new_room(ClientId(51)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(93), room).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(23), room).await.unwrap();
|
||||||
|
cl.remove_client_from_area(ClientId(51)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(12), room).await.unwrap();
|
||||||
|
cl.remove_client_from_area(ClientId(23)).await.unwrap();
|
||||||
|
cl.add_client_to_room(ClientId(99), room).await.unwrap();
|
||||||
|
|
||||||
|
assert!(cl.get_leader_by_client(ClientId(12)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#[allow(clippy::module_inception)]
|
|
||||||
pub mod login;
|
|
||||||
pub mod character;
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
src/login_server/Cargo.toml
Normal file
21
src/login_server/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[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 }
|
||||||
@ -2,6 +2,9 @@
|
|||||||
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};
|
||||||
|
|
||||||
@ -9,39 +12,45 @@ 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 crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
|
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
|
||||||
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
|
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
|
||||||
use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
|
use networking::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
|
||||||
use crate::common::leveltable::CharacterLevelTable;
|
use stats::leveltable::LEVEL_TABLE;
|
||||||
use libpso::{utf8_to_array, utf8_to_utf16_array};
|
use libpso::util::{utf8_to_array, utf8_to_utf16_array};
|
||||||
|
|
||||||
use crate::entity::gateway::{EntityGateway, GatewayError};
|
use entity::gateway::{EntityGateway, GatewayError};
|
||||||
use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
|
use entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
|
||||||
use crate::entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity, Meseta};
|
use entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankIdentifier, EquippedEntity, Meseta};
|
||||||
use crate::entity::item::weapon::Weapon;
|
use entity::item::weapon::Weapon;
|
||||||
use crate::entity::item::armor::Armor;
|
use entity::item::armor::Armor;
|
||||||
use crate::entity::item::tech::Technique;
|
use entity::item::tech::Technique;
|
||||||
use crate::entity::item::tool::Tool;
|
use entity::item::tool::Tool;
|
||||||
use crate::entity::item::mag::Mag;
|
use entity::item::mag::Mag;
|
||||||
use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
|
use entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
|
||||||
|
|
||||||
use crate::login::login::{get_login_status};
|
use crate::login::get_login_status;
|
||||||
use crate::common::interserver::AuthToken;
|
use networking::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),
|
||||||
CouldNotLoadSettings,
|
#[error("could not load settings {0}")]
|
||||||
|
CouldNotLoadSettings(GatewayError),
|
||||||
|
#[error("could not load characters")]
|
||||||
CouldNotLoadCharacters,
|
CouldNotLoadCharacters,
|
||||||
|
#[error("could not load guildcard")]
|
||||||
CouldNotLoadGuildcard,
|
CouldNotLoadGuildcard,
|
||||||
|
#[error("gateway error {0}")]
|
||||||
GatewayError(#[from] GatewayError),
|
GatewayError(#[from] GatewayError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +150,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(), 0x40),
|
filename: utf8_to_array(param.file_name().unwrap().to_str().unwrap()),
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer.append(&mut filebuf);
|
buffer.append(&mut filebuf);
|
||||||
@ -168,7 +177,7 @@ impl ClientState {
|
|||||||
user: None,
|
user: None,
|
||||||
characters: None,
|
characters: None,
|
||||||
guildcard_data_buffer: None,
|
guildcard_data_buffer: None,
|
||||||
session: Session::new(),
|
session: Session::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,22 +188,22 @@ struct ConnectedClient {
|
|||||||
expires: Option<chrono::DateTime<chrono::Utc>>,
|
expires: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CharacterServerState<EG: EntityGateway> {
|
#[derive(Clone)]
|
||||||
|
pub struct CharacterServerState<EG: EntityGateway + Clone> {
|
||||||
entity_gateway: EG,
|
entity_gateway: EG,
|
||||||
param_header: ParamDataHeader,
|
param_header: ParamDataHeader,
|
||||||
param_data: Vec<u8>,
|
param_data: Arc<Vec<u8>>,
|
||||||
clients: HashMap<ClientId, ClientState>,
|
clients: Arc<RwLock<HashMap<ClientId, ClientState>>>,
|
||||||
ships: BTreeMap<ServerId, Ship>,
|
ships: Arc<RwLock<BTreeMap<ServerId, Ship>>>,
|
||||||
level_table: CharacterLevelTable,
|
|
||||||
auth_token: AuthToken,
|
auth_token: AuthToken,
|
||||||
|
|
||||||
connected_clients: BTreeMap<UserAccountId, ConnectedClient>,
|
connected_clients: Arc<RwLock<BTreeMap<UserAccountId, ConnectedClient>>>,
|
||||||
authenticated_ships: BTreeSet<ServerId>,
|
authenticated_ships: Arc<RwLock<BTreeSet<ServerId>>>,
|
||||||
ship_sender: BTreeMap<ServerId, Box<dyn Fn(LoginMessage) + Send>>,
|
ship_sender: Arc<RwLock<BTreeMap<ServerId, channel::Sender<LoginMessage>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAccountEntity, preview: &CharacterPreview) -> Result<(), anyhow::Error> {
|
async fn new_character<EG: EntityGateway + Clone>(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)),
|
||||||
@ -203,6 +212,7 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
|
|||||||
|
|
||||||
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,
|
||||||
@ -257,6 +267,8 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
|
|||||||
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(
|
||||||
@ -294,7 +306,7 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
|
|||||||
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(), &BankName("".into())).await?;
|
entity_gateway.set_character_bank(&character.id, &BankEntity::default(), &BankIdentifier::Character).await?;
|
||||||
let equipped = EquippedEntity {
|
let equipped = EquippedEntity {
|
||||||
weapon: Some(weapon.id),
|
weapon: Some(weapon.id),
|
||||||
armor: Some(armor.id),
|
armor: Some(armor.id),
|
||||||
@ -306,49 +318,46 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<EG: EntityGateway> CharacterServerState<EG> {
|
impl<EG: EntityGateway + Clone> 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,
|
param_data: Arc::new(param_data),
|
||||||
clients: HashMap::new(),
|
clients: Default::default(),
|
||||||
ships: BTreeMap::new(),
|
ships: Default::default(),
|
||||||
level_table: CharacterLevelTable::default(),
|
//level_table: CharacterLevelTable::default(),
|
||||||
auth_token,
|
auth_token,
|
||||||
authenticated_ships: BTreeSet::new(),
|
authenticated_ships: Default::default(),
|
||||||
ship_sender: BTreeMap::new(),
|
ship_sender: Default::default(),
|
||||||
connected_clients: BTreeMap::new(),
|
connected_clients: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.get(&user.id) {
|
if let Some(connected_client) = self.connected_clients.read().await.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::new()))]);
|
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
|
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new());
|
let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::default());
|
||||||
response.guildcard = user.guildcard;
|
response.guildcard = user.guildcard;
|
||||||
response.team_id = user.team_id.map_or(0, |ti| ti) as u32;
|
response.team_id = user.team_id.map_or(0, |ti| ti);
|
||||||
|
|
||||||
let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
|
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
|
||||||
|
|
||||||
self.connected_clients.insert(user.id, ConnectedClient {
|
self.connected_clients.write().await.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)),
|
||||||
});
|
});
|
||||||
@ -358,33 +367,34 @@ impl<EG: EntityGateway> 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::new()))])
|
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::default()))])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_ship_list(&mut self, _id: ClientId, _pkt: &Login) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
async 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.iter().map(|(i, s)| {
|
SendCharacterPacket::ShipList(ShipList::new(self.ships.read().await.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, 0x11)
|
name: utf8_to_utf16_array(&s.name)
|
||||||
}
|
}
|
||||||
}).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 client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
|
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)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -396,7 +406,8 @@ impl<EG: EntityGateway> 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 client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
|
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)?);
|
||||||
}
|
}
|
||||||
@ -404,7 +415,7 @@ impl<EG: EntityGateway> 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) = self.level_table.get_stats_from_exp(char.char_class, char.exp);
|
let (level, _stats) = 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()
|
||||||
@ -442,7 +453,8 @@ impl<EG: EntityGateway> 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 client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
|
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();
|
||||||
@ -453,15 +465,16 @@ impl<EG: EntityGateway> CharacterServerState<EG> {
|
|||||||
Ok(vec![SendCharacterPacket::GuildcardDataHeader(GuildcardDataHeader::new(bytes.len(), crc.sum32()))])
|
Ok(vec![SendCharacterPacket::GuildcardDataHeader(GuildcardDataHeader::new(bytes.len(), crc.sum32()))])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guildcard_data_chunk(&mut self, id: ClientId, chunk: u32, again: u32) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
async fn guildcard_data_chunk(&mut self, id: ClientId, chunk: u32, again: u32) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
||||||
let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
|
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() as usize - start);
|
let len = std::cmp::min(GUILD_CARD_CHUNK_SIZE, client.guildcard_data_buffer.as_ref().unwrap().len() - start);
|
||||||
let end = start + len;
|
let end = start + len;
|
||||||
|
|
||||||
let mut buf = [0u8; GUILD_CARD_CHUNK_SIZE as usize];
|
let mut buf = [0u8; GUILD_CARD_CHUNK_SIZE];
|
||||||
buf[..len as usize].copy_from_slice(&client.guildcard_data_buffer.as_ref().unwrap()[start..end]);
|
buf[..len].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 {
|
||||||
@ -470,15 +483,17 @@ impl<EG: EntityGateway> 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 client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
let mut user = client.user.as_mut().unwrap();
|
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn param_data_chunk_request(&mut self, id: ClientId, _request: &ParamDataChunkRequest) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
async fn param_data_chunk_request(&mut self, id: ClientId, _request: &ParamDataChunkRequest) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
||||||
let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
|
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;
|
||||||
|
|
||||||
@ -499,8 +514,9 @@ impl<EG: EntityGateway> 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 client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?;
|
let mut client = self.clients.write().await;
|
||||||
let mut user = client.user.as_mut().unwrap();
|
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
|
||||||
|
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?
|
||||||
}
|
}
|
||||||
@ -522,26 +538,30 @@ impl<EG: EntityGateway> CharacterServerState<EG> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_ship(&mut self, id: ClientId, menuselect: &MenuSelect) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
async 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.get(&id) {
|
if let Some(client) = self.clients.read().await.get(&id) {
|
||||||
if let Some(user) = &client.user {
|
if let Some(user) = &client.user {
|
||||||
if let Some(cc) = self.connected_clients.get_mut(&user.id) {
|
if let Some(cc) = self.connected_clients.write().await.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.get(&ServerId(menuselect.item as usize))
|
let ship = self.ships.read().await;
|
||||||
.ok_or(CharacterError::InvalidMenuSelection(menuselect.menu, menuselect.item))?;
|
let ship = ship.get(&ServerId(menuselect.item as usize))
|
||||||
|
.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))])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ship_detail(&mut self, menudetail: &MenuDetail) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
async fn ship_detail(&mut self, menudetail: &MenuDetail) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
|
||||||
let players = self.connected_clients.iter()
|
let players = self.connected_clients
|
||||||
|
.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))
|
||||||
})
|
})
|
||||||
@ -552,13 +572,14 @@ impl<EG: EntityGateway> CharacterServerState<EG> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<EG: EntityGateway> ServerState for CharacterServerState<EG> {
|
impl<EG: EntityGateway + Clone> 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>>, anyhow::Error> {
|
async fn on_connect(&mut self, id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, anyhow::Error> {
|
||||||
self.clients.insert(id, ClientState::new());
|
self.clients.write().await.insert(id, ClientState::new());
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
@ -568,65 +589,66 @@ impl<EG: EntityGateway> 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((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
|
OnConnect::Cipher(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key),
|
||||||
Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
|
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)
|
async fn handle(&mut self, id: ClientId, pkt: RecvCharacterPacket) -> Result<Vec<(ClientId, SendCharacterPacket)>, anyhow::Error> {
|
||||||
-> 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 {
|
||||||
Box::new(self.send_ship_list(id, login)?.into_iter().map(move |pkt| (id, pkt)))
|
self.send_ship_list(id, &login).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Box::new(self.validate_login(id, login).await?.into_iter().map(move |pkt| (id, pkt)))
|
self.validate_login(id, &login).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::RequestSettings(_req) => {
|
RecvCharacterPacket::RequestSettings(_req) => {
|
||||||
Box::new(self.get_settings(id).await?.into_iter().map(move |pkt| (id, pkt)))
|
self.get_settings(id).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::CharSelect(sel) => {
|
RecvCharacterPacket::CharSelect(sel) => {
|
||||||
Box::new(self.char_select(id, sel).await?.into_iter().map(move |pkt| (id, pkt)))
|
self.char_select(id, &sel).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::Checksum(_checksum) => {
|
RecvCharacterPacket::Checksum(_checksum) => {
|
||||||
Box::new(self.validate_checksum().into_iter().map(move |pkt| (id, pkt)))
|
self.validate_checksum().into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::GuildcardDataRequest(_request) => {
|
RecvCharacterPacket::GuildcardDataRequest(_request) => {
|
||||||
Box::new(self.guildcard_data_header(id).await?.into_iter().map(move |pkt| (id, pkt)))
|
self.guildcard_data_header(id).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::GuildcardDataChunkRequest(request) => {
|
RecvCharacterPacket::GuildcardDataChunkRequest(request) => {
|
||||||
Box::new(self.guildcard_data_chunk(id, request.chunk, request.again)?.into_iter().map(move |pkt| (id, pkt)))
|
self.guildcard_data_chunk(id, request.chunk, request.again).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::ParamDataRequest(_request) => {
|
RecvCharacterPacket::ParamDataRequest(_request) => {
|
||||||
Box::new(vec![SendCharacterPacket::ParamDataHeader(self.param_header.clone())].into_iter().map(move |pkt| (id, pkt)))
|
vec![SendCharacterPacket::ParamDataHeader(self.param_header.clone())].into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::SetFlag(flag) => {
|
RecvCharacterPacket::SetFlag(flag) => {
|
||||||
Box::new(self.set_flag(id, flag).await?.map(move |pkt| (id, pkt)))
|
self.set_flag(id, &flag).await?.map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::ParamDataChunkRequest(request) => {
|
RecvCharacterPacket::ParamDataChunkRequest(request) => {
|
||||||
Box::new(self.param_data_chunk_request(id, request)?.into_iter().map(move |pkt| (id, pkt)))
|
self.param_data_chunk_request(id, &request).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::CharacterPreview(preview) => {
|
RecvCharacterPacket::CharacterPreview(preview) => {
|
||||||
Box::new(self.character_preview(id, preview).await?.into_iter().map(move |pkt| (id, pkt)))
|
self.character_preview(id, &preview).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::MenuSelect(menuselect) => {
|
RecvCharacterPacket::MenuSelect(menuselect) => {
|
||||||
Box::new(self.select_ship(id, menuselect)?.into_iter().map(move |pkt| (id, pkt)))
|
self.select_ship(id, &menuselect).await?.into_iter().map(move |pkt| (id, pkt)).collect()
|
||||||
},
|
},
|
||||||
RecvCharacterPacket::MenuDetail(menudetail) => {
|
RecvCharacterPacket::MenuDetail(menudetail) => {
|
||||||
match menudetail.menu {
|
match menudetail.menu {
|
||||||
SHIP_MENU_ID => Box::new(self.ship_detail(menudetail)?.into_iter().map(move |pkt| (id, pkt))),
|
SHIP_MENU_ID => self.ship_detail(&menudetail).await?.into_iter().map(move |pkt| (id, pkt)).collect(),
|
||||||
_ => Box::new(Vec::new().into_iter())
|
_ => Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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.remove(&id) {
|
if let Some(client) = self.clients.write().await.remove(&id) {
|
||||||
if let Some(user) = client.user {
|
if let Some(user) = client.user {
|
||||||
self.connected_clients.remove(&user.id);
|
self.connected_clients.write().await.remove(&user.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
@ -634,7 +656,7 @@ impl<EG: EntityGateway> ServerState for CharacterServerState<EG> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<EG: EntityGateway> InterserverActor for CharacterServerState<EG> {
|
impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
|
||||||
type SendMessage = LoginMessage;
|
type SendMessage = LoginMessage;
|
||||||
type RecvMessage = ShipMessage;
|
type RecvMessage = ShipMessage;
|
||||||
type Error = ();
|
type Error = ();
|
||||||
@ -643,21 +665,26 @@ impl<EG: EntityGateway> InterserverActor for CharacterServerState<EG> {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error> {
|
async fn on_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.insert(id);
|
self.authenticated_ships.write().await.insert(id);
|
||||||
}
|
}
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
},
|
},
|
||||||
ShipMessage::NewShip(new_ship) => {
|
ShipMessage::NewShip(new_ship) => {
|
||||||
if self.authenticated_ships.contains(&id) {
|
dbg!("adding ship", &id, &new_ship);
|
||||||
self.ships.insert(id, new_ship);
|
if self.authenticated_ships.read().await.contains(&id) {
|
||||||
|
self.ships.write().await.insert(id, new_ship);
|
||||||
}
|
}
|
||||||
|
dbg!("ship list", &self.authenticated_ships);
|
||||||
|
|
||||||
let ships = self.ships.iter().map(|(_, s)| s).cloned().collect::<Vec<_>>();
|
let ships = self.ships.read().await.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() })
|
||||||
@ -665,8 +692,8 @@ impl<EG: EntityGateway> InterserverActor for CharacterServerState<EG> {
|
|||||||
.collect())
|
.collect())
|
||||||
},
|
},
|
||||||
ShipMessage::AddUser(new_user) => {
|
ShipMessage::AddUser(new_user) => {
|
||||||
if self.authenticated_ships.contains(&id) {
|
if self.authenticated_ships.read().await.contains(&id) {
|
||||||
self.connected_clients.insert(new_user, ConnectedClient {
|
self.connected_clients.write().await.insert(new_user, ConnectedClient {
|
||||||
ship_id: Some(id),
|
ship_id: Some(id),
|
||||||
expires: None,
|
expires: None,
|
||||||
});
|
});
|
||||||
@ -674,15 +701,18 @@ impl<EG: EntityGateway> InterserverActor for CharacterServerState<EG> {
|
|||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
},
|
},
|
||||||
ShipMessage::RemoveUser(new_user) => {
|
ShipMessage::RemoveUser(new_user) => {
|
||||||
if self.authenticated_ships.contains(&id) {
|
if self.authenticated_ships.read().await.contains(&id) {
|
||||||
self.connected_clients.remove(&new_user);
|
self.connected_clients.write().await.remove(&new_user);
|
||||||
}
|
}
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
},
|
},
|
||||||
ShipMessage::RequestShipList => {
|
ShipMessage::RequestShipList => {
|
||||||
if self.authenticated_ships.contains(&id) {
|
dbg!("request ship list", &self.authenticated_ships);
|
||||||
|
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
|
||||||
@ -702,20 +732,25 @@ impl<EG: EntityGateway> 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.remove(&id);
|
self.ships.write().await.remove(&id);
|
||||||
self.ship_sender.remove(&id);
|
self.ship_sender.write().await.remove(&id);
|
||||||
self.connected_clients = self.connected_clients.clone().into_iter()
|
self.connected_clients
|
||||||
.filter(|(_, client)| {
|
.write()
|
||||||
|
.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, 1); // it should not be possible for the client to specify the kbm config preset from the char create screen
|
let mut character = NewCharacterEntity::new(user.id);
|
||||||
character.slot = preview.slot;
|
character.slot = preview.slot;
|
||||||
character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into();
|
character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into();
|
||||||
character.section_id = preview.character.section_id.into();
|
character.section_id = preview.character.section_id.into();
|
||||||
@ -789,7 +824,7 @@ 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, 16),
|
name: utf8_to_utf16_array(&character.name),
|
||||||
play_time: character.playtime,
|
play_time: character.playtime,
|
||||||
..character::SelectScreenCharacter::default()
|
..character::SelectScreenCharacter::default()
|
||||||
}
|
}
|
||||||
@ -800,9 +835,21 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::entity::account::*;
|
use entity::account::*;
|
||||||
use libpso::character::{settings, character};
|
use libpso::character::{settings, character};
|
||||||
use crate::entity::gateway::{InMemoryGateway, GatewayError};
|
use entity::gateway::{InMemoryGateway, EntityGatewayTransaction, 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() {
|
||||||
@ -812,6 +859,7 @@ 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),
|
||||||
@ -838,11 +886,9 @@ mod test {
|
|||||||
at_character: false,
|
at_character: false,
|
||||||
at_ship: false,
|
at_ship: false,
|
||||||
});
|
});
|
||||||
server.clients.insert(ClientId(5), clientstate);
|
server.clients.write().await.insert(ClientId(5), clientstate);
|
||||||
|
|
||||||
let send = server.handle(ClientId(5), &RecvCharacterPacket::RequestSettings(RequestSettings{})).await
|
let send = server.handle(ClientId(5), RecvCharacterPacket::RequestSettings(RequestSettings{})).await.unwrap();
|
||||||
.unwrap()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert!(send.len() == 1);
|
assert!(send.len() == 1);
|
||||||
|
|
||||||
assert!(send[0].0 == ClientId(5));
|
assert!(send[0].0 == ClientId(5));
|
||||||
@ -855,11 +901,13 @@ 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().collect::<Vec<_>>();
|
})).await.unwrap();
|
||||||
assert!(send.len() == 1);
|
assert!(send.len() == 1);
|
||||||
|
|
||||||
let bytes = send[0].1.as_bytes();
|
let bytes = send[0].1.as_bytes();
|
||||||
@ -887,10 +935,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.insert(ClientId(1), fake_user.clone());
|
server.clients.write().await.insert(ClientId(1), fake_user.clone());
|
||||||
let mut send = server.handle(ClientId(1), &RecvCharacterPacket::SetFlag(SetFlag {flags: 1})).await.unwrap().collect::<Vec<_>>();
|
let mut send = server.handle(ClientId(1), RecvCharacterPacket::SetFlag(SetFlag {flags: 1})).await.unwrap();
|
||||||
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],
|
||||||
@ -916,7 +964,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().collect::<Vec<_>>();
|
} })).await.unwrap();
|
||||||
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();
|
||||||
2
src/login_server/src/lib.rs
Normal file
2
src/login_server/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod login;
|
||||||
|
pub mod character;
|
||||||
@ -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 crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
|
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
|
||||||
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
|
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
|
||||||
|
|
||||||
use crate::entity::gateway::EntityGateway;
|
use entity::gateway::EntityGateway;
|
||||||
use crate::entity::account::{UserAccountEntity};
|
use 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,23 +83,16 @@ 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)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LoginServerState<EG: EntityGateway> {
|
#[derive(Clone)]
|
||||||
|
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>,
|
clients: HashMap<ClientId, String>, // TODO: this should be arc/mutex'd?
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<EG: EntityGateway> LoginServerState<EG> {
|
impl<EG: EntityGateway + Clone> 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,
|
||||||
@ -118,7 +111,7 @@ impl<EG: EntityGateway> 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::login::character::CHARACTER_PORT))])
|
SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::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))])
|
||||||
@ -128,13 +121,14 @@ impl<EG: EntityGateway> LoginServerState<EG> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<EG: EntityGateway> ServerState for LoginServerState<EG> {
|
impl<EG: EntityGateway + Clone> 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>>, anyhow::Error> {
|
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, 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];
|
||||||
@ -143,20 +137,20 @@ impl<EG: EntityGateway> 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((Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key)),
|
OnConnect::Cipher(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, client_key),
|
||||||
Box::new(PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))))
|
PSOBBCipher::new(ELSEWHERE_PARRAY, ELSEWHERE_PRIVATE_KEY, server_key))
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(&mut self, id: ClientId, pkt: &Self::RecvPacket)
|
async fn handle(&mut self, id: ClientId, pkt: Self::RecvPacket) -> Result<Vec<(ClientId, Self::SendPacket)>, anyhow::Error> {
|
||||||
-> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)> + Send>, anyhow::Error> {
|
|
||||||
Ok(match pkt {
|
Ok(match pkt {
|
||||||
RecvLoginPacket::Login(login) => {
|
RecvLoginPacket::Login(login) => {
|
||||||
Box::new(self.validate_login(id, login).await?
|
self.validate_login(id, &login).await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |pkt| {
|
.map(move |pkt| {
|
||||||
(id, pkt)
|
(id, pkt)
|
||||||
}))
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -176,8 +170,8 @@ impl<EG: EntityGateway> ServerState for LoginServerState<EG> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::entity::account::{UserAccountId};
|
use entity::account::{UserAccountId};
|
||||||
use crate::entity::gateway::GatewayError;
|
use entity::gateway::{EntityGatewayTransaction, GatewayError};
|
||||||
|
|
||||||
const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login {
|
const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login {
|
||||||
tag: 65536,
|
tag: 65536,
|
||||||
@ -202,6 +196,17 @@ 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() {
|
||||||
@ -211,6 +216,7 @@ 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 {
|
||||||
@ -237,7 +243,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().collect::<Vec<_>>();
|
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap();
|
||||||
assert!(send == vec![
|
assert!(send == vec![
|
||||||
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
||||||
status: AccountStatus::Ok,
|
status: AccountStatus::Ok,
|
||||||
@ -269,13 +275,14 @@ 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().collect::<Vec<_>>();
|
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap();
|
||||||
|
|
||||||
assert!(send == vec![
|
assert!(send == vec![
|
||||||
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
||||||
@ -303,6 +310,7 @@ 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 {
|
||||||
@ -324,7 +332,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().collect::<Vec<_>>();
|
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap();
|
||||||
|
|
||||||
assert!(send == vec![
|
assert!(send == vec![
|
||||||
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
||||||
@ -352,6 +360,7 @@ 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 {
|
||||||
@ -373,7 +382,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().collect::<Vec<_>>();
|
let send = server.handle(ClientId(1), LOGIN_PACKET).await.unwrap();
|
||||||
|
|
||||||
assert!(send == vec![
|
assert!(send == vec![
|
||||||
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
(ClientId(1), SendLoginPacket::LoginResponse(LoginResponse {
|
||||||
14
src/maps/Cargo.toml
Normal file
14
src/maps/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[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 }
|
||||||
@ -2,7 +2,7 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::ship::room::Episode;
|
use crate::room::Episode;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@ -318,8 +318,10 @@ impl fmt::Display for MapArea {
|
|||||||
pub struct MapAreaLookup(HashMap<u16, MapArea>);
|
pub struct MapAreaLookup(HashMap<u16, MapArea>);
|
||||||
|
|
||||||
impl MapAreaLookup {
|
impl MapAreaLookup {
|
||||||
pub fn get_area_map(&self, map_area: u16) -> Result<&MapArea, MapAreaError> {
|
pub fn get_area_map(&self, map_area: u16) -> Result<MapArea, MapAreaError> {
|
||||||
self.0.get(&map_area).ok_or(MapAreaError::UnknownMapArea(map_area))
|
self.0.get(&map_area)
|
||||||
|
.copied()
|
||||||
|
.ok_or(MapAreaError::UnknownMapArea(map_area))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_ep1_maps() -> MapAreaLookup {
|
fn default_ep1_maps() -> MapAreaLookup {
|
||||||
@ -1,18 +1,18 @@
|
|||||||
// TOOD: `pub(super) for most of these?`
|
// TOOD: `pub(super) for most of these?`
|
||||||
use std::io::{Read};
|
use std::io::{Read};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::ship::monster::MonsterType;
|
|
||||||
use crate::ship::room::Episode;
|
|
||||||
|
|
||||||
use crate::ship::map::*;
|
|
||||||
|
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::ship::drops::{load_rare_monster_file};
|
|
||||||
|
use crate::Holiday;
|
||||||
|
use crate::area::{MapArea, MapAreaError};
|
||||||
|
use crate::room::Episode;
|
||||||
|
use crate::monster::MonsterType;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct RawMapEnemy {
|
pub struct RawMapEnemy {
|
||||||
@ -68,6 +68,17 @@ impl RawMapEnemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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).unwrap();
|
||||||
|
toml::from_str::<T>(s.as_str()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("")]
|
#[error("")]
|
||||||
pub enum MapEnemyError {
|
pub enum MapEnemyError {
|
||||||
@ -75,6 +86,7 @@ pub enum MapEnemyError {
|
|||||||
MapAreaError(#[from] MapAreaError),
|
MapAreaError(#[from] MapAreaError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// making this `pub type` doesn't allow `impl`s to be defined?
|
// making this `pub type` doesn't allow `impl`s to be defined?
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct RareMonsterAppearTable {
|
pub struct RareMonsterAppearTable {
|
||||||
@ -87,10 +99,9 @@ impl RareMonsterAppearTable {
|
|||||||
|
|
||||||
let appear_rates: HashMap<MonsterType, f32> = cfg
|
let appear_rates: HashMap<MonsterType, f32> = cfg
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(monster, rate)| {
|
.filter_map(|(monster, rate)| {
|
||||||
let monster: MonsterType = monster.parse().unwrap(); // TODO: don't unwrap!
|
let monster: MonsterType = monster.parse().ok()?;
|
||||||
let appear_rate = rate;
|
Some((monster, rate))
|
||||||
(monster, appear_rate)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -99,11 +110,17 @@ impl RareMonsterAppearTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn roll_appearance(&self, monster: &MonsterType) -> bool {
|
fn roll_is_rare(&self, monster: &MonsterType) -> bool {
|
||||||
if rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) {
|
rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32)
|
||||||
return true
|
}
|
||||||
|
|
||||||
|
pub fn apply(&self, enemy: MapEnemy, event: Holiday) -> MapEnemy {
|
||||||
|
if enemy.can_be_rare() && self.roll_is_rare(&enemy.monster) {
|
||||||
|
enemy.into_rare(event)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
enemy
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +341,7 @@ impl MapEnemy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_rare_appearance(self) -> bool {
|
pub fn can_be_rare(&self) -> bool {
|
||||||
matches!(self.monster,
|
matches!(self.monster,
|
||||||
MonsterType::RagRappy | MonsterType::Hildebear |
|
MonsterType::RagRappy | MonsterType::Hildebear |
|
||||||
MonsterType::PoisonLily | MonsterType::PofuillySlime |
|
MonsterType::PoisonLily | MonsterType::PofuillySlime |
|
||||||
@ -339,32 +356,26 @@ impl MapEnemy {
|
|||||||
guaranteed rare monsters don't count towards the limit
|
guaranteed rare monsters don't count towards the limit
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn set_rare_appearance(self) -> MapEnemy {
|
pub fn into_rare(self, event: Holiday) -> MapEnemy {
|
||||||
match (self.monster, self.map_area.to_episode()) {
|
match (self.monster, self.map_area.to_episode(), event) {
|
||||||
(MonsterType::RagRappy, Episode::One) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
|
(MonsterType::RagRappy, Episode::One, _) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
|
||||||
(MonsterType::RagRappy, Episode::Two) => {MapEnemy {monster: MonsterType::EventRappy, shiny:true, ..self}},
|
(MonsterType::RagRappy, Episode::Two, Holiday::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}},
|
||||||
(MonsterType::Hildebear, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
|
(MonsterType::RagRappy, Episode::Two, Holiday::Halloween) => {MapEnemy {monster: MonsterType::HalloRappy, shiny:true, ..self}},
|
||||||
(MonsterType::PoisonLily, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},
|
(MonsterType::RagRappy, Episode::Two, Holiday::Christmas) => {MapEnemy {monster: MonsterType::StRappy, shiny:true, ..self}},
|
||||||
(MonsterType::PofuillySlime, _) => {MapEnemy {monster: MonsterType::PouillySlime, shiny:true, ..self}},
|
(MonsterType::RagRappy, Episode::Two, _) => {MapEnemy {monster: MonsterType::LoveRappy, shiny:true, ..self}},
|
||||||
(MonsterType::SandRappyCrater, _) => {MapEnemy {monster: MonsterType::DelRappyCrater, shiny:true, ..self}},
|
(MonsterType::Hildebear, _, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
|
||||||
(MonsterType::ZuCrater, _) => {MapEnemy {monster: MonsterType::PazuzuCrater, shiny:true, ..self}},
|
(MonsterType::PoisonLily, _, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},
|
||||||
(MonsterType::Dorphon, _) => {MapEnemy {monster: MonsterType::DorphonEclair, shiny:true, ..self}},
|
(MonsterType::PofuillySlime, _, _) => {MapEnemy {monster: MonsterType::PouillySlime, shiny:true, ..self}},
|
||||||
(MonsterType::SandRappyDesert, _) => {MapEnemy {monster: MonsterType::DelRappyDesert, shiny:true, ..self}},
|
(MonsterType::SandRappyCrater, _, _) => {MapEnemy {monster: MonsterType::DelRappyCrater, shiny:true, ..self}},
|
||||||
(MonsterType::ZuDesert, _) => {MapEnemy {monster: MonsterType::PazuzuDesert, shiny:true, ..self}},
|
(MonsterType::ZuCrater, _, _) => {MapEnemy {monster: MonsterType::PazuzuCrater, shiny:true, ..self}},
|
||||||
(MonsterType::MerissaA, _) => {MapEnemy {monster: MonsterType::MerissaAA, shiny:true, ..self}},
|
(MonsterType::Dorphon, _, _) => {MapEnemy {monster: MonsterType::DorphonEclair, shiny:true, ..self}},
|
||||||
(MonsterType::SaintMillion, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
|
(MonsterType::SandRappyDesert, _, _) => {MapEnemy {monster: MonsterType::DelRappyDesert, shiny:true, ..self}},
|
||||||
(MonsterType::Shambertin, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
|
(MonsterType::ZuDesert, _, _) => {MapEnemy {monster: MonsterType::PazuzuDesert, shiny:true, ..self}},
|
||||||
|
(MonsterType::MerissaA, _, _) => {MapEnemy {monster: MonsterType::MerissaAA, shiny:true, ..self}},
|
||||||
|
(MonsterType::SaintMillion, _, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
|
||||||
|
(MonsterType::Shambertin, _, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
|
||||||
_ => {self},
|
_ => {self},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in theory this should only be called on monsters we know can have rare types
|
|
||||||
#[must_use]
|
|
||||||
pub fn roll_appearance_for_mission(self, rare_monster_table: &RareMonsterAppearTable) -> MapEnemy {
|
|
||||||
if rare_monster_table.roll_appearance(&self.monster) {
|
|
||||||
return self.set_rare_appearance()
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
59
src/maps/src/lib.rs
Normal file
59
src/maps/src/lib.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,12 +6,15 @@ use std::fs::File;
|
|||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::ship::monster::MonsterType;
|
|
||||||
use crate::ship::room::{Episode, RoomMode};
|
|
||||||
|
|
||||||
// TODO: don't use *
|
|
||||||
use crate::ship::map::*;
|
|
||||||
|
|
||||||
|
//use crate::ship::ship::ShipEvent;
|
||||||
|
use crate::area::MapArea;
|
||||||
|
use crate::Holiday;
|
||||||
|
use crate::enemy::{MapEnemy, RawMapEnemy, RareMonsterAppearTable};
|
||||||
|
use crate::monster::MonsterType;
|
||||||
|
use crate::variant::{MapVariant, MapVariantMode};
|
||||||
|
use crate::object::{MapObject, RawMapObject};
|
||||||
|
use crate::room::{Episode, RoomMode, PlayerMode};
|
||||||
|
|
||||||
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
|
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
|
||||||
let mut object_data = Vec::new();
|
let mut object_data = Vec::new();
|
||||||
@ -34,7 +37,7 @@ fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) ->
|
|||||||
enemy
|
enemy
|
||||||
.map_or(vec![None], |monster| {
|
.map_or(vec![None], |monster| {
|
||||||
let mut monsters = vec![Some(monster)];
|
let mut monsters = vec![Some(monster)];
|
||||||
|
|
||||||
match monster.monster {
|
match monster.monster {
|
||||||
MonsterType::Monest => {
|
MonsterType::Monest => {
|
||||||
for _ in 0..30 {
|
for _ in 0..30 {
|
||||||
@ -171,25 +174,10 @@ fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
pub fn default_map_variants(episode: Episode, player_mode: PlayerMode) -> Vec<MapVariant> {
|
||||||
#[error("")]
|
match (episode, player_mode) {
|
||||||
pub enum MapsError {
|
(Episode::One, PlayerMode::Multi) => {
|
||||||
InvalidMonsterId(usize),
|
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
|
||||||
InvalidObjectId(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Maps {
|
|
||||||
map_variants: Vec<MapVariant>,
|
|
||||||
enemy_data: Vec<Option<MapEnemy>>,
|
|
||||||
object_data: Vec<Option<MapObject>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Maps {
|
|
||||||
pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable) -> Maps {
|
|
||||||
let map_variants = match (room_mode.episode(), room_mode.single_player()) {
|
|
||||||
(Episode::One, 0) => {
|
|
||||||
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
|
|
||||||
MapVariant::new(MapArea::Forest1, MapVariantMode::Online),
|
MapVariant::new(MapArea::Forest1, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::Forest2, MapVariantMode::Online),
|
MapVariant::new(MapArea::Forest2, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::Caves1, MapVariantMode::Online),
|
MapVariant::new(MapArea::Caves1, MapVariantMode::Online),
|
||||||
@ -204,10 +192,10 @@ impl Maps {
|
|||||||
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online),
|
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::VolOpt, MapVariantMode::Online),
|
MapVariant::new(MapArea::VolOpt, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online),
|
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
(Episode::One, 1) => {
|
(Episode::One, PlayerMode::Single) => {
|
||||||
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
|
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::Forest1, MapVariantMode::Offline),
|
MapVariant::new(MapArea::Forest1, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::Forest2, MapVariantMode::Offline),
|
MapVariant::new(MapArea::Forest2, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::Caves1, MapVariantMode::Offline),
|
MapVariant::new(MapArea::Caves1, MapVariantMode::Offline),
|
||||||
@ -222,10 +210,10 @@ impl Maps {
|
|||||||
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Offline),
|
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::VolOpt, MapVariantMode::Offline),
|
MapVariant::new(MapArea::VolOpt, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline),
|
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
(Episode::Two, 0) => {
|
(Episode::Two, PlayerMode::Multi) => {
|
||||||
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
|
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online),
|
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online),
|
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Online),
|
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Online),
|
||||||
@ -241,10 +229,10 @@ impl Maps {
|
|||||||
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Online),
|
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Online),
|
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::GolDragon, MapVariantMode::Online),
|
MapVariant::new(MapArea::GolDragon, MapVariantMode::Online),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
(Episode::Two, 1) => {
|
(Episode::Two, PlayerMode::Single) => {
|
||||||
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
|
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline),
|
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline),
|
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Offline),
|
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Offline),
|
||||||
@ -260,10 +248,10 @@ impl Maps {
|
|||||||
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Offline),
|
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Offline),
|
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Offline),
|
||||||
MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline),
|
MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
(Episode::Four, _) => {
|
(Episode::Four, PlayerMode::Multi) => {
|
||||||
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
|
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::CraterEast, MapVariantMode::Online),
|
MapVariant::new(MapArea::CraterEast, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::CraterWest, MapVariantMode::Online),
|
MapVariant::new(MapArea::CraterWest, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Online),
|
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Online),
|
||||||
@ -273,25 +261,45 @@ impl Maps {
|
|||||||
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Online),
|
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Online),
|
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Online),
|
||||||
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Online),
|
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Online),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
_ => unreachable!()
|
(Episode::Four, PlayerMode::Single) => {
|
||||||
};
|
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::CraterEast, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::CraterWest, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::CraterNorth, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::CraterInterior, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::SubDesert1, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Offline),
|
||||||
|
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Offline),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut maps = Maps {
|
#[derive(Error, Debug)]
|
||||||
enemy_data: map_variants.iter()
|
#[error("")]
|
||||||
.fold(Vec::new(), |mut enemy_data, map_variant| {
|
pub enum MapsError {
|
||||||
enemy_data.append(&mut enemy_data_from_map_data(map_variant, &room_mode.episode()));
|
InvalidMonsterId(usize),
|
||||||
enemy_data
|
InvalidObjectId(usize),
|
||||||
}),
|
}
|
||||||
object_data: map_variants.iter()
|
|
||||||
.flat_map(|map_variant| {
|
#[derive(Debug)]
|
||||||
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
|
pub struct Maps {
|
||||||
}).collect(),
|
map_variants: Vec<MapVariant>,
|
||||||
map_variants,
|
enemy_data: Vec<Option<MapEnemy>>,
|
||||||
};
|
object_data: Vec<Option<MapObject>>,
|
||||||
maps.roll_monster_appearance(rare_monster_table);
|
}
|
||||||
maps
|
|
||||||
|
impl Maps {
|
||||||
|
pub fn new(map_variants: Vec<MapVariant>, enemy_data: Vec<Option<MapEnemy>>, object_data: Vec<Option<MapObject>>) -> Maps {
|
||||||
|
Maps {
|
||||||
|
map_variants,
|
||||||
|
enemy_data,
|
||||||
|
object_data,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enemy_by_id(&self, id: usize) -> Result<MapEnemy, MapsError> {
|
pub fn enemy_by_id(&self, id: usize) -> Result<MapEnemy, MapsError> {
|
||||||
@ -314,9 +322,16 @@ impl Maps {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_quest_data(&mut self, enemies: Vec<Option<MapEnemy>>, objects: Vec<Option<MapObject>>, rare_monster_appear_table: &RareMonsterAppearTable) {
|
pub fn set_quest_data(&mut self,
|
||||||
self.enemy_data = enemies;
|
enemies: Vec<Option<MapEnemy>>,
|
||||||
self.roll_monster_appearance(rare_monster_appear_table);
|
objects: Vec<Option<MapObject>>,
|
||||||
|
rare_monster_table: &RareMonsterAppearTable,
|
||||||
|
event: Holiday)
|
||||||
|
{
|
||||||
|
self.enemy_data = enemies
|
||||||
|
.into_iter()
|
||||||
|
.map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event)))
|
||||||
|
.collect();
|
||||||
self.object_data = objects;
|
self.object_data = objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,10 +340,11 @@ impl Maps {
|
|||||||
let shiny: Vec<(usize, &Option<MapEnemy>)> = self.enemy_data.iter()
|
let shiny: Vec<(usize, &Option<MapEnemy>)> = self.enemy_data.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_,m)| {
|
.filter(|(_,m)| {
|
||||||
if m.is_some() {
|
match m {
|
||||||
m.unwrap().shiny
|
Some(m) => {
|
||||||
} else {
|
m.shiny
|
||||||
false
|
},
|
||||||
|
None => false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -341,22 +357,31 @@ impl Maps {
|
|||||||
}
|
}
|
||||||
rare_monsters
|
rare_monsters
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn roll_monster_appearance(&mut self, rare_monster_table: &RareMonsterAppearTable) {
|
pub fn generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> Maps {
|
||||||
self.enemy_data = self.enemy_data
|
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
|
||||||
.iter()
|
let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode());
|
||||||
// .map(|&x| if x.is_some() && x.unwrap().has_rare_appearance() {
|
Maps {
|
||||||
.map(|&x|
|
enemy_data: map_variants.iter()
|
||||||
if let Some(monster) = x {
|
.flat_map(|map_variant| {
|
||||||
if monster.has_rare_appearance() {
|
enemy_data_from_map_data(map_variant, &room_mode.episode())
|
||||||
Some(monster.roll_appearance_for_mission(rare_monster_table))
|
})
|
||||||
} else {
|
.map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event)))
|
||||||
Some(monster)
|
.collect(),
|
||||||
}
|
object_data: map_variants.iter()
|
||||||
} else {
|
.flat_map(|map_variant| {
|
||||||
x
|
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
|
||||||
}
|
}).collect(),
|
||||||
)
|
map_variants,
|
||||||
.collect();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn null_free_roam_maps(_room_mode: RoomMode, _event: Holiday) -> Maps {
|
||||||
|
Maps {
|
||||||
|
enemy_data: Default::default(),
|
||||||
|
object_data: Default::default(),
|
||||||
|
map_variants: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ use std::fs::File;
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::ship::room::{Difficulty, Episode, RoomMode};
|
use crate::room::{Difficulty, Episode, RoomMode};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -149,7 +149,7 @@ pub enum MonsterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct MonsterStats {
|
pub struct MonsterStats {
|
||||||
pub atp: u16,
|
pub atp: u16,
|
||||||
pub mst: u16,
|
pub mst: u16,
|
||||||
@ -1,13 +1,11 @@
|
|||||||
// TOOD: `pub(super) for most of these?`
|
// TOOD: `pub(super) for most of these?`
|
||||||
|
|
||||||
use std::io::{Read};
|
use std::io::Read;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
|
|
||||||
use crate::ship::room::Episode;
|
use crate::room::Episode;
|
||||||
|
use crate::area::MapArea;
|
||||||
// TODO: don't use *
|
|
||||||
use crate::ship::map::*;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
150
src/maps/src/room.rs
Normal file
150
src/maps/src/room.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#[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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,8 @@
|
|||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
// TODO: don't use *
|
// TODO: don't use *
|
||||||
use crate::ship::map::*;
|
//use crate::map::*;
|
||||||
|
use crate::area::MapArea;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum MapVariantMode {
|
pub enum MapVariantMode {
|
||||||
18
src/networking/Cargo.toml
Normal file
18
src/networking/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[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 }
|
||||||
@ -1,8 +1,9 @@
|
|||||||
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 crate::entity::account::UserAccountId;
|
use entity::account::UserAccountId;
|
||||||
use crate::entity::character::CharacterEntityId;
|
use 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);
|
||||||
@ -46,12 +47,13 @@ pub enum ShipMessage {
|
|||||||
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait InterserverActor {
|
pub trait InterserverActor: Clone {
|
||||||
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 action(&mut self, id: ServerId, msg: Self::RecvMessage) -> Result<Vec<(ServerId, Self::SendMessage)>, Self::Error>;
|
async fn on_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>);
|
||||||
}
|
}
|
||||||
4
src/networking/src/lib.rs
Normal file
4
src/networking/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod cipherkeys;
|
||||||
|
pub mod serverstate;
|
||||||
|
pub mod mainloop;
|
||||||
|
pub mod interserver;
|
||||||
296
src/networking/src/mainloop/client.rs
Normal file
296
src/networking/src/mainloop/client.rs
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
227
src/networking/src/mainloop/interserver.rs
Normal file
227
src/networking/src/mainloop/interserver.rs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
6
src/networking/src/mainloop/mod.rs
Normal file
6
src/networking/src/mainloop/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#![allow(unused_imports)]
|
||||||
|
mod client;
|
||||||
|
mod interserver;
|
||||||
|
|
||||||
|
pub use self::client::*;
|
||||||
|
pub use self::interserver::*;
|
||||||
@ -4,9 +4,10 @@ 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> {
|
pub enum OnConnect<S: SendServerPacket, C: PSOCipher> {
|
||||||
Packet(S),
|
Packet(S),
|
||||||
Cipher((Box<dyn PSOCipher + Send + Sync>, Box<dyn PSOCipher + Send + Sync>)),
|
Cipher(C, C),
|
||||||
|
//Cipher((Box<dyn PSOCipher + Send + Sync>, Box<dyn PSOCipher + Send + Sync>)),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RecvServerPacket: Sized + Sync {
|
pub trait RecvServerPacket: Sized + Sync {
|
||||||
@ -19,14 +20,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 {
|
pub trait ServerState: Clone {
|
||||||
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::PacketError>;
|
async fn on_connect(&mut self, id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, Self::PacketError>;
|
||||||
async fn handle(&mut self, id: ClientId, pkt: &Self::RecvPacket)
|
async fn handle(&mut self, id: ClientId, pkt: Self::RecvPacket) -> Result<Vec<(ClientId, Self::SendPacket)>, Self::PacketError>;
|
||||||
-> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)> + Send>, Self::PacketError>;
|
//-> Result<Box<dyn Iterator<Item = (ClientId, Self::SendPacket)>>, 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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
#[allow(clippy::module_inception)]
|
|
||||||
pub mod patch;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user