Compare commits

...

130 Commits

Author SHA1 Message Date
bdc5a5ae89 Merge pull request 'avoiding_doing_real_work' (#144) from avoiding_doing_real_work into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #144
2023-11-18 14:43:46 -05:00
156821cc6e more libpso updates
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-14 22:16:40 -07:00
b18ab064fa match libpso changes
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-14 22:08:04 -07:00
c851818813 remove unused macro 2023-11-14 20:57:41 -07:00
d4913eb6c6 looks like this is fixed 2023-11-14 20:44:47 -07:00
8f9f2b074c Merge pull request 'RightText support in the ship' (#143) from andy/right-text2 into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #143
2023-11-14 15:09:49 -05:00
a71692d62a RightText support in the ship
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-14 14:22:58 -04:00
6caeb0a8d9 Merge pull request 'cleanuppery' (#142) from cleanuppery into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #142
2023-11-14 11:54:53 -05:00
96dc459e5e another dead file
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-14 00:06:05 -07:00
40f5c80e3e this dep isn't even used
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-13 23:55:26 -07:00
d30a2bd3a3 move all the crate dirs back under src/
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-13 23:41:48 -07:00
8138cb13a2 move ship server into its own crate
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-13 23:27:12 -07:00
e359501be6 login_server crate
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-13 00:13:37 -07:00
d71287ef9a move patch_server to own crate
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-12 19:16:55 -07:00
d5ddfdb847 don't need these deps here
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-12 18:57:46 -07:00
95d23fceac clean up tests a bit by using itembuilder rather than using structs directly
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-12 16:33:27 -07:00
a7d6d9b5b0 missed one
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-12 00:57:25 -07:00
aa84768e58 this file has never been used why is it here 2023-11-12 00:47:59 -07:00
631a0c46c8 why did that arg ever exist anyway
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-12 00:46:02 -07:00
b42056419a Merge pull request 'cratesplitting' (#141) from cratesplitting into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #141
2023-11-12 02:44:11 -05:00
5fc23cd7ca fix a few warnings in tests
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-12 00:25:00 -07:00
2cede0077a try using cargo-prune instead of cargo-sweep
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-12 00:08:38 -07:00
8360e0343e try removing incremental build files to see if it helps ci disk usage
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 23:31:42 -07:00
ab0a844c8c don't load shops for real this time
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2023-11-11 23:11:58 -07:00
15c0ac50ee add nullable item shops for faster test runtimes
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-11 23:03:44 -07:00
c8813866a5 clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 22:05:40 -07:00
1be61556ab default to useful things, tests null things out on their own
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-11 22:02:36 -07:00
3cd4e9db10 null free roam maps by default
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-11 21:25:06 -07:00
d0866f5e83 maybe this will work for keeping the ci from filling the disk and making everyone sad
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 19:58:21 -07:00
7b26fdc28d clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 19:06:22 -07:00
af629695a5 further speed up tests by not loading drop charts by default
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-11-11 18:17:18 -07:00
183fba9afe speed up tests by not loading quests every time
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 17:11:55 -07:00
7afe44a520 remove redundant patch.rs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 16:21:35 -07:00
3dc48e9d43 clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-11 16:18:20 -07:00
402667d627 remove some redundant files
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-11-11 15:20:43 -07:00
2570749fbb remove another ci cache
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-11 15:18:21 -07:00
9cff5ad088 clippy cleanup
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-11 14:41:00 -07:00
d73a07391b break out the rest of everything into its own crate
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-11 14:28:41 -07:00
926483b6de remove cargo cache for now, not enough disk space on my poverty ci vps
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-11 00:07:56 -07:00
5d98fb8cc9 move leveltable to stats crate 2023-11-10 23:48:06 -07:00
72aa0f7f13 clean up toplevel cargo.toml a bit
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-10 23:38:56 -07:00
3b28d650ee crates for shops and stats
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-11-10 23:31:47 -07:00
2c930d4333 maybe specifying only 1 test job at a time will keep it from running out of memory
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-10 23:17:37 -07:00
6ac3ad50dc oh wow this has been wrong this entire time
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-10 22:29:50 -07:00
fc62be05e2 cleanup 2023-11-10 22:19:45 -07:00
5bf9ef59bf actually this shouldn't be needed 2023-11-10 22:19:40 -07:00
fbeaf5e93e genuflect towards the great clippy
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-10 22:04:20 -07:00
622c6e598d break out entity and maps into separate crates
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-10 21:37:23 -07:00
16041640c2 get elseware to compile on latest rust 2023-11-10 13:35:16 -07:00
1f7dd1eafe Merge pull request 'room notes, etc' (#139) from more_itemnotes into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #139
2023-02-18 21:01:46 -05:00
f5c71ced17 this was hidden in some debug code I was skipping over
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-18 18:09:11 -07:00
aa036ed5d7 oh yeah I kinda need this migration
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-02-18 18:06:40 -07:00
e989bd939a remove the cargo.lock it only caused suffering
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-02-18 18:01:04 -07:00
0c074e45ee clippy 2023-02-18 17:59:26 -07:00
99baa03582 small cleanup 2023-02-18 17:59:26 -07:00
23d784e2af roomnotery 2023-02-18 17:59:26 -07:00
044e36d37a update cargolock 2023-02-18 17:59:26 -07:00
a00a7d7191 char is not "char" reeeeee 2023-02-18 17:59:26 -07:00
455ce9316b update to sqlx 6.2 2023-02-18 17:59:26 -07:00
318aeeba2b minor room refactor, room db table, add room ids to itemnotes 2023-02-18 17:59:26 -07:00
3ba0cc9c55 initial room entities 2023-02-18 17:59:26 -07:00
580bed3cb0 fix dumb itemid problem 2023-02-18 17:58:39 -07:00
0f0adb05a3 Merge pull request 'andy/floor-limit' (#134) from andy/floor-limit into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #134
2023-02-17 20:21:33 -05:00
3749155a7d lock for ci
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-18 00:14:08 +00:00
b012692505 rebase sillyness
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-02-12 14:32:12 +00:00
00bc001297 clipster
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-12 02:49:08 +00:00
602b6e96f7 floor item limit reached 2023-02-12 02:47:04 +00:00
e8494c5bbd Merge pull request 'use GATs to remove lifetime on postgresgateway' (#132) from gat_fuckery into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #132
2023-02-11 19:41:50 -05:00
4a6b2dfb8f cleanup
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-11 12:56:49 -07:00
414672252c use GATs to remove lifetime on postgresgateway
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-02-11 11:51:32 -07:00
f8943d5bd2 Merge pull request 'shared banks' (#130) from shared_banks into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #130
2023-02-07 16:00:26 -05:00
4e4f5e4ee2 clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-06 23:16:46 -07:00
1028120048 test withdrawing a bit more 2023-02-06 23:16:28 -07:00
b8f05a0ea5 shared bank tests 2023-02-06 23:08:09 -07:00
7c3acc8b3d properly order bank items before assigning item ids 2023-02-06 22:38:13 -07:00
a7c1968a48 properly commit shared bank meseta in inmemory transaction 2023-02-06 20:42:48 -07:00
1bd88fdaaf itembuilder to clean up item creation in tests 2023-02-06 18:07:13 -07:00
9f91ada947 fix inmemory get_bank_meseta 2023-02-06 18:06:25 -07:00
fc5d318ac3 shared banks wow
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-05 18:34:04 -07:00
8b4eb146ac shared bank db migration 2023-02-05 01:54:03 -07:00
0bf84db189 bankname -> bankidentifier 2023-02-05 01:53:38 -07:00
ddab0ab6a3 use scapedoll
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-04 22:04:09 -07:00
a63e7fe28c chatcommand base 2023-02-03 16:03:33 -07:00
818b92b962 Merge pull request 'change tech menu' (#129) from change_tech_menu into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #129
2023-02-03 03:18:02 -05:00
d16b2418f3 update tech menu
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-03 00:15:11 -07:00
960e9f367e handle thing that apparently errored beyond my expectations
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-02 23:28:55 -07:00
effc82d782 Merge pull request 'impl grinders, fix mag pbs, fix bank thing, etc' (#128) from use_grinders into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #128
2023-02-02 11:37:55 -05:00
45996e9d01 clippy
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-02 00:10:15 -07:00
77f69a9d23 fix more dumb meseta things
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-02-01 23:30:53 -07:00
6399cbbb0e fix mag pbs maybe I dunno
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-01 19:21:30 -07:00
85d9fc9ce3 a test for bank item_id stuff 2023-01-31 20:00:18 -07:00
0d3161e1b4 more error context 2023-01-31 20:00:03 -07:00
aac2e429ed tests for item_id parity issues
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-31 19:23:20 -07:00
9123c4842b start of fixing item_id parity issues 2023-01-31 19:22:11 -07:00
cccf385ee9 recv pkt trace -> info 2023-01-31 19:18:18 -07:00
52da851d8a stray println
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-31 00:13:08 -07:00
28c0073dff implement grinders
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-31 00:08:56 -07:00
f27c121539 take ref to weapon modifier 2023-01-31 00:06:34 -07:00
6a2703ed6c update inmemory gateway to reflect how inventory actually works 2023-01-31 00:06:08 -07:00
f3bfa658cd Merge pull request 'bunch of small fixes' (#127) from make_actually_work into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #127
2023-01-30 22:07:37 -05:00
5b8f4fd087 disconnect a client if any error occurs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-30 18:50:05 -07:00
8f6b562c22 clippy3
Some checks reported errors
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build encountered an error
2023-01-30 18:19:04 -07:00
42ef2ebcbb clippy2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-01-30 18:12:43 -07:00
f3682d0b82 clippy
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-01-30 18:05:36 -07:00
6ef5ea6681 set mag owner when creating a char
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-01-30 15:45:34 -07:00
1fb0abce09 more info on incorrect tool usage 2023-01-30 12:19:41 -07:00
7144ede73f more green boxes
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-30 18:33:41 +00:00
939c511f92 add some atomizers
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-30 10:43:12 -07:00
aa019d4ea9 pick the newest character for a slot when recreating
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-29 23:57:17 -07:00
8157db4c69 set tech level correctly when reading from postgres 2023-01-29 23:56:28 -07:00
13c6592438 you can use techs now wow
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-29 23:05:42 -07:00
7bd385d580 anyhowing continues 2023-01-29 22:15:49 -07:00
f09c73611f fix some user setting entity stuff 2023-01-29 22:15:29 -07:00
d495ec97f2 fix this keyconfig nonsense 2023-01-29 20:41:12 -07:00
31ebc1af32 actually store meseta bank interaction 2023-01-29 17:06:48 -07:00
fe472eaae2 lvl value in level up pkt is 0-based
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-29 16:34:29 -07:00
0b641424da don't start with 300 meseta in bank 2023-01-29 16:34:11 -07:00
f5fea8540e fix problem where item drops were not pickup-able
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-29 15:57:58 -07:00
f80e37c438 oh forgot to add these migrations 2023-01-29 15:32:09 -07:00
58da1f87f6 inital drop test 2023-01-29 15:31:49 -07:00
220d3e7185 update exp tests with new map builder tech
Some checks failed
continuous-integration/drone/push Build is failing
2023-01-29 13:08:07 -07:00
bbaf39fa0b rename default_map_variants 2023-01-29 13:07:41 -07:00
33b80d7235 mapbuilder trait -> just a function 2023-01-29 12:48:42 -07:00
b831f9b83b drop table builder 2023-01-29 12:35:46 -07:00
e5f13b6cb7 why did I make this generic over rng anyway its not like I was ever gonna use that 2023-01-29 11:51:55 -07:00
62387366e4 fix anyhow-inflicted tests 2023-01-29 01:00:29 -07:00
08efcce6f7 mapbuilder 2023-01-29 00:56:56 -07:00
ab8c5e6688 more anyhow 2023-01-29 00:55:48 -07:00
a2686e2be8 anyhow 2023-01-28 20:12:20 -07:00
43579f7058 fix postgres stuff to get to a game 2023-01-28 00:56:31 -07:00
159 changed files with 7164 additions and 8821 deletions

View File

@ -1,12 +1,24 @@
---
kind: pipeline
type: docker
name: test elseware
name: test elseware
concurrency:
limit: 1
environment:
CARGO_INCREMENTAL: false
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
image: rustlang/rust:nightly
volumes:
@ -33,7 +45,7 @@ steps:
- name: target-cache
path: /drone/src/target
commands:
- cargo test
- cargo test --jobs 1
volumes:
- name: cache

2388
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,46 @@ version = "0.1.0"
authors = ["Jake Probst <jake.probst@gmail.com>"]
edition = "2021"
[dependencies]
libpso = { git = "http://git.sharnoth.com/jake/libpso" }
[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" }
async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
futures = "0.3.5"
rand = "0.7.3"
@ -27,9 +65,33 @@ ages-prs = "0.1"
async-trait = "0.1.51"
async-recursion= "1.0.0"
lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] }
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_macros = "0.19"
anyhow = { version = "1.0.47", features = ["backtrace"] }
anyhow = { version = "1.0.68", features = ["backtrace"] }
[dependencies]
entity = { workspace = true }
maps = { workspace = true }
networking = { workspace = true }
patch_server = { workspace = true }
login_server = { workspace = true }
ship_server = { workspace = true }
libpso = { workspace = true }
async-std = { workspace = true }
bcrypt = { workspace = true }
chrono = { workspace = true }
fern = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
[dev-dependencies]
drops = { workspace = true }
shops = { workspace = true }
items = { workspace = true }
quests = { workspace = true }
stats = { workspace = true }
async-trait = { workspace = true }

View File

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

View File

@ -1,8 +1,8 @@
use log::{info};
use elseware::entity::gateway::postgres::PostgresGateway;
use elseware::login::login::LoginServerState;
use elseware::login::character::CharacterServerState;
use elseware::common::interserver::AuthToken;
use entity::gateway::postgres::PostgresGateway;
use login_server::login::LoginServerState;
use login_server::character::CharacterServerState;
use networking::interserver::AuthToken;
fn main() {
let colors = fern::colors::ColoredLevelConfig::new()
@ -38,17 +38,17 @@ fn main() {
let login_state = LoginServerState::new(entity_gateway.clone(), charserv_ip);
let login_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(login_state, elseware::login::login::LOGIN_PORT).await;
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await;
});
let char_state = CharacterServerState::new(entity_gateway, AuthToken(shipgate_token));
let sub_char_state = char_state.clone();
let character_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
});
let inter_character_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_listen(char_state, elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_listen(char_state, login_server::login::COMMUNICATION_PORT).await;
});
info!("[auth/character] starting server");

View File

@ -1,18 +1,18 @@
use std::net::Ipv4Addr;
use log::{info};
use elseware::common::interserver::AuthToken;
use elseware::login::login::LoginServerState;
use elseware::login::character::CharacterServerState;
use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd};
use elseware::ship::ship::{ShipServerStateBuilder, ShipEvent};
use networking::interserver::AuthToken;
use login_server::login::LoginServerState;
use login_server::character::CharacterServerState;
use patch_server::{PatchServerState, generate_patch_tree, load_config, load_motd};
use ship_server::ShipServerStateBuilder;
#[allow(unused_imports)]
use elseware::entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
use elseware::entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
use elseware::entity::character::NewCharacterEntity;
use elseware::entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
use elseware::entity::item;
use maps::Holiday;
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() {
let colors = fern::colors::ColoredLevelConfig::new()
@ -52,7 +52,7 @@ fn main() {
for i in 0..5 {
let fake_user = NewUserAccountEntity {
email: format!("fake{}@email.com", i),
email: format!("fake{i}@email.com"),
username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) },
password: bcrypt::hash("qwer", 5).unwrap(),
guildcard: i + 1,
@ -64,18 +64,18 @@ fn main() {
};
let fake_user = entity_gateway.create_user(fake_user).await.unwrap();
entity_gateway.create_user_settings(NewUserSettingsEntity::new(fake_user.id)).await.unwrap();
let mut character = NewCharacterEntity::new(fake_user.id, 1);
let mut character = NewCharacterEntity::new(fake_user.id);
character.name = format!("Test Char {}", i*2);
let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
let mut character = NewCharacterEntity::new(fake_user.id, 1);
entity_gateway.set_bank_meseta(&character.id, &item::BankIdentifier::Character, item::Meseta(999999)).await.unwrap();
let mut character = NewCharacterEntity::new(fake_user.id);
character.slot = 2;
character.name = "ItemRefactor".into();
character.exp = 80000000;
let character = entity_gateway.create_character(character).await.unwrap();
entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
entity_gateway.set_bank_meseta(&character.id, &item::BankIdentifier::Character, item::Meseta(999999)).await.unwrap();
for _ in 0..3 {
entity_gateway.create_item(
@ -329,7 +329,7 @@ fn main() {
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_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");
@ -338,73 +338,73 @@ fn main() {
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_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(patch_state, patch_config.port).await;
networking::mainloop::run_server(patch_state, patch_config.port).await;
});
info!("[auth] starting server");
let login_state = LoginServerState::new(entity_gateway.clone(), "127.0.0.1".parse().unwrap());
let login_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(login_state, elseware::login::login::LOGIN_PORT).await;
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await;
});
info!("[character] starting server");
let char_state = CharacterServerState::new(entity_gateway.clone(), AuthToken("".into()));
let sub_char_state = char_state.clone();
let character_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
});
let sub_char_state = char_state.clone();
let inter_character_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_listen(sub_char_state, elseware::login::login::COMMUNICATION_PORT).await;
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())
.ip(Ipv4Addr::new(127,0,0,1))
.port(elseware::ship::ship::SHIP_PORT)
.event(ShipEvent::Halloween)
.port(ship_server::SHIP_PORT)
.event(Holiday::Halloween)
.gateway(entity_gateway.clone())
.build();
let sub_ship_state = ship_state.clone();
let ship_loop1 = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
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 {
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
});
let ship_state = ShipServerStateBuilder::default()
.name("EU/Dylath-Leen".into())
.ip(Ipv4Addr::new(127,0,0,1))
.port(elseware::ship::ship::SHIP_PORT+2000)
.event(ShipEvent::Christmas)
.port(ship_server::SHIP_PORT+2000)
.event(Holiday::Christmas)
.gateway(entity_gateway.clone())
.build();
let sub_ship_state = ship_state.clone();
let ship_loop2 = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+2000).await;
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 {
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
});
let ship_state = ShipServerStateBuilder::default()
.name("JP/Thalarion".into())
.ip(Ipv4Addr::new(127,0,0,1))
.port(elseware::ship::ship::SHIP_PORT+3000)
.port(ship_server::SHIP_PORT+3000)
.gateway(entity_gateway.clone())
.build();
let sub_ship_state = ship_state.clone();
let ship_loop3 = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+3000).await;
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 {
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
});
futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop,

View File

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

View File

@ -1,7 +1,7 @@
use log::{info};
use elseware::entity::gateway::postgres::PostgresGateway;
use elseware::ship::ship::ShipServerStateBuilder;
use elseware::common::interserver::AuthToken;
use log::info;
use entity::gateway::postgres::PostgresGateway;
use ship_server::ShipServerStateBuilder;
use networking::interserver::AuthToken;
fn main() {
let colors = fern::colors::ColoredLevelConfig::new()
@ -40,7 +40,7 @@ fn main() {
let ship_state = ShipServerStateBuilder::default()
.name(ship_name)
.ip(ip)
.port(elseware::ship::ship::SHIP_PORT)
.port(ship_server::SHIP_PORT)
.gateway(entity_gateway)
.auth_token(AuthToken(shipgate_token))
.build();
@ -49,10 +49,10 @@ fn main() {
let sub_ship_state = ship_state.clone();
let ship_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await;
});
let inter_ship_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_connect(ship_state, shipgate_ip, elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(ship_state, shipgate_ip, login_server::login::COMMUNICATION_PORT).await;
});
info!("[auth/character] starting server");

20
src/client/Cargo.toml Normal file
View 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 }

View File

@ -6,15 +6,20 @@ use futures::future::BoxFuture;
use libpso::packet::ship::*;
use libpso::packet::login::Session;
use crate::common::serverstate::ClientId;
use crate::entity::account::{UserAccountEntity, UserSettingsEntity};
use crate::entity::character::CharacterEntity;
use crate::entity::item;
use networking::serverstate::ClientId;
use entity::account::{UserAccountEntity, UserSettingsEntity};
use entity::character::CharacterEntity;
use entity::item;
use crate::ship::ship::ShipError;
use crate::ship::items;
use crate::ship::map::MapArea;
use crate::ship::shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
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)]
@ -36,7 +41,7 @@ impl Clients {
.into_inner())
}
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, ShipError>
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a,
@ -46,14 +51,14 @@ impl Clients {
.await;
let client = clients
.get(&client_id)
.ok_or_else(|| ShipError::ClientNotFound(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, ShipError>
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a,
@ -69,23 +74,20 @@ impl Clients {
for (cindex, client_id) in client_ids.iter().enumerate() {
let c = clients
.get(client_id)
.ok_or_else(|| ShipError::ClientNotFound(*client_id))?
.ok_or(ClientError::NotFound(*client_id))?
.read()
.await;
client_states[cindex].write(c);
}
let client_states = unsafe {
// TODO: this should just be a normal transmute but due to compiler limitations it
// does not yet work with const generics
// https://github.com/rust-lang/rust/issues/61956
std::mem::transmute_copy::<_, [RwLockReadGuard<ClientState>; N]>(&client_states)
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, ShipError>
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
where
T: Send,
F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a,
@ -95,7 +97,7 @@ impl Clients {
.await;
let mut client = clients
.get(&client_id)
.ok_or_else(|| ShipError::ClientNotFound(client_id))?
.ok_or(ClientError::NotFound(client_id))?
.write()
.await;

View File

@ -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
View 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 }

View File

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

View File

@ -1,14 +1,14 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::Rng;
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::item::armor::{ArmorType, Armor};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::item_stats::{armor_stats, ArmorStats};
use entity::character::SectionID;
use entity::item::armor::{ArmorType, Armor};
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
use stats::items::{armor_stats, ArmorStats};
#[derive(Debug, Serialize, Deserialize)]
@ -46,8 +46,8 @@ impl GenericArmorTable {
}
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,
self.rank_rates.rank3, self.rank_rates.rank4]).unwrap();
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();
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);
match armor_level {
@ -80,8 +80,8 @@ impl GenericArmorTable {
}
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,
self.slot_rates.slot3, self.slot_rates.slot4]).unwrap();
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();
slot_weights.sample(rng)
}

View File

@ -1,14 +1,14 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::Rng;
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::item::shield::{ShieldType, Shield};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::item_stats::{shield_stats, ShieldStats};
use entity::item::shield::{ShieldType, Shield};
use entity::character::SectionID;
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
use stats::items::{shield_stats, ShieldStats};
#[derive(Debug, Serialize, Deserialize)]
@ -36,8 +36,8 @@ impl GenericShieldTable {
}
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,
self.rank_rates.rank3, self.rank_rates.rank4]).unwrap();
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();
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);
match shield_level {

View File

@ -1,14 +1,14 @@
use std::collections::BTreeMap;
use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::Rng;
use rand::seq::IteratorRandom;
use crate::entity::item::unit::{UnitType, Unit, UnitModifier};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::item_stats::{unit_stats, UnitStats};
use entity::character::SectionID;
use entity::item::unit::{UnitType, Unit, UnitModifier};
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
use stats::items::{unit_stats, UnitStats};

View File

@ -1,14 +1,14 @@
use std::collections::{HashMap, BTreeMap};
use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::Rng;
use rand::distributions::{WeightedIndex, Distribution};
use rand::seq::SliceRandom;
use crate::entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use entity::character::SectionID;
use entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
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> {
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) {
0 => return None,
1 => Attribute::Native,
@ -253,7 +253,7 @@ impl AttributeTable {
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 percent = ((value + 1) * 5) as i8;
@ -477,7 +477,7 @@ impl GenericWeaponTable {
let pattern = std::cmp::min(area % ratio.inc, 3);
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)
}

298
src/drops/src/lib.rs Normal file
View 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);
}
}

View File

@ -1,20 +1,20 @@
use std::collections::HashMap;
use rand::Rng;
use serde::{Serialize, Deserialize};
use crate::entity::item::weapon::{Weapon, WeaponType};
use crate::entity::item::armor::{Armor, ArmorType};
use crate::entity::item::shield::{Shield, ShieldType};
use crate::entity::item::unit::{Unit, UnitType};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::mag::{Mag, MagType};
use crate::entity::character::SectionID;
use crate::ship::monster::MonsterType;
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::drops::generic_weapon::AttributeTable;
use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::ship::drops::generic_shield::GenericShieldTable;
use entity::item::weapon::{Weapon, WeaponType};
use entity::item::armor::{Armor, ArmorType};
use entity::item::shield::{Shield, ShieldType};
use entity::item::unit::{Unit, UnitType};
use entity::item::tool::{Tool, ToolType};
use entity::item::mag::{Mag, MagType};
use entity::character::SectionID;
use maps::monster::MonsterType;
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
use crate::generic_weapon::AttributeTable;
use crate::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable;
type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>;
@ -50,9 +50,9 @@ impl RareDropItem {
}
struct RareDropRate {
rate: f32,
item: RareDropItem
pub struct RareDropRate {
pub rate: f32,
pub item: RareDropItem
}
@ -71,30 +71,41 @@ pub struct RareDropTable {
shield_stats: GenericShieldTable,
}
fn load_default_monster_rates(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> HashMap<MonsterType, Vec<RareDropRate>> {
let cfg: HashMap<String, Vec<RareDropConfigEntity>> = load_data_file(episode, difficulty, section_id, "rare_rate.toml");
cfg.into_iter()
.map(|(monster, drops)| {
let monster = monster.parse().unwrap();
let drops = drops.into_iter().map(|drop| {
RareDropRate {
rate: drop.rate,
item: RareDropItem::from_string(drop.item),
}
}).collect();
(monster, drops)
}).collect()
}
impl RareDropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
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 {
rates,
rates: load_default_monster_rates(episode, difficulty, section_id),
attribute_table: AttributeTable::new(episode, difficulty, section_id),
armor_stats: GenericArmorTable::new(episode, difficulty, section_id),
shield_stats: GenericShieldTable::new(episode, difficulty, section_id),
}
}
pub fn builder() -> RareDropTableBuilder {
RareDropTableBuilder {
rates: None,
attribute_table: None,
armor_stats: None,
shield_stats: None,
}
}
pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
match item {
RareDropItem::Weapon(weapon) => {
@ -155,3 +166,46 @@ impl RareDropTable {
})
}
}
pub struct RareDropTableBuilder {
rates: Option<HashMap<MonsterType, Vec<RareDropRate>>>,
attribute_table: Option<AttributeTable>,
armor_stats: Option<GenericArmorTable>,
shield_stats: Option<GenericShieldTable>,
}
// TODO: add the rest of these later I just need these ones right now
impl RareDropTableBuilder {
pub fn rates(mut self, rates: HashMap<MonsterType, Vec<RareDropRate>>) -> RareDropTableBuilder {
self.rates = Some(rates);
self
}
#[must_use]
pub fn rate(mut self, monster_type: MonsterType, drop_rate: RareDropRate) -> RareDropTableBuilder {
match &mut self.rates {
Some(rates) => {
rates.entry(monster_type)
.or_insert(Vec::new())
.push(drop_rate);
},
None => {
let mut rates = HashMap::default();
rates.insert(monster_type, vec![drop_rate]);
self.rates = Some(rates);
}
}
self
}
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
RareDropTable {
rates: self.rates.unwrap_or_else(|| load_default_monster_rates(episode, difficulty, section_id)),
attribute_table: self.attribute_table.unwrap_or_else(|| AttributeTable::new(episode, difficulty, section_id)),
armor_stats: self.armor_stats.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)),
shield_stats: self.shield_stats.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)),
}
}
}

View File

@ -3,11 +3,11 @@ use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::item::tech::{Technique, TechniqueDisk};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use entity::item::tech::{Technique, TechniqueDisk};
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use entity::character::SectionID;
use crate::{ItemDropType, load_data_file};

View File

@ -1,14 +1,14 @@
use std::collections::{BTreeMap};
use std::collections::BTreeMap;
use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::Rng;
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::item::tool::{Tool, ToolType};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::drops::tech_table::TechniqueTable;
use entity::item::tool::{Tool, ToolType};
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use entity::character::SectionID;
use crate::{ItemDropType, load_data_file};
use crate::tech_table::TechniqueTable;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, enum_utils::FromStr)]

23
src/entity/Cargo.toml Normal file
View 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 }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,32 +1,31 @@
use std::convert::From;
use thiserror::Error;
use futures::Future;
use futures::future::{Future, BoxFuture};
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::account::*;
use crate::character::*;
use crate::item::*;
use crate::room::*;
// TODO: better granularity?
//#[derive(Error, Debug)]
#[derive(Error, Debug)]
#[error("")]
pub enum GatewayError {
#[error("unknown error")]
Error,
#[error("postgres error {0}")]
PgError(#[from] sqlx::Error)
}
#[async_trait::async_trait]
pub trait EntityGateway: Send + Sync {
type Transaction: EntityGatewayTransaction + Clone;
type Transaction<'t>: EntityGatewayTransaction + Clone where Self: 't;
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
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
F: FnOnce(Self::Transaction) -> Fut + Send,
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
R: Send,
E: From<GatewayError>,
Self: Sized
{
unimplemented!();
@ -97,7 +96,7 @@ pub trait EntityGateway: Send + Sync {
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!();
}
@ -105,7 +104,7 @@ pub trait EntityGateway: Send + Sync {
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!();
}
@ -113,7 +112,7 @@ pub trait EntityGateway: Send + Sync {
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!();
}
@ -133,11 +132,11 @@ pub trait EntityGateway: Send + Sync {
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!();
}
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!();
}
@ -148,6 +147,14 @@ pub trait EntityGateway: Send + Sync {
async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> {
unimplemented!();
}
async fn create_room(&mut self, _room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
unimplemented!();
}
async fn add_room_note(&mut self, _room_id: RoomEntityId, _note: RoomNote) -> Result<(), GatewayError> {
unimplemented!();
}
}

View File

@ -1,11 +1,12 @@
use std::collections::BTreeMap;
use std::convert::TryInto;
use futures::Future;
use futures::future::{Future, BoxFuture};
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use async_std::sync::{Arc, Mutex};
@ -31,7 +32,7 @@ where
// functions here have been skipped as they are not used in transactions, add as needed
#[async_trait::async_trait]
impl EntityGateway for InMemoryGatewayTransaction {
type Transaction = InMemoryGatewayTransaction;
type Transaction<'t> = InMemoryGatewayTransaction where Self: 't;
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
self.working_gateway.create_user(user).await
@ -72,6 +73,10 @@ impl EntityGateway for InMemoryGatewayTransaction {
}
}
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
self.original_gateway.save_user_settings(settings).await
}
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
copy_if_needed(&mut *self.working_gateway.characters.lock().await,
&*self.original_gateway.characters.lock().await,
@ -100,7 +105,7 @@ impl EntityGateway for InMemoryGatewayTransaction {
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
}
@ -113,11 +118,11 @@ impl EntityGateway for InMemoryGatewayTransaction {
}
}
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError> {
match self.working_gateway.get_character_bank(char_id, bank_name).await {
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
match self.working_gateway.get_character_bank(char_id, bank_identifier).await {
Ok(bank) => Ok(bank),
Err(_) => {
self.original_gateway.get_character_bank(char_id, bank_name).await
self.original_gateway.get_character_bank(char_id, bank_identifier).await
}
}
}
@ -126,11 +131,10 @@ impl EntityGateway for InMemoryGatewayTransaction {
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> {
self.working_gateway.set_character_bank(char_id, bank, bank_name).await
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
self.working_gateway.set_character_bank(char_id, bank, bank_identifier).await
}
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
match self.working_gateway.get_character_equips(char_id).await {
Ok(equips) => Ok(equips),
@ -157,15 +161,15 @@ impl EntityGateway for InMemoryGatewayTransaction {
}
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
self.working_gateway.set_bank_meseta(char_id, bank, meseta).await
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
self.working_gateway.set_bank_meseta(char_id, bank_identifier, meseta).await
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
match self.working_gateway.get_bank_meseta(char_id, bank).await {
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
match self.working_gateway.get_bank_meseta(char_id, bank_identifier).await {
Ok(meseta) => Ok(meseta),
Err(_) => {
self.original_gateway.get_bank_meseta(char_id, bank).await
self.original_gateway.get_bank_meseta(char_id, bank_identifier).await
}
}
}
@ -197,9 +201,11 @@ impl EntityGatewayTransaction for InMemoryGatewayTransaction {
self.original_gateway.characters.lock().await.extend(self.working_gateway.characters.lock().await.clone());
self.original_gateway.character_meseta.lock().await.extend(self.working_gateway.character_meseta.lock().await.clone());
self.original_gateway.bank_meseta.lock().await.extend(self.working_gateway.bank_meseta.lock().await.clone());
self.original_gateway.shared_bank_meseta.lock().await.extend(self.working_gateway.shared_bank_meseta.lock().await.clone());
self.original_gateway.items.lock().await.extend(self.working_gateway.items.lock().await.clone());
self.original_gateway.inventories.lock().await.extend(self.working_gateway.inventories.lock().await.clone());
self.original_gateway.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.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());
@ -209,16 +215,24 @@ impl EntityGatewayTransaction for InMemoryGatewayTransaction {
}
}
#[derive(Clone)]
enum InventoryItemElement {
Individual(ItemEntityId),
Stacked(Vec<ItemEntityId>),
}
#[derive(Clone)]
pub struct InMemoryGateway {
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
bank_meseta: Arc<Mutex<BTreeMap<(CharacterEntityId, BankName), Meseta>>>,
bank_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
shared_bank_meseta: Arc<Mutex<BTreeMap<(UserAccountId, BankName), Meseta>>>,
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, Vec<InventoryItemElement>>>>,
character_banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
shared_banks: Arc<Mutex<BTreeMap<(UserAccountId, BankName), BankEntity>>>,
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
@ -233,9 +247,11 @@ impl Default for InMemoryGateway {
characters: Arc::new(Mutex::new(BTreeMap::new())),
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
shared_bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
items: Arc::new(Mutex::new(BTreeMap::new())),
inventories: Arc::new(Mutex::new(BTreeMap::new())),
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())),
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
@ -302,52 +318,57 @@ fn apply_modifiers(items: &BTreeMap<ItemEntityId, ItemEntity>,
#[async_trait::async_trait]
impl EntityGateway for InMemoryGateway {
type Transaction = InMemoryGatewayTransaction;
type Transaction<'t> = InMemoryGatewayTransaction where Self: 't;
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
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
F: FnOnce(Self::Transaction) -> Fut + Send,
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
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();
Box::pin(async move {
let users = self.users.lock().await.clone();
let user_settings = self.user_settings.lock().await.clone();
let characters = self.characters.lock().await.clone();
let character_meseta = self.character_meseta.lock().await.clone();
let bank_meseta = self.bank_meseta.lock().await.clone();
let shared_bank_meseta = self.shared_bank_meseta.lock().await.clone();
let items = self.items.lock().await.clone();
let inventories = self.inventories.lock().await.clone();
let character_banks = self.character_banks.lock().await.clone();
let shared_banks = self.shared_banks.lock().await.clone();
let equips = self.equips.lock().await.clone();
let mag_modifiers = self.mag_modifiers.lock().await.clone();
let weapon_modifiers = self.weapon_modifiers.lock().await.clone();
let trades = self.trades.lock().await.clone();
let working_gateway = InMemoryGateway {
users: Arc::new(Mutex::new(users)),
user_settings: Arc::new(Mutex::new(user_settings)),
characters: Arc::new(Mutex::new(characters)),
character_meseta: Arc::new(Mutex::new(character_meseta)),
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
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 working_gateway = InMemoryGateway {
users: Arc::new(Mutex::new(users)),
user_settings: Arc::new(Mutex::new(user_settings)),
characters: Arc::new(Mutex::new(characters)),
character_meseta: Arc::new(Mutex::new(character_meseta)),
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
shared_bank_meseta: Arc::new(Mutex::new(shared_bank_meseta)),
items: Arc::new(Mutex::new(items)),
inventories: Arc::new(Mutex::new(inventories)),
character_banks: Arc::new(Mutex::new(character_banks)),
shared_banks: Arc::new(Mutex::new(shared_banks)),
equips: Arc::new(Mutex::new(equips)),
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
trades: Arc::new(Mutex::new(trades)),
};
let transaction = InMemoryGatewayTransaction {
working_gateway,
original_gateway: self.clone(),
};
let transaction = InMemoryGatewayTransaction {
working_gateway,
original_gateway: self.clone(),
};
let (transaction, result) = func(transaction).await?;
let (transaction, result) = func(transaction).await?;
transaction.commit().await?;
Ok(result)
transaction.commit().await?;
Ok(result)
})
}
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
@ -419,6 +440,12 @@ impl EntityGateway for InMemoryGateway {
.ok_or(GatewayError::Error)
}
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> {
let mut user_settings = self.user_settings.lock().await;
user_settings.insert(settings.id, settings.clone());
Ok(())
}
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
let characters = self.characters.lock().await;
const NONE: Option<CharacterEntity> = None;
@ -453,8 +480,6 @@ impl EntityGateway for InMemoryGateway {
materials: character.materials,
tech_menu: character.tech_menu,
option_flags: character.option_flags,
keyboard_config: character.keyboard_config,
gamepad_config: character.gamepad_config,
playtime: 0,
};
characters.insert(new_character.id, new_character.clone());
@ -515,11 +540,11 @@ impl EntityGateway for InMemoryGateway {
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
.entry(*item_id)
.or_insert_with(Vec::new)
.push(modifier);
.push(modifier.clone());
Ok(())
}
@ -531,30 +556,106 @@ impl EntityGateway for InMemoryGateway {
Ok(inventories
.iter()
.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))
.unwrap_or_default())
}
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
let banks = self.banks.lock().await;
Ok(banks
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, b)| b.clone())
.unwrap_or_default())
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let character_banks = self.character_banks.lock().await;
Ok(character_banks
.iter()
.find(|(id, _)| **id == *char_id)
.map(|(_, b)| b.clone())
.unwrap_or_default())
},
BankIdentifier::Shared(bank_name) => {
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
let shared_banks = self.shared_banks.lock().await;
Ok(shared_banks
.iter()
.find(|((id, name), _)| *id == user_id && *name == *bank_name)
.map(|(_, b)| b.clone())
.unwrap_or_default())
}
}
}
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
let mut inventories = self.inventories.lock().await;
inventories.insert(*char_id, inventory.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(())
}
// TOOD: impl bank name
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> {
let mut banks = self.banks.lock().await;
banks.insert(*char_id, bank.clone());
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let mut character_banks = self.character_banks.lock().await;
character_banks.insert(*char_id, bank.clone());
},
BankIdentifier::Shared(bank_name) => {
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
let mut shared_banks = self.shared_banks.lock().await;
shared_banks.insert((user_id, bank_name.clone()), bank.clone());
}
}
Ok(())
}
@ -589,19 +690,58 @@ impl EntityGateway for InMemoryGateway {
}
}
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
let mut bank_meseta = self.bank_meseta.lock().await;
bank_meseta.insert((*char_id, bank.clone()), meseta);
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let mut bank_meseta = self.bank_meseta.lock().await;
bank_meseta.insert(*char_id, meseta);
}
BankIdentifier::Shared(bank_name) => {
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
self.shared_bank_meseta
.lock()
.await
.insert((user_id, bank_name.clone()), meseta);
}
}
Ok(())
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
let mut bank_meseta = self.bank_meseta.lock().await;
if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank.clone())) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
match bank_identifier {
BankIdentifier::Character => {
let mut bank_meseta = self.bank_meseta.lock().await;
if let Some(meseta) = bank_meseta.get_mut(char_id) {
Ok(*meseta)
}
else {
Err(GatewayError::Error)
}
},
BankIdentifier::Shared(bank_name) => {
let mut shared_bank_meseta = self.shared_bank_meseta.lock().await;
let user_id = self.characters
.lock()
.await
.iter()
.find(|(id, _)| **id == *char_id)
.unwrap()
.1
.user_id;
if let Some(meseta) = shared_bank_meseta.get_mut(&(user_id, bank_name.clone())) {
Ok(*meseta)
}
else {
Ok(Meseta(0))
}
}
}
}
@ -627,4 +767,21 @@ impl EntityGateway for InMemoryGateway {
Err(GatewayError::Error)
}
}
// I do not care to replicate this in testing
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
Ok(RoomEntity {
id: RoomEntityId(0),
name: room.name,
section_id: room.section_id,
episode: room.episode,
difficulty: room.difficulty,
mode: room.mode,
})
}
// I do not care to replicate this in testing
async fn add_room_note(&mut self, _room_id: RoomEntityId, _note: RoomNote) -> Result<(), GatewayError> {
Ok(())
}
}

View File

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

View File

@ -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
);

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
);

View 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
);

View 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
);

View File

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

View File

@ -4,10 +4,13 @@ use std::convert::Into;
use serde::{Serialize, Deserialize};
use libpso::character::settings;
use libpso::util::vec_to_array;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::ship::map::MapArea;
use crate::account::*;
use crate::character::*;
use crate::item::*;
use crate::room::*;
use maps::area::MapArea;
use maps::room::{Episode, Difficulty};
use maps::monster::MonsterType;
#[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount {
@ -49,8 +52,8 @@ pub struct PgUserSettings {
id: i32,
user_account: i32,
blocked_users: Vec<u8>, //[u32; 0x1E],
keyboard_config: Vec<u8>, //[u8; 0x16C],
gamepad_config: Vec<u8>, //[u8; 0x38],
key_config: Vec<u8>, //[u8; 0x16C],
joystick_config: Vec<u8>, //[u8; 0x38],
option_flags: i32,
shortcuts: Vec<u8>, //[u8; 0xA40],
symbol_chats: Vec<u8>, //[u8; 0x4E0],
@ -64,8 +67,8 @@ impl From<PgUserSettings> for UserSettingsEntity {
user_id: UserAccountId(other.user_account as u32),
settings: settings::UserSettings {
blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
keyboard_config: vec_to_array(other.keyboard_config),
gamepad_config: vec_to_array(other.gamepad_config),
keyboard_config: vec_to_array(other.key_config),
gamepad_config: vec_to_array(other.joystick_config),
option_flags: other.option_flags as u32,
shortcuts: vec_to_array(other.shortcuts),
symbol_chats: vec_to_array(other.symbol_chats),
@ -217,8 +220,6 @@ pub struct PgCharacter {
tp: i16,
tech_menu: Vec<u8>,
keyboard_config: Vec<u8>,
gamepad_config: Vec<u8>,
playtime: i32,
}
@ -246,13 +247,13 @@ impl From<PgCharacter> for CharacterEntity {
prop_y: other.prop_y,
},
techs: CharacterTechniques {
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t + 1)) ).collect()
},
config: CharacterConfig {
raw_data: vec_to_array(other.config)
},
info_board: CharacterInfoboard {
board: libpso::utf8_to_utf16_array!(other.infoboard, 172),
board: libpso::util::utf8_to_utf16_array(other.infoboard),
},
guildcard: CharacterGuildCard {
description: other.guildcard,
@ -270,12 +271,6 @@ impl From<PgCharacter> for CharacterEntity {
tech_menu: CharacterTechMenu {
tech_menu: vec_to_array(other.tech_menu)
},
keyboard_config: CharacterKeyboardConfig {
keyboard_config: vec_to_array(other.keyboard_config)
},
gamepad_config: CharacterGamepadConfig {
gamepad_config: vec_to_array(other.gamepad_config)
},
playtime: other.playtime as u32,
}
}
@ -585,6 +580,16 @@ pub enum PgItemNoteDetail {
},
EnemyDrop {
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,
x: f32,
y: f32,
@ -600,14 +605,19 @@ pub enum PgItemNoteDetail {
y: f32,
z: f32,
},
Consumed,
Consumed {
character_id: u32,
},
FedToMag {
character_id: u32,
mag: u32,
},
BoughtAtShop {
character_id: u32,
},
SoldToShop,
SoldToShop {
character_id: u32,
},
Trade {
trade_id: u32,
character_to: u32,
@ -615,11 +625,14 @@ pub enum PgItemNoteDetail {
},
Withdraw {
character_id: u32,
bank: String,
bank: BankIdentifier,
},
Deposit {
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 {
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,
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,
x,y,z,
},
@ -642,14 +663,19 @@ impl From<ItemNote> for PgItemNoteDetail {
map_area,
x,y,z,
},
ItemNote::Consumed => PgItemNoteDetail::Consumed,
ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed {
character_id: character_id.0,
},
ItemNote::FedToMag{character_id, mag} => PgItemNoteDetail::FedToMag{
character_id: character_id.0,
mag: mag.0
},
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
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 {
trade_id: trade_id.0,
character_to: character_to.0,
@ -658,15 +684,20 @@ impl From<ItemNote> for PgItemNoteDetail {
ItemNote::Withdraw{character_id, bank} => {
PgItemNoteDetail::Withdraw {
character_id: character_id.0,
bank: bank.0,
bank,
}
},
ItemNote::Deposit{character_id, bank} => {
PgItemNoteDetail::Deposit {
character_id: character_id.0,
bank: bank.0,
bank,
}
}
},
ItemNote::FloorLimitReached { map_area } => {
PgItemNoteDetail::FloorLimitReached {
map_area,
}
},
}
}
}
@ -677,8 +708,16 @@ impl From<PgItemNoteDetail> for ItemNote {
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
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),
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,
x,y,z,
},
@ -690,14 +729,19 @@ impl From<PgItemNoteDetail> for ItemNote {
map_area,
x,y,z,
},
PgItemNoteDetail::Consumed => ItemNote::Consumed,
PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::FedToMag{character_id, mag} => ItemNote::FedToMag{
character_id: CharacterEntityId(character_id),
mag: ItemEntityId(mag)
},
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
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 {
trade_id: TradeId(trade_id),
character_to: CharacterEntityId(character_to),
@ -705,11 +749,14 @@ impl From<PgItemNoteDetail> for ItemNote {
},
PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw {
character_id: CharacterEntityId(character_id),
bank: BankName(bank),
bank,
},
PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit {
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(),
}
}
}

View File

@ -1,15 +1,16 @@
// this lint is currently bugged and suggests incorrect code https://github.com/rust-lang/rust-clippy/issues/9123
#![allow(clippy::explicit_auto_deref)]
use std::convert::{From, TryFrom, Into};
use futures::{Future, TryStreamExt};
use async_std::stream::StreamExt;
use std::convert::{From, Into};
use futures::future::{Future, BoxFuture};
use futures::stream::{StreamExt, FuturesOrdered};
use async_std::sync::{Arc, Mutex};
use libpso::character::guildcard;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use super::models::*;
use sqlx::postgres::PgPoolOptions;
@ -18,7 +19,7 @@ use sqlx::Connection;
mod embedded {
use refinery::embed_migrations;
embed_migrations!("src/entity/gateway/postgres/migrations");
embed_migrations!("src/gateway/postgres/migrations");
}
@ -36,9 +37,7 @@ impl<'t> EntityGatewayTransaction for PostgresTransaction<'t> {
self
}
//async fn commit(self: Box<Self>) -> Result<(), GatewayError> {
async fn commit(self) -> Result<(), GatewayError> {
//self.pgtransaction.lock().await.commit().await?;
Arc::try_unwrap(self.pgtransaction)
.unwrap()
.into_inner()
@ -50,13 +49,12 @@ impl<'t> EntityGatewayTransaction for PostgresTransaction<'t> {
#[derive(Clone)]
pub struct PostgresGateway<'t> {
pub struct PostgresGateway {
pool: sqlx::Pool<sqlx::Postgres>,
_t: std::marker::PhantomData<&'t ()>,
}
impl<'t> PostgresGateway<'t> {
pub fn new(host: &str, dbname: &str, username: &str, password: &str) -> PostgresGateway<'t> {
impl PostgresGateway {
pub fn new(host: &str, dbname: &str, username: &str, password: &str) -> PostgresGateway {
let mut conn = refinery::config::Config::new(refinery::config::ConfigDbType::Postgres)
.set_db_host(host)
.set_db_user(username)
@ -67,12 +65,11 @@ impl<'t> PostgresGateway<'t> {
let pool = async_std::task::block_on(async move {
PgPoolOptions::new()
.max_connections(5)
.connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap()
.connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap()
});
PostgresGateway {
pool,
_t: Default::default(),
}
}
}
@ -93,7 +90,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
.bind(id.0 as i32)
.fetch(conn);
weapon_modifiers.for_each(|modifier| {
weapon_modifiers.for_each(|modifier| async move {
if let Ok(modifier) = modifier {
weapon.apply_modifier(&modifier.modifier);
}
@ -101,7 +98,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
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
from mag_modifier
left join item on item.id = cast (modifier ->> 'FeedMag' as integer)
@ -111,7 +108,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
.bind(id.0 as i32)
.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 modifier: mag::MagModifier = modifier.0.into();
match modifier {
@ -128,6 +125,8 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
mag.change_owner(class, section_id)
},
}
mag
}).await;
ItemDetail::Mag(mag)
@ -141,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>
{
let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password, activated) values ($1, $2, $3, $4) returning *;")
@ -155,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>
{
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?;
Ok(user.into())
}
@ -176,8 +200,8 @@ async fn save_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> R
.bind(&user.password)
.bind(user.banned_until)
.bind(user.muted_until)
.bind(user.flags)
.bind(user.id.0)
.bind(user.flags as i32)
.bind(user.id.0 as i32)
.execute(conn).await?;
Ok(())
}
@ -186,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)
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.keyboard_config.to_vec())
.bind(settings.settings.gamepad_config.to_vec())
@ -201,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>
{
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?;
Ok(settings.into())
}
@ -212,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.keyboard_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.symbol_chats.to_vec())
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.id.0)
.fetch_one(conn).await?;
.bind(settings.id.0 as i32)
.execute(conn).await?;
Ok(())
}
async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError>
{
let q = r#"insert into player_character
(user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags)
(user_account, slot, name, exp, class,
section_id, costume, skin, face, head,
hair, hair_r, hair_g, hair_b, prop_x,
prop_y, techs, config, infoboard, guildcard,
power, mind, def, evade, luck,
hp, tp, tech_menu, option_flags, playtime)
values
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)
($1, $2, $3, $4, $5,
$6, $7, $8, $9, $10,
$11, $12, $13, $14, $15,
$16, $17, $18, $19, $20,
$21, $22, $23, $24, $25,
$26, $27, $28, $29, $30)
returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q)
.bind(char.user_id.0)
.bind(char.user_id.0 as i32)
.bind(char.slot as i16)
.bind(char.name)
.bind(char.exp as i32)
@ -259,6 +292,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
.bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec())
.bind(char.option_flags as i32)
.bind(0)
.fetch_one(conn).await?;
Ok(character.into())
@ -266,17 +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>
{
let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot")
.bind(user.id.0)
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 as i32)
.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>
@ -285,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,
hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23,
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29, playtime=$30
where id=$31;"#;
where id=$31;"#;
sqlx::query(q)
.bind(char.user_id.0) // $1
.bind(char.user_id.0 as i32) // $1
.bind(char.slot as i16) // $2
.bind(&char.name) // $3
.bind(char.exp as i32) // $4
@ -316,7 +350,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
.bind(char.materials.tp as i16) // $27
.bind(char.tech_menu.tech_menu.to_vec()) // $28
.bind(char.option_flags as i32) // $29
.bind(char.playtime as i32) // $20
.bind(char.playtime as i32) // $30
.bind(char.id.0 as i32) // $31
.execute(conn).await?;
Ok(())
@ -337,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>
{
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)))
.execute(conn).await?;
Ok(())
@ -346,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>
{
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})))
.execute(conn).await?;
Ok(())
@ -355,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>
{
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))))
.execute(conn).await?;
Ok(())
@ -364,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>
{
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))))
.execute(conn).await?;
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);")
.bind(item_id.0)
.bind(item_id.0 as i32)
.bind(sqlx::types::Json(modifier))
.execute(conn).await?;
Ok(())
@ -381,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>
{
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")
.bind(char_id.0)
.fetch_one(&mut t).await?;
.bind(char_id.0 as i32)
.fetch_one(&mut **conn.lock().await).await?;
// TODO: inefficient
let mut real_inventory = Vec::new();
for inv_item in inventory.items.0.into_iter() {
match inv_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 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())?)
Ok(InventoryEntity::new(
inventory.items.0
.into_iter()
.map(move |item| {
let conn = conn.clone();
async move {
fetch_item(&mut **conn.lock().await, item, InventoryItemEntity::Individual, InventoryItemEntity::Stacked).await
}
real_inventory.push(InventoryItemEntity::Stacked(stacked_item));
}
}
}
Ok(InventoryEntity::new(real_inventory))
})
.collect::<FuturesOrdered<_>>()
.collect::<Vec<_>>()
.await
.into_iter()
.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 bank = sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1 and name = $2")
.bind(char_id.0)
.bind(bank_name.0.clone())
.fetch_one(&mut t).await?;
// TODO: inefficient
let mut real_bank = Vec::new();
for bank_item in bank.items.0.into_iter() {
match bank_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 t).await
.map(|item| item.into())
.map(|item| apply_item_modifications(&mut t, item))?
.await;
real_bank.push(BankItemEntity::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())
.map(|item| apply_item_modifications(&mut t, item))?
.await)
}
real_bank.push(BankItemEntity::Stacked(stacked_item));
}
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit
let bank = match bank_identifier {
BankIdentifier::Character => {
sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1")
.bind(char_id.0 as i32)
.fetch_one(&mut **conn.lock().await).await?
},
BankIdentifier::Shared(bank_name) => {
sqlx::query_as::<_, PgInventoryEntity>("select player_character.id as pchar, shared_bank.items as items from shared_bank
join player_character on shared_bank.user_account = player_character.user_account
where player_character.id = $1 and shared_bank.name = $2")
.bind(char_id.0 as i32)
.bind(&bank_name.0)
.fetch_optional(&mut **conn.lock().await)
.await?
.unwrap_or_else(|| PgInventoryEntity {
pchar: char_id.0 as i32,
items: sqlx::types::Json::default(),
})
}
}
};
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>
@ -469,15 +492,15 @@ async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &Charac
.collect::<Vec<_>>();
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))
.execute(conn)
.await?;
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()
.map(|item| {
match item {
@ -491,19 +514,33 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
})
.collect::<Vec<_>>();
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, $3) on conflict (pchar, name) do update set items = $2")
.bind(char_id.0)
.bind(sqlx::types::Json(bank))
.bind(bank_name.0.clone())
.execute(conn)
.await?;
match bank_identifier {
BankIdentifier::Character => {
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, '') on conflict (pchar, name) do update set items = $2")
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(bank))
.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(())
}
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")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn)
.await?;
@ -514,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)
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.armor.map(|i| i.0 as i32))
.bind(equips.shield.map(|i| i.0 as i32))
@ -528,10 +565,11 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character
Ok(())
}
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")
.bind(char_id.0)
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2")
.bind(char_id.0 as i32)
.bind(meseta.0 as i32)
.execute(conn)
.await?;
@ -542,41 +580,70 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
{
#[derive(sqlx::FromRow)]
struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#)
.bind(char_id.0)
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#)
.bind(char_id.0 as i32)
.fetch_one(conn)
.await?;
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")
.bind(char_id.0)
.bind(meseta.0 as i32)
.bind(bank.0.clone())
.execute(conn)
.await?;
match bank_identifier {
BankIdentifier::Character => {
sqlx::query("insert into bank_meseta values ($1, '', $2) on conflict (pchar, bank) do update set meseta = $2")
.bind(char_id.0 as i32)
.bind(meseta.0 as i32)
.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(())
}
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)]
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)
.bind(bank.0.clone())
.fetch_one(conn)
.await?;
let meseta = match bank_identifier {
BankIdentifier::Character => {
sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1"#)
.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))
}
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 *;"#)
.bind(char_id1.0)
.bind(char_id2.0)
.bind(char_id1.0 as i32)
.bind(char_id2.0 as i32)
.fetch_one(conn)
.await?;
Ok(trade.into())
@ -584,31 +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>
{
sqlx::query_as::<_, PgTradeEntity>(r#"update player_character set playtime=$2 where id=$1;"#)
.bind(char_id.0)
.bind(playtime)
sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#)
.bind(char_id.0 as i32)
.bind(playtime as i32)
.execute(conn)
.await?;
Ok(())
}
async fn create_room(conn: &mut sqlx::PgConnection, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
sqlx::query_as::<_, PgRoomEntity>("insert into room (name, section_id, mode, episode, difficulty) values ($1, $2, $3, $4, $5) returning *")
.bind(room.name)
.bind(u8::from(room.section_id) as i8)
.bind(u8::from(room.mode) as i8)
.bind(u8::from(room.episode) as i8)
.bind(u8::from(room.difficulty) as i8)
.fetch_one(conn)
.await
.map(|room| room.into())
.map_err(|err| err.into())
}
async fn add_room_note(conn: &mut sqlx::PgConnection, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
sqlx::query("insert into room_note (room, note) values ($1, $2)")
.bind(room_id.0 as i32)
.bind(sqlx::types::Json(note))
.execute(conn)
.await?;
Ok(())
}
#[async_trait::async_trait]
impl<'t> EntityGateway for PostgresGateway<'t> {
type Transaction = PostgresTransaction<'t>;
impl EntityGateway for PostgresGateway {
type Transaction<'t> = PostgresTransaction<'t> where Self: 't;
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
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
F: FnOnce(Self::Transaction) -> Fut + Send,
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
R: Send,
E: From<GatewayError>,
{
let transaction = PostgresTransaction {
pgtransaction: Arc::new(Mutex::new(self.pool.begin().await.map_err(|_| ()).unwrap()))
};
let (transaction, result) = func(transaction).await.map_err(|_| ()).unwrap();
transaction.commit().await.map_err(|_| ()).unwrap();
Ok(result)
Box::pin(async move {
let transaction = PostgresTransaction {
pgtransaction: Arc::new(Mutex::new(self.pool.begin().await?))
};
let (transaction, result) = func(transaction).await?;
transaction.commit().await?;
Ok(result)
})
}
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
@ -679,7 +769,7 @@ impl<'t> EntityGateway for PostgresGateway<'t> {
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
}
@ -687,16 +777,16 @@ impl<'t> EntityGateway for PostgresGateway<'t> {
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> {
get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_name).await
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_identifier).await
}
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
}
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> {
set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_name).await
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_identifier).await
}
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
@ -715,12 +805,12 @@ impl<'t> EntityGateway for PostgresGateway<'t> {
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> {
set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank, meseta).await
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_identifier, meseta).await
}
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
get_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank).await
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_identifier).await
}
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
@ -730,12 +820,20 @@ impl<'t> EntityGateway for PostgresGateway<'t> {
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
set_character_playtime(&mut *self.pool.acquire().await?, char_id, playtime).await
}
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
create_room(&mut *self.pool.acquire().await?, room).await
}
async fn add_room_note(&mut self, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
add_room_note(&mut *self.pool.acquire().await?, room_id, note).await
}
}
#[async_trait::async_trait]
impl<'c> EntityGateway for PostgresTransaction<'c> {
type Transaction = PostgresTransaction<'c>;
type Transaction<'t> = PostgresTransaction<'c> where Self: 't;
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
create_user(&mut *self.pgtransaction.lock().await, user).await
@ -805,7 +903,7 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
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.lock().await, item_id, modifier).await
}
@ -813,16 +911,16 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
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> {
get_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank_name).await
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
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> {
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> {
set_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank, bank_name).await
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
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> {
@ -841,12 +939,12 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
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> {
set_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank, meseta).await
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
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> {
get_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank).await
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
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> {
@ -856,5 +954,13 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
set_character_playtime(&mut *self.pgtransaction.lock().await, char_id, playtime).await
}
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
create_room(&mut *self.pgtransaction.lock().await, room).await
}
async fn add_room_note(&mut self, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
add_room_note(&mut *self.pgtransaction.lock().await, room_id, note).await
}
}

View File

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

View File

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

View File

@ -9,9 +9,11 @@ pub mod mag;
pub mod esweapon;
use serde::{Serialize, Deserialize};
use crate::entity::character::CharacterEntityId;
use crate::ship::map::MapArea;
use crate::ship::drops::ItemDropType;
use crate::character::CharacterEntityId;
use crate::room::RoomEntityId;
use maps::area::MapArea;
use maps::monster::MonsterType;
//use crate::ship::drops::ItemDropType;
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
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)]
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)]
pub enum ItemNote {
CharacterCreation {
@ -29,8 +37,16 @@ pub enum ItemNote {
},
EnemyDrop {
character_id: CharacterEntityId,
//monster_type: MonsterType,
//droprate: f32,
room_id: RoomEntityId,
monster_type: MonsterType,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: CharacterEntityId,
room_id: RoomEntityId,
map_area: MapArea,
x: f32,
y: f32,
@ -46,15 +62,19 @@ pub enum ItemNote {
y: f32,
z: f32,
},
Consumed, // TODO: character_id
Consumed {
character_id: CharacterEntityId,
},
FedToMag {
//character_id: CharacterEntityId,
character_id: CharacterEntityId,
mag: ItemEntityId,
},
BoughtAtShop {
character_id: CharacterEntityId,
},
SoldToShop,
SoldToShop {
character_id: CharacterEntityId,
},
Trade {
trade_id: TradeId,
character_to: CharacterEntityId,
@ -62,11 +82,14 @@ pub enum ItemNote {
},
Withdraw {
character_id: CharacterEntityId,
bank: BankName,
bank: BankIdentifier,
},
Deposit {
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] {
match self {
ItemDetail::Weapon(w) => w.as_bytes(),

View File

@ -1,4 +1,4 @@
use crate::entity::item::ItemEntityId;
use crate::item::ItemEntityId;
use serde::{Serialize, Deserialize};
#[derive(Debug, Copy, Clone)]
@ -1479,44 +1479,52 @@ impl Weapon {
}
pub fn apply_modifier(&mut self, modifier: &WeaponModifier) {
if let WeaponModifier::Tekked{special, percent, grind} = modifier {
match special {
TekSpecialModifier::Plus => {
self.special = self.special.map(|special| {
special.rank_up()
});
},
TekSpecialModifier::Minus => {
self.special = self.special.map(|special| {
special.rank_down()
});
},
TekSpecialModifier::Neutral => {
},
}
for i in 0..3 {
self.attrs[i] = self.attrs[i].map(|mut attr| {
match percent {
TekPercentModifier::PlusPlus => {
attr.value += 10;
},
TekPercentModifier::Plus => {
attr.value += 5;
},
TekPercentModifier::MinusMinus => {
attr.value -= 10;
},
TekPercentModifier::Minus => {
attr.value -= 5;
},
TekPercentModifier::Neutral => {
match modifier {
WeaponModifier::Tekked{special, percent, grind} => {
match special {
TekSpecialModifier::Plus => {
self.special = self.special.map(|special| {
special.rank_up()
});
},
TekSpecialModifier::Minus => {
self.special = self.special.map(|special| {
special.rank_down()
});
},
TekSpecialModifier::Neutral => {
},
}
for i in 0..3 {
self.attrs[i] = self.attrs[i].map(|mut attr| {
match percent {
TekPercentModifier::PlusPlus => {
attr.value += 10;
},
TekPercentModifier::Plus => {
attr.value += 5;
},
TekPercentModifier::MinusMinus => {
attr.value -= 10;
},
TekPercentModifier::Minus => {
attr.value -= 5;
},
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;
}
}

View File

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

83
src/entity/src/room.rs Normal file
View 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
View 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
View 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 }

View File

@ -1,44 +1,48 @@
// TODO: replace various u32s and usizes denoting item amounts for ItemAmount(u32) for consistency
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemNote};
use crate::ClientItemId;
use entity::item::{Meseta, ItemNote};
use async_std::sync::Arc;
use std::future::Future;
use futures::future::BoxFuture;
use std::pin::Pin;
use std::iter::IntoIterator;
use anyhow::Context;
use libpso::packet::{ship::Message, messages::GameMessage};
use crate::ship::map::MapArea;
use crate::ship::ship::SendShipPacket;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::ship::items::bank::{BankItem, BankItemDetail};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use crate::ship::items::floor::{FloorItem, FloorItemDetail};
use crate::ship::items::apply_item::{apply_item, ApplyItemAction};
use crate::entity::item::{ItemDetail, NewItemEntity, TradeId};
use crate::entity::item::tool::Tool;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::packet::builder;
use crate::ship::location::AreaClient;
type BoxFuture<T> = Pin<Box<dyn Future<Output=T> + Send>>;
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::gateway::{EntityGateway, EntityGatewayTransaction};
use entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier};
use entity::item::tool::Tool;
use entity::room::RoomEntityId;
use maps::area::MapArea;
use crate::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::bank::{BankItem, BankItemDetail};
use crate::inventory::{InventoryItem, InventoryItemDetail};
use crate::floor::{FloorItem, FloorItemDetail};
use crate::apply_item::{apply_item, ApplyItemAction};
use shops::ShopItem;
use drops::{ItemDrop, ItemDropType};
use location::AreaClient;
use maps::monster::MonsterType;
pub enum TriggerCreateItem {
Yes,
No
}
pub(super) fn take_item_from_floor<EG, TR>(
#[derive(Clone)]
pub enum CreateItem {
Individual(AreaClient, ClientItemId, IndividualItemDetail),
Stacked(AreaClient, ClientItemId, Tool, usize),
}
pub(super) fn take_item_from_floor<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + 'a
where
EG: EntityGateway + Send,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction): (ItemStateProxy, TR) , _| {
Box::pin(async move {
@ -51,13 +55,13 @@ where
}
}
pub(super) fn add_floor_item_to_inventory<EG, TR>(
pub(super) fn add_floor_item_to_inventory<'a, EG, TR>(
character: &CharacterEntity
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<Result<((ItemStateProxy, TR), TriggerCreateItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), TriggerCreateItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'a,
{
let character = character.clone();
move |(mut item_state, transaction), floor_item| {
@ -97,21 +101,49 @@ where
}
pub(super) fn take_item_from_inventory<EG, TR>(
pub(super) fn remove_item_from_inventory<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItemDetail), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id).await?;
let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoFloorItem(item_id))?;
let item = inventory.remove_item(&item_id, amount)
.await
.ok_or_else(|| ItemStateError::NoInventoryItem(item_id))
.with_context(|| format!("{inventory:#?}"))?;
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await;
Ok(((item_state, transaction), item))
})
}
}
pub(super) fn take_item_from_inventory<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway + 'a,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id).await?;
let item = inventory.take_item(&item_id, amount)
.await
.ok_or_else(|| ItemStateError::NoInventoryItem(item_id))
.with_context(|| format!("{inventory:#?}"))?;
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await;
@ -122,15 +154,15 @@ where
}
pub(super) fn add_inventory_item_to_shared_floor<EG, TR>(
pub(super) fn add_inventory_item_to_shared_floor<'a, EG, TR>(
character_id: CharacterEntityId,
map_area: MapArea,
drop_position: (f32, f32, f32),
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), inventory_item| {
Box::pin(async move {
@ -156,14 +188,14 @@ where
}
pub(super) fn take_meseta_from_inventory<EG, TR>(
pub(super) fn take_meseta_from_inventory<'a, EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
@ -177,14 +209,14 @@ where
}
}
pub(super) fn add_meseta_to_inventory<EG, TR>(
pub(super) fn add_meseta_to_inventory<'a, EG, TR>(
character_id: CharacterEntityId,
amount: u32
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
@ -198,16 +230,16 @@ where
}
}
pub(super) fn add_meseta_to_shared_floor<EG, TR>(
pub(super) fn add_meseta_to_shared_floor<'a, EG, TR>(
character_id: CharacterEntityId,
amount: u32,
map_area: MapArea,
drop_position: (f32, f32)
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), _| {
Box::pin(async move {
@ -229,40 +261,42 @@ where
}
}
pub(super) fn take_meseta_from_bank<EG, TR>(
pub(super) fn take_meseta_from_bank<'a, EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id).await?;
bank.remove_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
transaction.gateway().set_bank_meseta(&character_id, &bank.identifier, bank.meseta).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ()))
})
}
}
pub(super) fn add_meseta_from_bank_to_inventory<EG, TR>(
pub(super) fn add_meseta_from_bank_to_inventory<'a, EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut inventory = item_state.inventory(&character_id).await?;
inventory.add_meseta_no_overflow(amount)?;
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
item_state.set_inventory(inventory).await;
Ok(((item_state, transaction), ()))
})
@ -270,20 +304,21 @@ where
}
pub(super) fn add_meseta_to_bank<EG, TR>(
pub(super) fn add_meseta_to_bank<'a, EG, TR>(
character_id: CharacterEntityId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id).await?;
bank.add_meseta(amount)?;
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
transaction.gateway().set_bank_meseta(&character_id, &bank.identifier, bank.meseta).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ()))
})
@ -291,21 +326,23 @@ where
}
pub(super) fn take_item_from_bank<EG, TR>(
pub(super) fn take_item_from_bank<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), BankItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), BankItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id).await?;
let item = bank.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoBankItem(item_id))?;
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?;
let item = bank.take_item(&item_id, amount)
.await
.ok_or_else(|| ItemStateError::NoBankItem(item_id))?;
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.identifier).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), item))
@ -313,28 +350,28 @@ where
}
}
pub(super) fn add_bank_item_to_inventory<EG, TR>(
pub(super) fn add_bank_item_to_inventory<'a, EG, TR>(
character: &CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), BankItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
let character = character.clone();
move |(mut item_state, transaction), bank_item| {
let character = character.clone();
Box::pin(async move {
let bank_name = item_state.bank(&character.id).await?.name;
let bank_identifier = item_state.bank(&character.id).await?.identifier;
let mut inventory = item_state.inventory(&character.id).await?;
let character_id = character.id;
let transaction = bank_item.with_entity_id(transaction, |mut transaction, entity_id| {
let bank_name = bank_name.clone();
let bank_identifier = bank_identifier.clone();
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::Withdraw {
character_id,
bank: bank_name,
bank: bank_identifier,
}).await?;
Ok(transaction)
}}).await?;
@ -364,49 +401,48 @@ where
}
pub(super) fn add_inventory_item_to_bank<EG, TR>(
pub(super) fn add_inventory_item_to_bank<'a, EG, TR>(
character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), inventory_item| {
Box::pin(async move {
let mut bank = item_state.bank(&character_id).await?;
let bank_name = bank.name.clone();
let bank_identifier = bank.identifier.clone();
let mut transaction = inventory_item.with_entity_id(transaction, move |mut transaction, entity_id| {
let bank_name = bank_name.clone();
let bank_identifier = bank_identifier.clone();
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::Deposit {
character_id,
bank: bank_name,
bank: bank_identifier,
}).await?;
Ok(transaction)
}}).await?;
bank.add_inventory_item(inventory_item)?;
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?;
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.identifier).await?;
item_state.set_bank(bank).await;
Ok(((item_state, transaction), ()))
})
}
}
pub(super) fn equip_inventory_item<EG, TR>(
pub(super) fn equip_inventory_item<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId,
equip_slot: u8,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
@ -421,14 +457,14 @@ where
}
pub(super) fn unequip_inventory_item<EG, TR>(
pub(super) fn unequip_inventory_item<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
@ -444,14 +480,14 @@ where
pub(super) fn sort_inventory_items<EG, TR>(
pub(super) fn sort_inventory_items<'a, EG, TR>(
character_id: CharacterEntityId,
item_ids: Vec<ClientItemId>,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
let item_ids = item_ids.clone();
@ -467,13 +503,13 @@ where
}
pub(super) fn use_consumed_item<EG, TR>(
pub(super) fn use_consumed_item<'a, EG, TR>(
character: &CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), ItemStateError>>
) -> impl Fn((ItemStateProxy, TR), InventoryItemDetail)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), anyhow::Error>>
where
EG: EntityGateway + Clone + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
EG: EntityGateway + Clone + 'a,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
let character = character.clone();
move |(mut item_state, transaction), inventory_item| {
@ -481,7 +517,9 @@ where
Box::pin(async move {
let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed).await?;
transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed {
character_id: character.id,
}).await?;
Ok(transaction)
}}).await?;
@ -493,14 +531,14 @@ where
}
pub(super) fn feed_mag_item<EG, TR>(
pub(super) fn feed_mag_item<'a, EG, TR>(
character: CharacterEntity,
mag_item_id: ClientItemId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), tool| {
let character = character.clone();
@ -516,7 +554,7 @@ where
let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| {
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag {
//character_id: character.id,
character_id: character.id,
mag: mag_entity_id,
}).await?;
transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?;
@ -551,10 +589,10 @@ pub(super) fn add_bought_item_to_inventory<'a, EG, TR>(
item_id: ClientItemId,
amount: u32,
) -> impl Fn((ItemStateProxy, TR), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Send + 'a>>
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Send + 'a>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
Box::pin(async move {
@ -581,7 +619,8 @@ where
tool,
})
};
inventory.add_item(inventory_item)?.1
inventory.add_item(inventory_item.clone())
.with_context(|| format!("inventory {inventory:?}\nitem {inventory_item:?}"))?.1
},
item_detail => {
let item_entity = transaction.gateway().create_item(NewItemEntity {
@ -598,7 +637,8 @@ where
item: item_detail,
})
};
inventory.add_item(inventory_item)?.1
inventory.add_item(inventory_item.clone())
.with_context(|| format!("inventory {inventory:?}\nitem {inventory_item:?}"))?.1
},
};
@ -610,13 +650,13 @@ where
}
pub(super) fn sell_inventory_item<EG, TR>(
pub(super) fn sell_inventory_item<'a, EG, TR>(
character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), inventory_item| {
Box::pin(async move {
@ -626,7 +666,9 @@ where
let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop).await?;
transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop {
character_id,
}).await?;
Ok(transaction)
}}).await?;
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
@ -643,7 +685,7 @@ async fn iterate_inner<'a, EG, TR, I, O, T, F, FR>(
mut input: Vec<I>,
func: F,
arg: T,
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
) -> Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>
where
'a: 'async_recursion,
EG: EntityGateway,
@ -651,9 +693,9 @@ where
I: Send,
O: Send,
T: Clone + Send + Sync,
F: Fn(I) -> FR + Send + Sync + Clone + 'static,
F: Fn(I) -> FR + Send + Sync + Clone + 'a,
FR: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
{
let item = match input.pop() {
Some(item) => item,
@ -669,20 +711,20 @@ where
Ok((state, output))
}
pub(super) fn iterate<EG, TR, I, O, T, F, FR>(
pub(super) fn iterate<'a, EG, TR, I, O, T, F, FR>(
input: Vec<I>,
func: F,
) -> impl Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
O: Send,
I: Send + Clone + 'static + std::fmt::Debug,
T: Send + Clone + 'static + std::fmt::Debug,
F: Fn(I) -> FR + Send + Sync + Clone + 'static,
F: Fn(I) -> FR + Send + Sync + Clone + 'a,
FR: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
T: Clone + Send + Sync,
{
move |(item_state, transaction), arg| {
@ -701,16 +743,16 @@ async fn foreach_inner<'a, EG, TR, O, T, F, I>(
state: (ItemStateProxy, TR),
mut input: I,
func: Arc<F>,
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
) -> Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>
where
'a: 'async_recursion,
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
O: Send,
T: Send,
F: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
I: Iterator<Item = T> + Send + Sync + 'static,
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
I: Iterator<Item = T> + Send + Sync + 'a,
{
let item = match input.next() {
Some(item) => item,
@ -725,19 +767,19 @@ where
Ok((state, output))
}
pub(super) fn foreach<EG, TR, O, T, F, I>(
pub(super) fn foreach<'a, EG, TR, O, T, F, I>(
func: F
) -> impl Fn((ItemStateProxy, TR), I)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
O: Send,
T: Send + Clone + 'static + std::fmt::Debug,
F: Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync + 'static,
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync + 'a,
T: Send + Sync,
I: IntoIterator<Item = T> + Send + Sync + 'static,
I: IntoIterator<Item = T> + Send + Sync + 'a,
I::IntoIter: Send + Sync,
{
let func = Arc::new(func);
@ -754,10 +796,10 @@ where
pub(super) fn insert<'a, EG, TR, T>(
element: T
) -> impl Fn((ItemStateProxy, TR), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), T), ItemStateError>> + Send + 'a>>
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), T), anyhow::Error>> + Send + 'a>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
T: Send + Clone + 'a,
{
move |state, _| {
@ -768,17 +810,17 @@ where
}
}
pub(super) fn fork<EG, TR, F1, F2, T, O1, O2>(
pub(super) fn fork<'a, EG, TR, F1, F2, T, O1, O2>(
func1: F1,
func2: F2,
) -> impl Fn((ItemStateProxy, TR), T)
-> BoxFuture<Result<((ItemStateProxy, TR), (O1, O2)), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), (O1, O2)), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
F1: Fn((ItemStateProxy, TR), T) -> BoxFuture<Result<((ItemStateProxy, TR), O1), ItemStateError>> + Send + Sync + 'static,
F2: Fn((ItemStateProxy, TR), T) -> BoxFuture<Result<((ItemStateProxy, TR), O2), ItemStateError>> + Send + Sync + 'static,
T: Send + Sync + Clone + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
F1: Fn((ItemStateProxy, TR), T) -> BoxFuture<'a, Result<((ItemStateProxy, TR), O1), anyhow::Error>> + Send + Sync + 'a,
F2: Fn((ItemStateProxy, TR), T) -> BoxFuture<'a, Result<((ItemStateProxy, TR), O2), anyhow::Error>> + Send + Sync + 'a,
T: Send + Sync + Clone + 'a,
O1: Send,
O2: Send,
{
@ -796,13 +838,13 @@ where
}
}
pub(super) fn add_item_to_inventory<EG, TR>(
pub(super) fn add_item_to_inventory<'a, EG, TR>(
character: CharacterEntity,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), inventory_item| {
let character = character.clone();
@ -824,15 +866,15 @@ where
}
}
pub(super) fn record_trade<EG, TR>(
pub(super) fn record_trade<'a, EG, TR>(
trade_id: TradeId,
character_to: CharacterEntityId,
character_from: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), Vec<InventoryItem>)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<InventoryItem>), ItemStateError>> + Clone
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<InventoryItem>), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, mut transaction), traded_items| {
Box::pin(async move {
@ -853,12 +895,12 @@ where
}
pub(super) fn assign_new_item_id<EG, TR>(
pub(super) fn assign_new_item_id<'a, EG, TR>(
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction), mut inventory_item| {
Box::pin(async move {
@ -869,14 +911,13 @@ where
}
pub(super) fn convert_item_drop_to_floor_item<EG, TR>(
character_id: CharacterEntityId,
pub(super) fn convert_item_drop_to_floor_item<'a, EG, TR>(
item_drop: ItemDrop,
) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>> + Clone
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), _| {
let item_drop = item_drop.clone();
@ -912,13 +953,6 @@ where
let entity = transaction.gateway().create_item(NewItemEntity {
item: item_detail.clone(),
}).await?;
transaction.gateway().add_item_note(&entity.id, ItemNote::EnemyDrop {
character_id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}).await?;
FloorItem {
item_id,
item: FloorItemDetail::Individual(IndividualItemDetail {
@ -935,13 +969,6 @@ where
let entity = transaction.gateway().create_item(NewItemEntity {
item: ItemDetail::Tool(tool),
}).await?;
transaction.gateway().add_item_note(&entity.id, ItemNote::EnemyDrop {
character_id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}).await?;
FloorItem {
item_id,
item: FloorItemDetail::Stacked(StackedItemDetail{
@ -971,13 +998,95 @@ where
}
}
pub(super) fn add_item_to_local_floor<EG, TR>(
pub(super) fn item_note_enemy_drop<'a, EG, TR>(
character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, mut transaction), floor_item| {
Box::pin(async move {
match &floor_item.item {
FloorItemDetail::Individual(individual) => {
transaction.gateway().add_item_note(&individual.entity_id, ItemNote::EnemyDrop {
character_id,
room_id,
monster_type,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
FloorItemDetail::Stacked(stacked) => {
transaction.gateway().add_item_note(&stacked.entity_ids[0], ItemNote::EnemyDrop {
character_id,
room_id,
monster_type,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
_ => {},
}
Ok(((item_state, transaction), floor_item))
})
}
}
pub(super) fn item_note_box_drop<'a, EG, TR>(
character_id: CharacterEntityId,
room_id: RoomEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, mut transaction), floor_item| {
Box::pin(async move {
match &floor_item.item {
FloorItemDetail::Individual(individual) => {
transaction.gateway().add_item_note(&individual.entity_id, ItemNote::BoxDrop {
character_id,
room_id,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
FloorItemDetail::Stacked(stacked) => {
transaction.gateway().add_item_note(&stacked.entity_ids[0], ItemNote::BoxDrop {
character_id,
room_id,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
_ => {},
}
Ok(((item_state, transaction), floor_item))
})
}
}
pub(super) fn add_item_to_local_floor<'a, EG, TR>(
character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, transaction) , floor_item| {
Box::pin(async move {
@ -990,13 +1099,13 @@ where
}
}
pub(super) fn apply_modifier_to_inventory_item<EG, TR>(
pub(super) fn apply_modifier_to_inventory_item<'a, EG, TR>(
modifier: ItemModifier,
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, mut transaction), mut inventory_item| {
let modifier = modifier.clone();
@ -1004,9 +1113,9 @@ where
match (&mut inventory_item.item, modifier) {
(InventoryItemDetail::Individual(IndividualItemDetail{entity_id, item: ItemDetail::Weapon(ref mut weapon), ..}), ItemModifier::WeaponModifier(modifier)) => {
weapon.apply_modifier(&modifier);
transaction.gateway().add_weapon_modifier(entity_id, modifier).await?;
transaction.gateway().add_weapon_modifier(entity_id, &modifier).await?;
},
_ => return Err(ItemStateError::InvalidModifier)
_ => return Err(ItemStateError::InvalidModifier.into())
}
Ok(((item_state, transaction), inventory_item))
@ -1014,18 +1123,18 @@ where
}
}
pub(super) fn as_individual_item<EG, TR>(
pub(super) fn as_individual_item<'a, EG, TR>(
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
-> BoxFuture<Result<((ItemStateProxy, TR), IndividualItemDetail), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), IndividualItemDetail), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, transaction), inventory_item| {
Box::pin(async move {
let item = match inventory_item.item {
InventoryItemDetail::Individual(individual_item) => individual_item,
_ => return Err(ItemStateError::WrongItemType(inventory_item.item_id))
_ => return Err(ItemStateError::WrongItemType(inventory_item.item_id).into())
};
Ok(((item_state, transaction), item))
@ -1034,14 +1143,14 @@ where
}
pub(super) fn apply_item_action_packets<EG, TR>(
pub(super) fn apply_item_action_packets<'a, EG, TR>(
character_id: CharacterEntityId,
area_client: AreaClient,
) -> impl Fn((ItemStateProxy, TR), ApplyItemAction)
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<SendShipPacket>), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<CreateItem>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(mut item_state, mut transaction), apply_item_action| {
Box::pin(async move {
@ -1055,7 +1164,7 @@ where
let (inventory_item_detail, create_item) = if item_detail.is_stackable() {
let tool = item_detail.as_tool().ok_or_else(|| ItemStateError::NotATool(ClientItemId(0xFFFFFFFF)))?;
let create_item = builder::message::create_stacked_item(area_client, item_id, &tool, 1).map_err(|_err| ItemStateError::Dummy)?;
let create_item = CreateItem::Stacked(area_client, item_id, tool, 1);
let item_detail = StackedItemDetail {
entity_ids: vec![new_item.id],
tool
@ -1067,7 +1176,7 @@ where
entity_id: new_item.id,
item: item_detail,
};
let create_item = builder::message::create_individual_item(area_client, item_id, &item_detail).map_err(|_err| ItemStateError::Dummy)?;
let create_item = CreateItem::Individual(area_client, item_id, item_detail.clone());
(InventoryItemDetail::Individual(item_detail), create_item)
};
@ -1081,7 +1190,8 @@ where
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await;
vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
//vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
vec![create_item]
}
else {
Vec::new()
@ -1092,13 +1202,13 @@ where
}
}
pub(super) fn apply_item_action_character<EG, TR>(
pub(super) fn apply_item_action_character<'a, EG, TR>(
character: &CharacterEntity
) -> impl Fn((ItemStateProxy, TR), Vec<ApplyItemAction>)
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
let character = character.clone();
move |(item_state, transaction), apply_item_actions| {
@ -1114,3 +1224,25 @@ where
})
}
}
pub(super) fn delete_item_from_floor<'a, EG, TR>(
map_area: MapArea
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'a,
{
move |(item_state, transaction), floor_item| {
Box::pin(async move {
let transaction = floor_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::FloorLimitReached {
map_area
}).await?;
Ok(transaction)
}}).await?;
Ok(((item_state, transaction), ()))
})
}
}

View File

@ -1,15 +1,18 @@
use std::convert::TryInto;
use futures::future::join_all;
use thiserror::Error;
use anyhow::Context;
use rand::SeedableRng;
use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::CharacterEntity;
use crate::entity::item::mag::{MagCell, MagCellError};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::{ItemDetail, ItemEntityId};
use crate::ship::items::state::{ItemStateProxy, ItemStateError};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use entity::gateway::{EntityGateway, GatewayError};
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)]
@ -18,14 +21,12 @@ pub enum ApplyItemError {
NoCharacter,
#[error("item not equipped")]
ItemNotEquipped,
#[error("invalid item")]
#[error("could not use item invalid item")]
InvalidItem,
#[error("invalid tool")]
InvalidTool,
#[error("gateway error {0}")]
GatewayError(#[from] GatewayError),
#[error("itemstate error {0}")]
ItemStateError(Box<ItemStateError>),
#[error("magcell error {0}")]
MagCellError(#[from] MagCellError),
}
@ -34,53 +35,47 @@ pub enum ApplyItemError {
pub enum ApplyItemAction {
UpdateCharacter(Box<CharacterEntity>),
CreateItem(ItemDetail),
//TransformItem,
//TransformItem(ItemDetail),
//RemoveItem,
}
impl From<ItemStateError> for ApplyItemError {
fn from(other: ItemStateError) -> ApplyItemError {
ApplyItemError::ItemStateError(Box::new(other))
}
}
async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.power += 1;
entity_gateway.save_character(character).await?;
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.mind += 1;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.evade += 1;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.def += 1;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.luck += 1;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.hp += 1;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
character.materials.tp += 1;
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
@ -113,7 +108,7 @@ async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy,
character: &CharacterEntity,
cell_entity_id: ItemEntityId,
mag_cell_type: MagCell)
-> Result<Vec<ApplyItemAction>, ApplyItemError>
-> Result<Vec<ApplyItemAction>, anyhow::Error>
where
EG: EntityGateway + ?Sized,
{
@ -229,9 +224,9 @@ pub async fn liberta_kit<EG: EntityGateway>(entity_gateway: &mut EG, used_cell:
*/
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, ApplyItemError>
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, anyhow::Error>
{
let mag_rate = WeightedIndex::new(&[13, 13, 13, 13, 12, 12, 12, 12]).unwrap();
let mag_rate = WeightedIndex::new([13, 13, 13, 13, 12, 12, 12, 12]).unwrap();
let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) {
0 => ToolType::CellOfMag502,
1 => ToolType::CellOfMag213,
@ -247,12 +242,36 @@ fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, ApplyItemError>
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,
character: &mut CharacterEntity,
entity_id: ItemEntityId,
tool: ToolType)
-> Result<Vec<ApplyItemAction>, ApplyItemError>
-> Result<Vec<ApplyItemAction>, anyhow::Error>
where
EG: EntityGateway + ?Sized,
{
@ -270,6 +289,17 @@ where
ToolType::Monofluid => Ok(Vec::new()),
ToolType::Difluid => Ok(Vec::new()),
ToolType::Trifluid => Ok(Vec::new()),
ToolType::SolAtomizer => Ok(Vec::new()),
ToolType::MoonAtomizer => Ok(Vec::new()),
ToolType::StarAtomizer => Ok(Vec::new()),
ToolType::Telepipe => Ok(Vec::new()),
ToolType::Antidote => Ok(Vec::new()),
ToolType::Antiparalysis => Ok(Vec::new()),
ToolType::TrapVision => Ok(Vec::new()),
ToolType::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::CellOfMag213
@ -299,25 +329,48 @@ where
}
ToolType::JackOLantern => jack_o_lantern(),
// TODO: rest of these
_ => Err(ApplyItemError::InvalidItem)
_ => Err(anyhow::Error::from(ApplyItemError::InvalidTool))
.with_context(|| {
format!("invalid tool {tool:?}")
})
}
}
pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
item: InventoryItem
) -> Result<Vec<ApplyItemAction>, ApplyItemError>
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 + Clone + 'static
EG: EntityGateway + ?Sized,
{
match item.item {
// TODO: make sure the class can learn that specific tech
character.techs.set_tech(tech.tech, TechLevel(tech.level as u8));
entity_gateway.save_character(character).await.unwrap();
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
}
pub async fn apply_item<'a, EG>(item_state: &'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) => {
match individual_item.item {
ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await,
_ => Err(ApplyItemError::InvalidItem)
ItemDetail::TechniqueDisk(tech) => apply_tech(item_state, entity_gateway, character, individual_item.entity_id, tech).await,
_ => Err(anyhow::Error::from(ApplyItemError::InvalidItem))
.with_context(|| {
format!("item {individual_item:?}")
})
}
},
InventoryItemDetail::Stacked(stacked_item) => {

View File

@ -1,13 +1,15 @@
use std::cmp::Ordering;
use libpso::character::character;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity, BankName};
use crate::ClientItemId;
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity};
use std::future::Future;
use async_std::sync::{Arc, Mutex};
use crate::entity::character::CharacterEntityId;
use crate::ship::items::state::ItemStateError;
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use entity::character::CharacterEntityId;
use entity::item::BankIdentifier;
use crate::state::ItemStateError;
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::inventory::{InventoryItem, InventoryItemDetail};
#[derive(thiserror::Error, Debug)]
@ -21,6 +23,7 @@ pub enum BankError {
}
#[derive(Clone, Debug)]
pub enum BankItemDetail {
Individual(IndividualItemDetail),
@ -64,10 +67,10 @@ pub struct BankItem {
}
impl BankItem {
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where
F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{
match &self.item {
BankItemDetail::Individual(individual_item) => {
@ -96,19 +99,27 @@ impl Bank {
#[derive(Clone, Debug)]
pub struct BankState {
pub character_id: CharacterEntityId,
pub item_id_counter: u32,
pub name: BankName,
pub item_id_counter: Arc<Mutex<u32>>,
pub identifier: BankIdentifier,
pub bank: Bank,
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 {
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();
BankState {
character_id,
item_id_counter: 0,
name,
item_id_counter: Arc::new(Mutex::new(0)),
identifier,
bank,
meseta,
}
@ -118,34 +129,37 @@ impl BankState {
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() {
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 {
return Err(ItemStateError::FullOfMeseta)
return Err(ItemStateError::FullOfMeseta.into())
}
self.meseta.0 += amount;
Ok(())
}
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
if amount > self.meseta.0 {
return Err(ItemStateError::InvalidMesetaRemoval(amount))
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
}
self.meseta.0 -= amount;
Ok(())
}
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, BankError> {
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, anyhow::Error> {
match item.item {
InventoryItemDetail::Individual(iitem) => {
if self.bank.0.len() >= 30 {
Err(BankError::BankFull)
Err(BankError::BankFull.into())
}
else {
self.bank.0.push(BankItem {
@ -166,7 +180,7 @@ impl BankState {
match existing_stack {
Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(BankError::StackFull)
Err(BankError::StackFull.into())
}
else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
@ -175,7 +189,7 @@ impl BankState {
},
None => {
if self.bank.0.len() >= 30 {
Err(BankError::BankFull)
Err(BankError::BankFull.into())
}
else {
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
.iter()
.position(|i| i.item_id == *item_id)?;
@ -211,9 +225,8 @@ impl BankState {
}
else {
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
self.item_id_counter += 1;
Some(BankItem {
item_id: ClientItemId(self.item_id_counter),
item_id: new_item_id(&self.item_id_counter).await,
item: BankItemDetail::Stacked(StackedItemDetail {
entity_ids,
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 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]);
self_bytes.copy_from_slice(&self.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 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 {
fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
let mut self_bytes = [0u8; 4];
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::PartialOrd for BankItemDetail {
fn partial_cmp(&self, other: &BankItemDetail) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for BankItem {
fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
impl std::cmp::Ord for BankItemDetail {
fn cmp(&self, other: &BankItemDetail) -> std::cmp::Ordering {
let mut self_bytes = [0u8; 4];
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]);
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
let self_value = u32::from_le_bytes(self_bytes);
let other_value = u32::from_le_bytes(other_bytes);
let self_value = u32::from_be_bytes(self_bytes);
let other_value = u32::from_be_bytes(other_bytes);
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)
}
}

View File

@ -1,14 +1,14 @@
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail};
use crate::ClientItemId;
use entity::item::{Meseta, ItemEntityId, ItemDetail};
use std::future::Future;
use crate::ship::map::MapArea;
use crate::entity::character::CharacterEntityId;
use crate::entity::item::mag::Mag;
use maps::area::MapArea;
use entity::character::CharacterEntityId;
use entity::item::mag::Mag;
use crate::ship::items::state::ItemStateError;
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use crate::state::ItemStateError;
use crate::state::{IndividualItemDetail, StackedItemDetail};
use crate::inventory::{InventoryItem, InventoryItemDetail};
pub enum FloorType {
Local,
@ -33,7 +33,7 @@ pub struct FloorItem {
}
impl FloorItem {
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where
F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
@ -53,10 +53,10 @@ impl FloorItem {
Ok(param)
}
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where
F: FnMut(T, ItemEntityId, Mag) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{
if let FloorItemDetail::Individual(individual_item) = &self.item {
if let ItemDetail::Mag(mag) = &individual_item.item {
@ -96,13 +96,13 @@ pub struct FloorState {
impl FloorState {
pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<FloorItem> {
let item = self.local.0
.drain_filter(|item| {
.extract_if(|item| {
item.item_id == *item_id
})
.next();
item.or_else(|| {
self.shared.0
.drain_filter(|item| {
.extract_if(|item| {
item.item_id == *item_id
})
.next()

View File

@ -1,16 +1,18 @@
use std::cmp::Ordering;
use libpso::character::character;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
use crate::ClientItemId;
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
use std::future::Future;
use async_std::sync::{Arc, Mutex};
use crate::entity::character::CharacterEntityId;
use crate::entity::item::tool::ToolType;
use crate::entity::item::mag::Mag;
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
use crate::ship::items::state::ItemStateError;
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::ship::items::floor::{FloorItem, FloorItemDetail};
use entity::character::CharacterEntityId;
use entity::item::tool::ToolType;
use entity::item::mag::Mag;
use entity::item::weapon::Weapon;
use shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
use crate::state::ItemStateError;
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::floor::{FloorItem, FloorItemDetail};
#[derive(Clone, Debug)]
pub enum InventoryItemDetail {
@ -60,7 +62,7 @@ impl InventoryItemDetail {
}
// TODO: this should probably go somewhere a bit more fundamental like ItemDetail
pub fn sell_price(&self) -> Result<u32, ItemStateError> {
pub fn sell_price(&self) -> Result<u32, anyhow::Error> {
match self {
InventoryItemDetail::Individual(individual_item) => {
match &individual_item.item {
@ -102,7 +104,7 @@ impl InventoryItemDetail {
Ok((ToolShopItem::from(d).price() / 8) as u32)
},
ItemDetail::Mag(_m) => {
Err(ItemStateError::ItemNotSellable)
Err(ItemStateError::ItemNotSellable.into())
},
ItemDetail::ESWeapon(_e) => {
Ok(10u32)
@ -116,22 +118,12 @@ impl InventoryItemDetail {
}
}
}
#[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>
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
where
F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{
match &self.item {
match &self {
InventoryItemDetail::Individual(individual_item) => {
param = func(param, individual_item.entity_id).await?;
},
@ -144,11 +136,28 @@ impl InventoryItem {
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
F: FnMut(T, ItemEntityId, Mag) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
Fut: Future<Output=Result<T, anyhow::Error>>,
{
if let InventoryItemDetail::Individual(individual_item) = &self.item {
if let ItemDetail::Mag(mag) = &individual_item.item {
@ -182,34 +191,49 @@ pub enum InventoryError {
#[derive(Clone, Debug)]
pub struct InventoryState {
pub character_id: CharacterEntityId,
pub item_id_counter: u32,
pub item_id_counter: Arc<Mutex<u32>>,
pub inventory: Inventory,
pub equipped: EquippedEntity,
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 {
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() {
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 {
self.item_id_counter += 1;
ClientItemId(self.item_id_counter)
pub async fn new_item_id(&mut self) -> ClientItemId {
let mut item_id_counter = self.item_id_counter.lock().await;
let item_id = *item_id_counter;
*item_id_counter += 1;
ClientItemId(item_id)
}
pub fn count(&self) -> usize {
self.inventory.0.len()
}
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, InventoryError> {
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, anyhow::Error> {
match item.item {
FloorItemDetail::Individual(iitem) => {
if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
}
else {
self.inventory.0.push(InventoryItem {
@ -229,7 +253,7 @@ impl InventoryState {
match existing_stack {
Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(InventoryError::StackFull)
Err(InventoryError::StackFull.into())
}
else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
@ -238,7 +262,7 @@ impl InventoryState {
},
None => {
if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
}
else {
self.inventory.0.push(InventoryItem {
@ -253,7 +277,7 @@ impl InventoryState {
},
FloorItemDetail::Meseta(meseta) => {
if self.meseta == Meseta(999999) {
Err(InventoryError::MesetaFull)
Err(InventoryError::MesetaFull.into())
}
else {
self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999);
@ -263,11 +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 {
InventoryItemDetail::Individual(_) => {
if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
}
else {
self.inventory.0.push(item);
@ -290,7 +314,7 @@ impl InventoryState {
match existing_stack {
Some(existing_stack) => {
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
Err(InventoryError::StackFull)
Err(InventoryError::StackFull.into())
}
else {
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
@ -307,7 +331,7 @@ impl InventoryState {
},
None => {
if self.inventory.0.len() >= 30 {
Err(InventoryError::InventoryFull)
Err(InventoryError::InventoryFull.into())
}
else {
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
.iter()
.position(|i| i.item_id == *item_id)?;
@ -345,9 +398,8 @@ impl InventoryState {
}
else {
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
self.item_id_counter += 1;
Some(InventoryItem {
item_id: ClientItemId(self.item_id_counter),
item_id: new_item_id(&self.item_id_counter).await,
item: InventoryItemDetail::Stacked(StackedItemDetail {
entity_ids,
tool: stacked_item.tool,
@ -370,25 +422,25 @@ impl InventoryState {
.find(|i| i.item_id == *item_id)
}
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
if self.meseta.0 == 999999 {
return Err(ItemStateError::FullOfMeseta)
return Err(ItemStateError::FullOfMeseta.into())
}
self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
Ok(())
}
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> {
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), anyhow::Error> {
if self.meseta.0 + amount > 999999 {
return Err(ItemStateError::FullOfMeseta)
return Err(ItemStateError::FullOfMeseta.into())
}
self.meseta.0 += amount;
Ok(())
}
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
if amount > self.meseta.0 {
return Err(ItemStateError::InvalidMesetaRemoval(amount))
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
}
self.meseta.0 -= amount;
Ok(())
@ -453,6 +505,18 @@ impl InventoryState {
.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]) {
self.inventory.0.sort_by(|a, b| {
let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id);

View File

@ -34,38 +34,40 @@ where
S: 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
F: Fn(S, ()) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O), E>> + Send
F: Fn(S, ()) -> Fut + Send + Sync + 'a,
Fut: Future<Output=Result<(S, O), E>> + Send + 'a
{
ItemActionStage {
_s: Default::default(),
_e: std::marker::PhantomData,
_a: Default::default(),
prev: self,
actionf: f,
}
}
}
pub struct ItemActionStage<O, P, F, Fut, S, E>
pub struct ItemActionStage<'a, O, P, F, Fut, S, E>
where
P: ItemAction,
F: Fn(S, P::Output) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O) , E>> + Send,
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
Fut: Future<Output=Result<(S, O) , E>> + Send + 'a,
{
_s: std::marker::PhantomData<S>,
_e: std::marker::PhantomData<E>,
_a: std::marker::PhantomData<&'a ()>,
prev: P,
actionf: F,
}
#[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
P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
F: Fn(S, P::Output) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O), E>> + Send,
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
Fut: Future<Output=Result<(S, O), E>> + Send + 'a,
S: Send + Sync,
P::Output: 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
P: ItemAction<Start = S, Error = E> + Send + Sync,
F: Fn(S, P::Output) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O), E>> + Send,
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
Fut: Future<Output=Result<(S, O), E>> + Send + 'a,
S: Send + Sync,
P::Output: Send + Sync,
E: Send + Sync,
@ -99,16 +101,17 @@ where
P::Error: Send + Sync,
{
#[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
S: Send + Sync,
G: Fn(S, <ItemActionStage<O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync,
GFut: Future<Output=Result<(S, O2), E>> + Send,
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 + 'b,
O2: Send + Sync,
{
ItemActionStage {
_s: Default::default(),
_e: Default::default(),
_a: Default::default(),
prev: self,
actionf: g,
}

View File

@ -1,3 +1,5 @@
#![feature(extract_if)]
pub mod state;
pub mod actions;
pub mod apply_item;
@ -6,6 +8,7 @@ pub mod inventory;
pub mod floor;
pub mod bank;
pub mod tasks;
pub mod trade;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)]
pub struct ClientItemId(pub u32);

View File

@ -1,19 +1,21 @@
use std::collections::HashMap;
use std::collections::{HashMap, BinaryHeap};
use std::cmp::Reverse;
use async_std::sync::{Arc, RwLock, Mutex};
use futures::future::join_all;
use futures::stream::{FuturesOrdered, StreamExt};
use anyhow::Context;
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankName};
use crate::entity::item::tool::Tool;
use crate::entity::item::weapon::Weapon;
use crate::entity::item::mag::Mag;
use crate::ship::drops::ItemDrop;
use crate::ship::items::ClientItemId;
use crate::ship::items::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState};
use crate::ship::items::floor::{FloorState, FloorItem, LocalFloor, SharedFloor, FloorType};
use crate::ship::items::bank::{Bank, BankState, BankItem, BankItemDetail, BankError};
use crate::ship::location::{AreaClient, RoomId};
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 {
@ -21,6 +23,8 @@ pub enum ItemStateError {
NoCharacter(CharacterEntityId),
#[error("room {0} not found")]
NoRoom(RoomId),
#[error("inventory item {0} not found")]
NoInventoryItem(ClientItemId),
#[error("floor item {0} not found")]
NoFloorItem(ClientItemId),
#[error("expected {0} to be a tool")]
@ -46,7 +50,7 @@ pub enum ItemStateError {
#[error("stacked item")]
StackedItemError(Vec<ItemEntity>),
#[error("apply item {0}")]
ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError),
ApplyItemError(#[from] crate::apply_item::ApplyItemError),
#[error("item is not a mag {0}")]
NotAMag(ClientItemId),
#[error("item is not mag food {0}")]
@ -55,7 +59,7 @@ pub enum ItemStateError {
ItemNotSellable,
#[error("could not modify item")]
InvalidModifier,
#[error("wrong item type ")]
#[error("wrong item type {0}")]
WrongItemType(ClientItemId),
}
@ -88,6 +92,14 @@ impl IndividualItemDetail {
}
}
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(),
@ -123,15 +135,40 @@ pub enum AddItemResult {
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>>,
}
@ -144,13 +181,14 @@ impl Default for ItemState {
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, ItemStateError> {
pub async fn get_character_inventory(&self, character: &CharacterEntity) -> Result<InventoryState, anyhow::Error> {
Ok(self.character_inventory
.read()
.await
@ -161,7 +199,7 @@ impl ItemState {
.clone())
}
pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result<BankState, ItemStateError> {
pub async fn get_character_bank(&self, character: &CharacterEntity) -> Result<BankState, anyhow::Error> {
Ok(self.character_bank
.read()
.await
@ -174,20 +212,19 @@ impl ItemState {
}
impl ItemState {
async fn new_item_id(&mut self) -> Result<ClientItemId, ItemStateError> {
async fn new_item_id(&mut self) -> Result<ClientItemId, anyhow::Error> {
*self.room_item_id_counter
.write()
.await += 1;
Ok(ClientItemId(*self.room_item_id_counter.read().await))
}
pub async fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> {
pub async fn load_character_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 bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?;
let equipped = entity_gateway.get_character_equips(&character.id).await?;
let inventory_items = inventory.items.into_iter()
.map(|item| -> Result<InventoryItem, ItemStateError> {
.map(|item| -> Result<InventoryItem, anyhow::Error> {
Ok(match item {
InventoryItemEntity::Individual(item) => {
InventoryItem {
@ -214,60 +251,69 @@ impl ItemState {
},
})
})
.collect::<Result<Vec<_>, ItemStateError>>()?;
.collect::<Result<Vec<_>, anyhow::Error>>()?;
let character_meseta = entity_gateway.get_character_meseta(&character.id).await?;
let inventory_state = InventoryState {
character_id: character.id,
item_id_counter: 0,
item_id_counter: Arc::new(Mutex::new(0)),
inventory: Inventory::new(inventory_items),
equipped,
meseta: character_meseta,
};
let bank_items = join_all(
bank.items.into_iter()
.map(|item| {
let mut citem_state = self.clone();
async move {
Ok(match item {
BankItemEntity::Individual(item) => {
BankItem {
item_id: citem_state.new_item_id().await?,
item: BankItemDetail::Individual(IndividualItemDetail {
entity_id: item.id,
item: item.item,
})
}
},
BankItemEntity::Stacked(items) => {
BankItem {
item_id: citem_state.new_item_id().await?,
item: 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::<Vec<_>>())
.await
.into_iter()
.collect::<Result<Vec<_>, ItemStateError>>()?;
let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?;
let bank_state = BankState::new(character.id, BankName("".into()), Bank::new(bank_items), bank_meseta);
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
@ -275,8 +321,21 @@ impl ItemState {
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 base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000;
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
@ -284,8 +343,8 @@ impl ItemState {
.unwrap()
.write()
.await
.initialize_item_ids(base_inventory_id);
let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000;
.initialize_item_ids(base_item_ids.inventory(&area_client).clone())
.await;
self.character_bank
.read()
.await
@ -293,7 +352,8 @@ impl ItemState {
.unwrap()
.write()
.await
.initialize_item_ids(base_bank_id);
.initialize_item_ids(base_item_ids.bank(&area_client))
.await;
self.character_room
.write()
.await
@ -322,7 +382,7 @@ impl ItemState {
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) {
@ -334,7 +394,7 @@ impl ItemState {
}
}
pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), ItemStateError> {
pub async fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(FloorItem, FloorType), anyhow::Error> {
let local_floors = self.character_floor
.read()
.await;
@ -369,6 +429,7 @@ impl ItemState {
.map(|item| (item.clone(), FloorType::Shared))
})
.ok_or_else(|| ItemStateError::NoFloorItem(*item_id))
.with_context(|| format!("character {character_id}\nlocal floors: {local_floors:#?}\nshared floors: {shared_floors:#?}"))
}
}
@ -421,7 +482,7 @@ impl ItemStateProxy {
async fn get_or_clone<K, V>(master: &Arc<RwLock<HashMap<K, RwLock<V>>>>,
proxy: &Arc<Mutex<HashMap<K, V>>>,
key: K,
err: fn(K) -> ItemStateError) -> Result<V, ItemStateError>
err: fn(K) -> ItemStateError) -> Result<V, anyhow::Error>
where
K: Eq + std::hash::Hash + Copy,
V: Clone
@ -451,7 +512,7 @@ impl ItemStateProxy {
}
}
pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, ItemStateError> {
pub async fn inventory(&mut self, character_id: &CharacterEntityId) -> Result<InventoryState, anyhow::Error> {
get_or_clone(&self.item_state.character_inventory,
&self.proxied_state.character_inventory,
*character_id,
@ -462,7 +523,7 @@ impl ItemStateProxy {
self.proxied_state.character_inventory.lock().await.insert(inventory.character_id, inventory);
}
pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, ItemStateError> {
pub async fn bank(&mut self, character_id: &CharacterEntityId) -> Result<BankState, anyhow::Error> {
get_or_clone(&self.item_state.character_bank,
&self.proxied_state.character_bank,
*character_id,
@ -473,22 +534,28 @@ impl ItemStateProxy {
self.proxied_state.character_bank.lock().await.insert(bank.character_id, bank);
}
pub async fn floor(&mut self, character_id: &CharacterEntityId) -> Result<FloorState, ItemStateError> {
let room_id = *self.item_state.character_room.read().await.get(character_id).unwrap();
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?,
shared: get_or_clone(&self.item_state.room_floor, &self.proxied_state.room_floor, room_id, ItemStateError::NoRoom).await?,
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).unwrap();
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, ItemStateError> {
pub async fn new_item_id(&mut self) -> Result<ClientItemId, anyhow::Error> {
self.item_state.new_item_id().await
}
}

View File

@ -1,33 +1,35 @@
use crate::ship::items::ClientItemId;
use crate::entity::item::Meseta;
use futures::future::BoxFuture;
use crate::ClientItemId;
use entity::item::Meseta;
use crate::ship::ship::SendShipPacket;
use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateError, IndividualItemDetail};
use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction};
use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::floor::FloorItem;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem;
use crate::ship::trade::TradeItem;
use crate::ship::location::AreaClient;
use crate::ship::drops::ItemDrop;
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::ship::items::actions;
use crate::actions;
pub async fn pick_up_item<EG>(
item_state: &mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId)
-> Result<actions::TriggerCreateItem, ItemStateError>
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: Clone,
EG::Transaction<'a>: Clone,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -36,21 +38,22 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn drop_item<EG>(
item_state: &mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
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))
-> Result<FloorItem, ItemStateError>
drop_position: (f32, f32, f32),
)-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
EG::Transaction<'a>: Clone,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -59,22 +62,22 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn drop_partial_item<'a, EG>(
pub fn drop_partial_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
map_area: MapArea,
drop_position: (f32, f32),
amount: u32)
-> Result<FloorItem, ItemStateError>
amount: u32
) -> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -83,23 +86,23 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn drop_meseta<'a, EG>(
pub fn drop_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
map_area: MapArea,
drop_position: (f32, f32),
amount: u32)
-> Result<FloorItem, ItemStateError>
amount: u32,
) -> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -108,20 +111,20 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn withdraw_meseta<'a, EG>(
pub fn withdraw_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
amount: u32)
-> Result<(), ItemStateError>
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
amount: u32,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -130,20 +133,20 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn deposit_meseta<'a, EG>(
pub fn deposit_meseta<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
amount: u32)
-> Result<(), ItemStateError>
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
amount: u32,
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -152,21 +155,21 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, ()))
}).await
})
}
pub async fn withdraw_item<'a, EG>(
pub fn withdraw_item<'a, EG>(
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
amount: u32)
-> Result<InventoryItem, ItemStateError>
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(|transaction| async move {
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))
@ -177,21 +180,21 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn deposit_item<'a, EG> (
pub fn deposit_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
amount: u32)
-> Result<(), ItemStateError>
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(|transaction| async move {
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))
@ -200,20 +203,20 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn equip_item<'a, EG> (
pub fn equip_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
equip_slot: u8,
) -> Result<(), ItemStateError>
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -221,20 +224,20 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn unequip_item<'a, EG> (
pub fn unequip_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
) -> Result<(), ItemStateError>
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(|transaction| async move {
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))
@ -242,20 +245,20 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn sort_inventory<'a, EG> (
pub fn sort_inventory<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_ids: Vec<ClientItemId>,
) -> Result<(), ItemStateError>
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -263,25 +266,25 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn use_item<'a, EG> (
pub fn use_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
entity_gateway: &'a mut EG,
character: &'a mut CharacterEntity,
area_client: AreaClient,
item_id: &ClientItemId,
item_id: &'a ClientItemId,
amount: u32,
) -> Result<Vec<SendShipPacket>, ItemStateError>
) -> BoxFuture<'a, Result<Vec<actions::CreateItem>, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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::take_item_from_inventory(character.id, *item_id, amount))
.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)),
@ -293,21 +296,21 @@ where
*character = new_character;
Ok((transaction, pkts.into_iter().flatten().collect()))
}).await
})
}
pub async fn feed_mag<'a, EG> (
pub fn feed_mag<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
mag_item_id: &ClientItemId,
tool_item_id: &ClientItemId,
) -> Result<(), ItemStateError>
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(|transaction| async move {
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))
@ -316,23 +319,23 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, ()))
}).await
})
}
pub async fn buy_shop_item<'a, EG> (
pub fn buy_shop_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
shop_item: &'a (dyn ShopItem + Send + Sync),
item_id: ClientItemId,
amount: u32,
) -> Result<InventoryItem, ItemStateError>
) -> BoxFuture<'a, Result<InventoryItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
let item_price = shop_item.price() as u32 * amount;
entity_gateway.with_transaction(|transaction| async move {
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))
@ -343,21 +346,21 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn sell_item<'a, EG> (
pub fn sell_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: ClientItemId,
amount: u32,
) -> Result<InventoryItem, ItemStateError>
) -> BoxFuture<'a, Result<InventoryItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -366,14 +369,16 @@ where
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
}).await
})
}
pub async fn trade_items<'a, EG> (
#[allow(clippy::type_complexity)]
pub fn trade_items<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
p1: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta),
p2: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta))
-> Result<(Vec<InventoryItem>, Vec<InventoryItem>), ItemStateError>
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,
{
@ -395,7 +400,7 @@ where
}
})
.collect();
entity_gateway.with_transaction(|mut transaction| async move {
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?;
@ -434,20 +439,20 @@ where
item_state_proxy.commit().await;
Ok((transaction, (p1_new_items, p2_new_items)))
}).await
})
}
pub async fn take_meseta<'a, EG> (
pub fn take_meseta<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character_id: &CharacterEntityId,
entity_gateway: &'a mut EG,
character_id: &'a CharacterEntityId,
meseta: Meseta)
-> Result<(), ItemStateError>
-> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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))
@ -456,43 +461,70 @@ where
item_state_proxy.commit().await;
Ok((transaction, ()))
}).await
})
}
pub async fn enemy_drops_item<'a, EG> (
pub fn enemy_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
item_drop: ItemDrop)
-> Result<FloorItem, ItemStateError>
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(|transaction| async move {
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(character_id, item_drop))
.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))
}).await
})
}
pub async fn apply_modifier<'a, EG> (
pub fn box_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: ClientItemId,
modifier: ItemModifier)
-> Result<IndividualItemDetail, ItemStateError>
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(|transaction| async move {
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))
@ -504,5 +536,29 @@ where
item_state_proxy.commit().await;
Ok((transaction, item))
}).await
})
}
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
View 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,
}
}
}

View File

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

12
src/location/Cargo.toml Normal file
View 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 }

View File

@ -2,7 +2,7 @@
use std::collections::HashMap;
use std::time::SystemTime;
use thiserror::Error;
use crate::common::serverstate::ClientId;
use networking::serverstate::ClientId;
use async_std::sync::{Arc, RwLock};
use futures::{stream, StreamExt};
@ -30,41 +30,50 @@ impl LobbyId {
#[derive(Error, Debug, PartialEq, Eq)]
#[error("create room")]
pub enum CreateRoomError {
#[error("no open slots")]
NoOpenSlots,
#[error("client already in area")]
ClientInAreaAlready,
#[error("join error")]
JoinError,
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("join room")]
pub enum JoinRoomError {
#[error("room does not exist")]
RoomDoesNotExist,
#[error("room is full")]
RoomFull,
#[error("client already in area")]
ClientInAreaAlready,
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("join lobby")]
pub enum JoinLobbyError {
#[error("lobby does not exist")]
LobbyDoesNotExist,
#[error("lobby is full")]
LobbyFull,
#[error("client already in area")]
ClientInAreaAlready,
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("get area")]
pub enum GetAreaError {
#[error("not in a room")]
NotInRoom,
#[error("not in a lobby")]
NotInLobby,
#[error("get area: invalid client")]
InvalidClient,
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("client removal")]
pub enum ClientRemovalError {
#[error("client removal: client not in area")]
ClientNotInArea,
#[error("client removal: invalid area")]
InvalidArea,
}
@ -77,17 +86,20 @@ pub enum GetClientsError {
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("get neighbor")]
pub enum GetNeighborError {
#[error("get neighbor: invalid client")]
InvalidClient,
#[error("get neighbor: invalid area")]
InvalidArea,
}
#[derive(Error, Debug, PartialEq, Eq)]
#[error("get leader")]
pub enum GetLeaderError {
#[error("get leader: invalid client")]
InvalidClient,
#[error("get leader: invalid area")]
InvalidArea,
#[error("get leader: client not in area")]
NoClientInArea,
}

View File

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

View File

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

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

View File

@ -12,39 +12,45 @@ use libpso::packet::login::*;
use libpso::packet::ship::{MenuDetail, SmallLeftDialog};
use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher;
use crate::entity::item;
use libpso::character::character;
use entity::item;
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
use crate::common::leveltable::LEVEL_TABLE;
use libpso::{utf8_to_array, utf8_to_utf16_array};
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use networking::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
use stats::leveltable::LEVEL_TABLE;
use libpso::util::{utf8_to_array, utf8_to_utf16_array};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
use crate::entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankName, EquippedEntity, Meseta};
use crate::entity::item::weapon::Weapon;
use crate::entity::item::armor::Armor;
use crate::entity::item::tech::Technique;
use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag;
use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
use entity::gateway::{EntityGateway, GatewayError};
use entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
use entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankIdentifier, EquippedEntity, Meseta};
use entity::item::weapon::Weapon;
use entity::item::armor::Armor;
use entity::item::tech::Technique;
use entity::item::tool::Tool;
use entity::item::mag::Mag;
use entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
use crate::login::login::{get_login_status};
use crate::common::interserver::AuthToken;
use crate::login::get_login_status;
use networking::interserver::AuthToken;
use pktbuilder::ship::SHIP_MENU_ID;
pub const CHARACTER_PORT: u16 = 12001;
pub const SHIP_MENU_ID: u32 = 1;
#[derive(thiserror::Error, Debug)]
#[error("")]
pub enum CharacterError {
#[error("invalid menu selection {0} {1}")]
InvalidMenuSelection(u32, u32),
#[error("client not found {0}")]
ClientNotFound(ClientId),
CouldNotLoadSettings,
#[error("could not load settings {0}")]
CouldNotLoadSettings(GatewayError),
#[error("could not load characters")]
CouldNotLoadCharacters,
#[error("could not load guildcard")]
CouldNotLoadGuildcard,
#[error("gateway error {0}")]
GatewayError(#[from] GatewayError),
}
@ -144,7 +150,7 @@ fn generate_param_data(path: &str) -> (ParamDataHeader, Vec<u8>) {
size: len as u32,
checksum: crc.sum32(),
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);
@ -171,7 +177,7 @@ impl ClientState {
user: None,
characters: None,
guildcard_data_buffer: None,
session: Session::new(),
session: Session::default(),
}
}
}
@ -206,6 +212,7 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
let character = entity_gateway.create_character(character).await?;
entity_gateway.set_character_meseta(&character.id, Meseta(300)).await?;
entity_gateway.set_bank_meseta(&character.id, &BankIdentifier::Character, Meseta(0)).await?;
let new_weapon = match character.char_class {
CharacterClass::HUmar | CharacterClass::HUnewearl | CharacterClass::HUcast | CharacterClass::HUcaseal => item::weapon::WeaponType::Saber,
@ -260,6 +267,8 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
character_id: character.id,
}).await?;
entity_gateway.change_mag_owner(&mag.id, &character).await?;
let mut monomates = Vec::new();
for _ in 0..4usize {
let monomate = entity_gateway.create_item(
@ -297,7 +306,7 @@ async fn new_character<EG: EntityGateway + Clone>(entity_gateway: &mut EG, user:
InventoryItemEntity::Stacked(monomates), InventoryItemEntity::Stacked(monofluids)],
};
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 {
weapon: Some(weapon.id),
armor: Some(armor.id),
@ -333,15 +342,15 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
if let Some(connected_client) = self.connected_clients.read().await.get(&user.id) {
if let Some(expires) = connected_client.expires {
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 {
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.team_id = user.team_id.map_or(0, |ti| ti);
@ -358,7 +367,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
Ok(vec![SendCharacterPacket::LoginResponse(response)])
},
Err(err) => {
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::new()))])
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::default()))])
}
}
}
@ -370,7 +379,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
menu: SHIP_MENU_ID,
item: i.0 as u32,
flags: 0,
name: utf8_to_utf16_array!(s.name, 0x11)
name: utf8_to_utf16_array(&s.name)
}
}).collect()))
])
@ -385,7 +394,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
Ok(settings) => settings,
Err(_) => {
let user_settings = NewUserSettingsEntity::new(user.id);
self.entity_gateway.create_user_settings(user_settings).await.map_err(|_| CharacterError::CouldNotLoadSettings)?
self.entity_gateway.create_user_settings(user_settings).await.map_err(CharacterError::CouldNotLoadSettings)?
}
};
@ -476,7 +485,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
async fn set_flag(&mut self, id: ClientId, setflag: &SetFlag) -> Result<std::option::IntoIter<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let mut user = client.user.as_mut().unwrap();
let user = client.user.as_mut().unwrap();
user.flags = setflag.flags;
self.entity_gateway.save_user(user).await.unwrap();
Ok(None.into_iter())
@ -507,7 +516,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
async fn character_preview(&mut self, id: ClientId, preview: &CharacterPreview) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let mut user = client.user.as_mut().unwrap();
let user = client.user.as_mut().unwrap();
if user.flags == USERFLAG_NEWCHAR {
new_character(&mut self.entity_gateway, user, preview).await?
}
@ -741,7 +750,7 @@ impl<EG: EntityGateway + Clone> InterserverActor for CharacterServerState<EG> {
fn new_character_from_preview(user: &UserAccountEntity, preview: &CharacterPreview) -> NewCharacterEntity {
let mut character = NewCharacterEntity::new(user.id, 1); // it should not be possible for the client to specify the kbm config preset from the char create screen
let mut character = NewCharacterEntity::new(user.id);
character.slot = preview.slot;
character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into();
character.section_id = preview.character.section_id.into();
@ -815,7 +824,7 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
hair_b: character.appearance.hair_b,
prop_x: character.appearance.prop_x,
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,
..character::SelectScreenCharacter::default()
}
@ -826,9 +835,21 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
#[cfg(test)]
mod test {
use super::*;
use crate::entity::account::*;
use entity::account::*;
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 fn test_option_send() {
@ -838,7 +859,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction = ();
type Transaction<'a> = CharTestDb where Self: 'a;
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
Ok(UserSettingsEntity {
id: UserSettingsId(0),
@ -881,7 +902,7 @@ mod test {
#[derive(Clone)]
struct TestData;
impl EntityGateway for TestData {
type Transaction = ();
type Transaction<'a> = CharTestDb where Self: 'a;
}
let mut server = CharacterServerState::new(TestData {}, AuthToken("".into()));
let send = server.handle(ClientId(1), RecvCharacterPacket::Checksum(Checksum {checksum: 1234,

View File

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

View File

@ -11,18 +11,18 @@ use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher;
use libpso::util::array_to_utf8;
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::entity::gateway::EntityGateway;
use crate::entity::account::{UserAccountEntity};
use entity::gateway::EntityGateway;
use entity::account::{UserAccountEntity};
pub const LOGIN_PORT: u16 = 12000;
pub const COMMUNICATION_PORT: u16 = 12123;
#[derive(thiserror::Error, Debug)]
#[error("")]
pub enum LoginError {
#[error("dberror")]
DbError
}
@ -83,21 +83,13 @@ pub async fn get_login_status(entity_gateway: &mut impl EntityGateway, pkt: &Log
pub fn check_if_already_online(user: UserAccountEntity) -> Result<UserAccountEntity, AccountStatus> {
Ok(user)
/*
if user.is_currently_online() {
Err(AccountStatus::PayUp)
}
else {
Ok(user)
}
*/
}
#[derive(Clone)]
pub struct LoginServerState<EG: EntityGateway + Clone> {
character_server_ip: net::Ipv4Addr,
entity_gateway: EG,
clients: HashMap<ClientId, String>,
clients: HashMap<ClientId, String>, // TODO: this should be arc/mutex'd?
}
impl<EG: EntityGateway + Clone> LoginServerState<EG> {
@ -119,7 +111,7 @@ impl<EG: EntityGateway + Clone> LoginServerState<EG> {
let response = SendLoginPacket::LoginResponse(LoginResponse::by_status(AccountStatus::Ok, pkt.session));
let ip = u32::from_ne_bytes(self.character_server_ip.octets());
Ok(vec![response,
SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::login::character::CHARACTER_PORT))])
SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::character::CHARACTER_PORT))])
},
Err(err) => {
Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))])
@ -178,8 +170,8 @@ impl<EG: EntityGateway + Clone> ServerState for LoginServerState<EG> {
#[cfg(test)]
mod test {
use super::*;
use crate::entity::account::{UserAccountId};
use crate::entity::gateway::{EntityGatewayTransaction, GatewayError};
use entity::account::{UserAccountId};
use entity::gateway::{EntityGatewayTransaction, GatewayError};
const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login {
tag: 65536,
@ -204,13 +196,16 @@ mod test {
character_slot: 0,
}
});
impl EntityGateway for () {
type Transaction = ();
#[derive(Clone)]
struct LoginTestDb;
impl EntityGateway for LoginTestDb {
type Transaction<'t> = LoginTestDb where Self: 't;
}
impl EntityGatewayTransaction for () {
type ParentGateway = ();
impl EntityGatewayTransaction for LoginTestDb {
type ParentGateway = LoginTestDb;
}
#[async_std::test]
@ -221,7 +216,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction = ();
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {
@ -280,7 +275,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction = ();
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, _name: String) -> Result<UserAccountEntity, GatewayError> {
Err(GatewayError::Error)
}
@ -315,7 +310,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction = ();
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {
@ -365,7 +360,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction = ();
type Transaction<'t> = LoginTestDb where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {

14
src/maps/Cargo.toml Normal file
View 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 }

View File

@ -2,7 +2,7 @@
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use thiserror::Error;
use crate::ship::room::Episode;
use crate::room::Episode;
use std::fmt;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,19 +1,18 @@
// TOOD: `pub(super) for most of these?`
use std::io::{Read};
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use byteorder::{LittleEndian, ReadBytesExt};
use thiserror::Error;
use crate::ship::ship::ShipEvent;
use crate::ship::monster::MonsterType;
use crate::ship::room::Episode;
use crate::ship::map::*;
use rand::{Rng, SeedableRng};
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)]
pub struct RawMapEnemy {
@ -69,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)]
#[error("")]
pub enum MapEnemyError {
@ -76,6 +86,7 @@ pub enum MapEnemyError {
MapAreaError(#[from] MapAreaError),
}
// making this `pub type` doesn't allow `impl`s to be defined?
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RareMonsterAppearTable {
@ -99,9 +110,18 @@ impl RareMonsterAppearTable {
}
}
pub fn roll_is_rare(&self, monster: &MonsterType) -> bool {
fn roll_is_rare(&self, monster: &MonsterType) -> bool {
rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32)
}
pub fn apply(&self, enemy: MapEnemy, event: Holiday) -> MapEnemy {
if enemy.can_be_rare() && self.roll_is_rare(&enemy.monster) {
enemy.into_rare(event)
}
else {
enemy
}
}
}
@ -336,12 +356,12 @@ impl MapEnemy {
guaranteed rare monsters don't count towards the limit
*/
#[must_use]
pub fn into_rare(self, event: ShipEvent) -> MapEnemy {
pub fn into_rare(self, event: Holiday) -> MapEnemy {
match (self.monster, self.map_area.to_episode(), event) {
(MonsterType::RagRappy, Episode::One, _) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, ShipEvent::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, ShipEvent::Halloween) => {MapEnemy {monster: MonsterType::HalloRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, ShipEvent::Christmas) => {MapEnemy {monster: MonsterType::StRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, Holiday::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, Holiday::Halloween) => {MapEnemy {monster: MonsterType::HalloRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, Holiday::Christmas) => {MapEnemy {monster: MonsterType::StRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, _) => {MapEnemy {monster: MonsterType::LoveRappy, shiny:true, ..self}},
(MonsterType::Hildebear, _, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
(MonsterType::PoisonLily, _, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},

59
src/maps/src/lib.rs Normal file
View 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,
}
}
}

View File

@ -6,13 +6,15 @@ use std::fs::File;
use thiserror::Error;
use crate::ship::ship::ShipEvent;
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>> {
let mut object_data = Vec::new();
@ -35,7 +37,7 @@ fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) ->
enemy
.map_or(vec![None], |monster| {
let mut monsters = vec![Some(monster)];
match monster.monster {
MonsterType::Monest => {
for _ in 0..30 {
@ -172,25 +174,10 @@ fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<
}
#[derive(Error, Debug)]
#[error("")]
pub enum MapsError {
InvalidMonsterId(usize),
InvalidObjectId(usize),
}
#[derive(Debug)]
pub struct Maps {
map_variants: Vec<MapVariant>,
enemy_data: Vec<Option<MapEnemy>>,
object_data: Vec<Option<MapObject>>,
}
impl Maps {
pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable, event: ShipEvent) -> Maps {
let map_variants = match (room_mode.episode(), room_mode.single_player()) {
(Episode::One, 0) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
pub fn default_map_variants(episode: Episode, player_mode: PlayerMode) -> Vec<MapVariant> {
match (episode, player_mode) {
(Episode::One, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
MapVariant::new(MapArea::Forest1, MapVariantMode::Online),
MapVariant::new(MapArea::Forest2, MapVariantMode::Online),
MapVariant::new(MapArea::Caves1, MapVariantMode::Online),
@ -205,10 +192,10 @@ impl Maps {
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online),
MapVariant::new(MapArea::VolOpt, MapVariantMode::Online),
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online),
]
},
(Episode::One, 1) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
]
},
(Episode::One, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline),
MapVariant::new(MapArea::Forest1, MapVariantMode::Offline),
MapVariant::new(MapArea::Forest2, MapVariantMode::Offline),
MapVariant::new(MapArea::Caves1, MapVariantMode::Offline),
@ -223,10 +210,10 @@ impl Maps {
MapVariant::new(MapArea::DeRolLe, MapVariantMode::Offline),
MapVariant::new(MapArea::VolOpt, MapVariantMode::Offline),
MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline),
]
},
(Episode::Two, 0) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
]
},
(Episode::Two, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online),
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online),
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online),
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Online),
@ -242,10 +229,10 @@ impl Maps {
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Online),
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Online),
MapVariant::new(MapArea::GolDragon, MapVariantMode::Online),
]
},
(Episode::Two, 1) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
]
},
(Episode::Two, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline),
MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline),
MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline),
MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Offline),
@ -261,10 +248,10 @@ impl Maps {
MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Offline),
MapVariant::new(MapArea::BarbaRay, MapVariantMode::Offline),
MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline),
]
},
(Episode::Four, _) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
]
},
(Episode::Four, PlayerMode::Multi) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online),
MapVariant::new(MapArea::CraterEast, MapVariantMode::Online),
MapVariant::new(MapArea::CraterWest, MapVariantMode::Online),
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Online),
@ -274,23 +261,44 @@ impl Maps {
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Online),
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Online),
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Online),
]
},
_ => unreachable!()
};
]
},
(Episode::Four, PlayerMode::Single) => {
vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterEast, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterWest, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterSouth, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterNorth, MapVariantMode::Offline),
MapVariant::new(MapArea::CraterInterior, MapVariantMode::Offline),
MapVariant::new(MapArea::SubDesert1, MapVariantMode::Offline),
MapVariant::new(MapArea::SubDesert2, MapVariantMode::Offline),
MapVariant::new(MapArea::SubDesert3, MapVariantMode::Offline),
MapVariant::new(MapArea::SaintMillion, MapVariantMode::Offline),
]
},
}
}
#[derive(Error, Debug)]
#[error("")]
pub enum MapsError {
InvalidMonsterId(usize),
InvalidObjectId(usize),
}
#[derive(Debug)]
pub struct Maps {
map_variants: Vec<MapVariant>,
enemy_data: Vec<Option<MapEnemy>>,
object_data: Vec<Option<MapObject>>,
}
impl Maps {
pub fn new(map_variants: Vec<MapVariant>, enemy_data: Vec<Option<MapEnemy>>, object_data: Vec<Option<MapObject>>) -> Maps {
Maps {
enemy_data: map_variants.iter()
.flat_map(|map_variant| {
enemy_data_from_map_data(map_variant, &room_mode.episode())
})
.map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
.collect(),
object_data: map_variants.iter()
.flat_map(|map_variant| {
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
}).collect(),
map_variants,
enemy_data,
object_data,
}
}
@ -318,11 +326,11 @@ impl Maps {
enemies: Vec<Option<MapEnemy>>,
objects: Vec<Option<MapObject>>,
rare_monster_table: &RareMonsterAppearTable,
event: ShipEvent)
event: Holiday)
{
self.enemy_data = enemies
.into_iter()
.map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
.map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event)))
.collect();
self.object_data = objects;
}
@ -351,13 +359,29 @@ impl Maps {
}
}
fn apply_rare_enemy(enemy: Option<MapEnemy>, rare_enemy_table: &RareMonsterAppearTable, event: ShipEvent) -> Option<MapEnemy> {
enemy.map(|enemy| {
if enemy.can_be_rare() && rare_enemy_table.roll_is_rare(&enemy.monster) {
enemy.into_rare(event)
}
else {
enemy
}
})
pub fn generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> Maps {
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode());
Maps {
enemy_data: map_variants.iter()
.flat_map(|map_variant| {
enemy_data_from_map_data(map_variant, &room_mode.episode())
})
.map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event)))
.collect(),
object_data: map_variants.iter()
.flat_map(|map_variant| {
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
}).collect(),
map_variants,
}
}
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(),
}
}

View File

@ -4,7 +4,7 @@ use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use serde::{Serialize, Deserialize};
use crate::ship::room::{Difficulty, Episode, RoomMode};
use crate::room::{Difficulty, Episode, RoomMode};
#[derive(Debug)]
@ -149,7 +149,7 @@ pub enum MonsterType {
}
#[derive(serde::Deserialize, Debug)]
#[derive(Deserialize, Debug)]
pub struct MonsterStats {
pub atp: u16,
pub mst: u16,

View File

@ -1,13 +1,11 @@
// TOOD: `pub(super) for most of these?`
use std::io::{Read};
use std::io::Read;
use byteorder::{LittleEndian, ReadBytesExt};
use crate::ship::room::Episode;
// TODO: don't use *
use crate::ship::map::*;
use crate::room::Episode;
use crate::area::MapArea;
#[derive(Debug, Copy, Clone)]

150
src/maps/src/room.rs Normal file
View 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,
}
}
}

View File

@ -3,7 +3,8 @@
use rand::Rng;
// TODO: don't use *
use crate::ship::map::*;
//use crate::map::*;
use crate::area::MapArea;
#[derive(Debug, PartialEq, Eq)]
pub enum MapVariantMode {

18
src/networking/Cargo.toml Normal file
View 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 }

View File

@ -2,8 +2,8 @@ use std::net::Ipv4Addr;
use async_std::channel;
use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned;
use crate::entity::account::UserAccountId;
use crate::entity::character::CharacterEntityId;
use entity::account::UserAccountId;
use entity::character::CharacterEntityId;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ServerId(pub usize);

View File

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

View File

@ -1,15 +1,16 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Write;
use async_std::channel;
use async_std::io::prelude::{ReadExt, WriteExt};
use async_std::sync::{Arc, RwLock};
use futures::future::Future;
use log::{trace, info, warn};
use log::{trace, info, warn, error};
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use libpso::PacketParseError;
use crate::common::serverstate::ClientId;
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
use crate::serverstate::ClientId;
use crate::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
#[derive(Debug)]
@ -71,7 +72,7 @@ impl<C: PSOCipher> PacketReceiver<C> {
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();
let buf = self.recv_buffer.drain(..block_chunk_len).collect::<Vec<_>>();
self.cipher.decrypt(&buf)?
};
self.incoming_data.append(&mut dec_buf);
@ -147,7 +148,27 @@ where
}
},
Err(err) => {
warn!("[client recv {:?}] error {:?} ", client_id, err);
error!("[client recv {:?}] error {:?} ", client_id, err);
let mut f = std::fs::File::options().create(true).append(true).open("errors.txt").unwrap();
f.write_all(format!("[{client_id:?}] {err:?}").as_bytes()).unwrap();
// disconnect client on an error
for pkt in state.on_disconnect(client_id).await.unwrap() {
clients
.read()
.await
.get(&pkt.0)
.unwrap()
.send(pkt.1)
.await
.unwrap();
}
clients
.write()
.await
.remove(&client_id);
break;
}
}
}
@ -173,7 +194,7 @@ where
break;
}
_ => {
warn!("[client {:?} recv error] {:?}", client_id, err);
error!("[client {:?} recv error] {:?}", client_id, err);
}
}
}
@ -206,7 +227,7 @@ where
Ok(pkt) => {
info!("[send to {:?}] {:#?}", client_id, pkt);
if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await {
warn!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
error!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
}
},
Err(err) => {
@ -234,7 +255,7 @@ where
let (mut socket, addr) = listener.accept().await.unwrap();
id += 1;
let client_id = crate::common::serverstate::ClientId(id);
let client_id = crate::serverstate::ClientId(id);
info!("new client {:?} {:?} {:?}", client_id, socket, addr);
let (client_tx, client_rx) = async_std::channel::unbounded();
@ -254,7 +275,9 @@ where
cipher_out = Some(cout);
},
OnConnect::Packet(pkt) => {
send_pkt(&mut socket, &mut NullCipher {}, &pkt).await.unwrap();
if let Err(err) = send_pkt(&mut socket, &mut NullCipher {}, &pkt).await {
error!("error sending on_connect packet {:?}", err);
}
}
}
}

View File

@ -8,13 +8,11 @@ use std::collections::HashMap;
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::common::interserver::{ServerId, InterserverActor};
use crate::interserver::{ServerId, InterserverActor};
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use crate::common::serverstate::{ServerState, SendServerPacket, RecvServerPacket};
use crate::login::character::CharacterServerState;
//use crate::ship::ship::ShipServerState;
use crate::entity::gateway::entitygateway::EntityGateway;
use crate::serverstate::{ServerState, SendServerPacket, RecvServerPacket};
use entity::gateway::entitygateway::EntityGateway;
use async_std::channel;
use std::fmt::Debug;
@ -149,7 +147,7 @@ where
info!("[interserver listen] new server: {:?} {:?}", socket, addr);
id += 1;
let server_id = crate::common::interserver::ServerId(id);
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;
@ -196,7 +194,7 @@ where
}
};
id += 1;
let server_id = crate::common::interserver::ServerId(id);
let server_id = crate::interserver::ServerId(id);
info!("[interserver connect] found loginserv: {:?} {:?}", server_id, socket);
let (client_tx, client_rx) = async_std::channel::unbounded();
@ -220,12 +218,8 @@ where
let mut buf = [0u8; 1];
loop {
let peek = socket.peek(&mut buf).await;
match peek {
Ok(len) if len == 0 => {
break
},
_ => {
}
if let Ok(0) = peek {
break
}
}
}

View File

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

View File

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

View File

@ -11,8 +11,8 @@ use libpso::crypto::pc::PSOPCCipher;
use ron::de::from_str;
use serde::Deserialize;
use crate::common::mainloop::{NetworkError};
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
use networking::mainloop::{NetworkError};
use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
#[derive(Debug)]
pub enum PatchError {
@ -341,7 +341,7 @@ impl Iterator for SendFileIterator {
if len == 0 {
self.current_file = None;
self.chunk_num = 0;
Some(SendPatchPacket::EndFileSend(EndFileSend::new()))
Some(SendPatchPacket::EndFileSend(EndFileSend::default()))
}
else {
let mut crc = crc32::Digest::new(crc32::IEEE);
@ -395,18 +395,18 @@ pub struct PatchConfig {
pub fn load_config() -> PatchConfig {
let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) {
Err(err) => panic!("Failed to open patch.ron config file. \n{}", err),
Err(err) => panic!("Failed to open patch.ron config file. \n{err}"),
Ok(ini_file) => ini_file,
};
let mut s = String::new();
if let Err(err) = (&ini_file).read_to_string(&mut s) {
panic!("Failed to read patch.ron config file. \n{}", err);
panic!("Failed to read patch.ron config file. \n{err}");
}
let config: PatchConfig = match from_str(s.as_str()) {
Ok(config) => config,
Err(err) => panic!("Failed to load values from patch.ron \n{}",err),
Err(err) => panic!("Failed to load values from patch.ron \n{err}"),
};
config
}

22
src/pktbuilder/Cargo.toml Normal file
View File

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

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