Compare commits
1 Commits
master
...
morfin/tes
Author | SHA1 | Date | |
---|---|---|---|
97f7a202e1 |
16
.drone.yml
16
.drone.yml
@ -1,24 +1,12 @@
|
||||
---
|
||||
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:
|
||||
@ -45,7 +33,7 @@ steps:
|
||||
- name: target-cache
|
||||
path: /drone/src/target
|
||||
commands:
|
||||
- cargo test --jobs 1
|
||||
- cargo test
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
|
2388
Cargo.lock
generated
Normal file
2388
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
72
Cargo.toml
72
Cargo.toml
@ -4,46 +4,8 @@ version = "0.1.0"
|
||||
authors = ["Jake Probst <jake.probst@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"src/client",
|
||||
"src/drops",
|
||||
"src/entity",
|
||||
"src/items",
|
||||
"src/location",
|
||||
"src/maps",
|
||||
"src/networking",
|
||||
"src/pktbuilder",
|
||||
"src/quests",
|
||||
"src/room",
|
||||
"src/shops",
|
||||
"src/stats",
|
||||
"src/trade",
|
||||
"src/patch_server",
|
||||
"src/login_server",
|
||||
"src/ship_server",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
entity = { path = "./src/entity" }
|
||||
maps = { path = "./src/maps" }
|
||||
networking = { path = "./src/networking" }
|
||||
shops = { path = "./src/shops" }
|
||||
stats = { path = "./src/stats" }
|
||||
items = { path = "./src/items" }
|
||||
pktbuilder = { path = "./src/pktbuilder" }
|
||||
quests = { path = "./src/quests" }
|
||||
location = { path = "./src/location" }
|
||||
client = { path = "./src/client" }
|
||||
drops = { path = "./src/drops" }
|
||||
trade = { path = "./src/trade" }
|
||||
room = { path = "./src/room" }
|
||||
patch_server = { path = "./src/patch_server" }
|
||||
login_server = { path = "./src/login_server" }
|
||||
ship_server = { path = "./src/ship_server" }
|
||||
|
||||
libpso = { git = "http://git.sharnoth.com/jake/libpso", rev="90246b6" }
|
||||
|
||||
[dependencies]
|
||||
libpso = { git = "http://git.sharnoth.com/jake/libpso" }
|
||||
async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
|
||||
futures = "0.3.5"
|
||||
rand = "0.7.3"
|
||||
@ -65,33 +27,9 @@ 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.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
|
||||
sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
|
||||
strum = "0.19.5"
|
||||
strum_macros = "0.19"
|
||||
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 }
|
||||
anyhow = { version = "1.0.47", features = ["backtrace"] }
|
||||
|
@ -11,7 +11,7 @@ photon_blast = "Pilla"
|
||||
|
||||
[Surya]
|
||||
feed_table = 3
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Vayu]
|
||||
feed_table = 4
|
||||
@ -19,7 +19,7 @@ photon_blast = "MyllaYoulla"
|
||||
|
||||
[Varaha]
|
||||
feed_table = 4
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Kama]
|
||||
feed_table = 4
|
||||
@ -27,7 +27,7 @@ photon_blast = "Pilla"
|
||||
|
||||
[Ushasu]
|
||||
feed_table = 4
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Apsaras]
|
||||
feed_table = 4
|
||||
@ -35,7 +35,7 @@ photon_blast = "Estlla"
|
||||
|
||||
[Kumara]
|
||||
feed_table = 4
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Kaitabha]
|
||||
feed_table = 4
|
||||
@ -55,7 +55,7 @@ photon_blast = "Estlla"
|
||||
|
||||
[Rudra]
|
||||
feed_table = 2
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Marutah]
|
||||
feed_table = 2
|
||||
@ -63,7 +63,7 @@ photon_blast = "Pilla"
|
||||
|
||||
[Yaksa]
|
||||
feed_table = 5
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Sita]
|
||||
feed_table = 5
|
||||
@ -99,7 +99,7 @@ photon_blast = "Estlla"
|
||||
|
||||
[Vritra]
|
||||
feed_table = 1
|
||||
photon_blast = "Leilla"
|
||||
photon_blast = "Golla"
|
||||
|
||||
[Namuci]
|
||||
feed_table = 2
|
||||
@ -107,7 +107,7 @@ photon_blast = "MyllaYoulla"
|
||||
|
||||
[Sumba]
|
||||
feed_table = 2
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Naga]
|
||||
feed_table = 6
|
||||
@ -144,7 +144,7 @@ photon_blast = "Estlla"
|
||||
|
||||
[Naraka]
|
||||
feed_table = 6
|
||||
photon_blast = "Golla"
|
||||
photon_blast = "Leilla"
|
||||
|
||||
[Madhu]
|
||||
feed_table = 6
|
||||
|
@ -1,8 +1,8 @@
|
||||
use log::{info};
|
||||
use entity::gateway::postgres::PostgresGateway;
|
||||
use login_server::login::LoginServerState;
|
||||
use login_server::character::CharacterServerState;
|
||||
use networking::interserver::AuthToken;
|
||||
use elseware::entity::gateway::postgres::PostgresGateway;
|
||||
use elseware::login::login::LoginServerState;
|
||||
use elseware::login::character::CharacterServerState;
|
||||
use elseware::common::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 {
|
||||
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await;
|
||||
elseware::common::mainloop::run_server(login_state, elseware::login::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 {
|
||||
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
|
||||
elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
|
||||
});
|
||||
|
||||
let inter_character_loop = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_interserver_listen(char_state, login_server::login::COMMUNICATION_PORT).await;
|
||||
elseware::common::mainloop::run_interserver_listen(char_state, elseware::login::login::COMMUNICATION_PORT).await;
|
||||
});
|
||||
|
||||
info!("[auth/character] starting server");
|
||||
|
@ -1,18 +1,18 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use log::{info};
|
||||
|
||||
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;
|
||||
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 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;
|
||||
#[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;
|
||||
|
||||
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{i}@email.com"),
|
||||
email: format!("fake{}@email.com", i),
|
||||
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);
|
||||
let mut character = NewCharacterEntity::new(fake_user.id, 1);
|
||||
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::BankIdentifier::Character, item::Meseta(999999)).await.unwrap();
|
||||
let mut character = NewCharacterEntity::new(fake_user.id);
|
||||
entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
|
||||
let mut character = NewCharacterEntity::new(fake_user.id, 1);
|
||||
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::BankIdentifier::Character, item::Meseta(999999)).await.unwrap();
|
||||
entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap();
|
||||
|
||||
for _ in 0..3 {
|
||||
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::BankIdentifier::Character).await.unwrap();
|
||||
entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), &item::BankName("".into())).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 {
|
||||
networking::mainloop::run_server(patch_state, patch_config.port).await;
|
||||
elseware::common::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 {
|
||||
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await;
|
||||
elseware::common::mainloop::run_server(login_state, elseware::login::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 {
|
||||
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
|
||||
elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
|
||||
});
|
||||
|
||||
let sub_char_state = char_state.clone();
|
||||
let inter_character_loop = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_interserver_listen(sub_char_state, login_server::login::COMMUNICATION_PORT).await;
|
||||
elseware::common::mainloop::run_interserver_listen(sub_char_state, elseware::login::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(ship_server::SHIP_PORT)
|
||||
.event(Holiday::Halloween)
|
||||
.port(elseware::ship::ship::SHIP_PORT)
|
||||
.event(ShipEvent::Halloween)
|
||||
.gateway(entity_gateway.clone())
|
||||
.build();
|
||||
let sub_ship_state = ship_state.clone();
|
||||
let ship_loop1 = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await;
|
||||
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
|
||||
});
|
||||
let sub_ship_state = ship_state.clone();
|
||||
let inter_ship_loop1 = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
|
||||
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
|
||||
});
|
||||
|
||||
let ship_state = ShipServerStateBuilder::default()
|
||||
.name("EU/Dylath-Leen".into())
|
||||
.ip(Ipv4Addr::new(127,0,0,1))
|
||||
.port(ship_server::SHIP_PORT+2000)
|
||||
.event(Holiday::Christmas)
|
||||
.port(elseware::ship::ship::SHIP_PORT+2000)
|
||||
.event(ShipEvent::Christmas)
|
||||
.gateway(entity_gateway.clone())
|
||||
.build();
|
||||
let sub_ship_state = ship_state.clone();
|
||||
let ship_loop2 = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+2000).await;
|
||||
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+2000).await;
|
||||
});
|
||||
let sub_ship_state = ship_state.clone();
|
||||
let inter_ship_loop2 = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
|
||||
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
|
||||
});
|
||||
|
||||
let ship_state = ShipServerStateBuilder::default()
|
||||
.name("JP/Thalarion".into())
|
||||
.ip(Ipv4Addr::new(127,0,0,1))
|
||||
.port(ship_server::SHIP_PORT+3000)
|
||||
.port(elseware::ship::ship::SHIP_PORT+3000)
|
||||
.gateway(entity_gateway.clone())
|
||||
.build();
|
||||
let sub_ship_state = ship_state.clone();
|
||||
let ship_loop3 = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+3000).await;
|
||||
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+3000).await;
|
||||
});
|
||||
let sub_ship_state = ship_state.clone();
|
||||
let inter_ship_loop3 = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
|
||||
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
|
||||
});
|
||||
|
||||
futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use patch_server::{PatchServerState, generate_patch_tree, load_config_env, load_motd};
|
||||
use log::info;
|
||||
use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config_env, load_motd};
|
||||
use log::{info};
|
||||
|
||||
fn main() {
|
||||
info!("[patch] starting server");
|
||||
@ -9,8 +9,10 @@ fn main() {
|
||||
let patch_state = PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd);
|
||||
|
||||
let patch_loop = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_server(patch_state, patch_config.port).await;
|
||||
elseware::common::mainloop::run_server(patch_state, patch_config.port).await;
|
||||
});
|
||||
|
||||
async_std::task::block_on(patch_loop);
|
||||
async_std::task::block_on(async move {
|
||||
patch_loop.await
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use log::info;
|
||||
use entity::gateway::postgres::PostgresGateway;
|
||||
use ship_server::ShipServerStateBuilder;
|
||||
use networking::interserver::AuthToken;
|
||||
use log::{info};
|
||||
use elseware::entity::gateway::postgres::PostgresGateway;
|
||||
use elseware::ship::ship::ShipServerStateBuilder;
|
||||
use elseware::common::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(ship_server::SHIP_PORT)
|
||||
.port(elseware::ship::ship::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 {
|
||||
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await;
|
||||
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
|
||||
});
|
||||
let inter_ship_loop = async_std::task::spawn(async move {
|
||||
networking::mainloop::run_interserver_connect(ship_state, shipgate_ip, login_server::login::COMMUNICATION_PORT).await;
|
||||
elseware::common::mainloop::run_interserver_connect(ship_state, shipgate_ip, elseware::login::login::COMMUNICATION_PORT).await;
|
||||
});
|
||||
|
||||
info!("[auth/character] starting server");
|
||||
|
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
entity = { workspace = true }
|
||||
maps = { workspace = true }
|
||||
networking = { workspace = true }
|
||||
shops = { workspace = true }
|
||||
items = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
async-std = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
chrono = { workspace = true }
|
@ -2,8 +2,8 @@ use std::net::Ipv4Addr;
|
||||
use async_std::channel;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use entity::account::UserAccountId;
|
||||
use entity::character::CharacterEntityId;
|
||||
use crate::entity::account::UserAccountId;
|
||||
use crate::entity::character::CharacterEntityId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ServerId(pub usize);
|
@ -1,6 +1,6 @@
|
||||
use std::fs::File;
|
||||
use serde_json::Value;
|
||||
use entity::character::CharacterClass;
|
||||
use crate::entity::character::CharacterClass;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub static LEVEL_TABLE: LazyLock<CharacterLevelTable> = LazyLock::new(CharacterLevelTable::default);
|
@ -1,16 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Write;
|
||||
use async_std::channel;
|
||||
use async_std::io::prelude::{ReadExt, WriteExt};
|
||||
use async_std::sync::{Arc, RwLock};
|
||||
use futures::future::Future;
|
||||
use log::{trace, info, warn, error};
|
||||
use log::{trace, info, warn};
|
||||
|
||||
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
|
||||
use libpso::PacketParseError;
|
||||
use crate::serverstate::ClientId;
|
||||
use crate::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
|
||||
use crate::common::serverstate::ClientId;
|
||||
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -72,7 +71,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::<Vec<_>>();
|
||||
let buf = self.recv_buffer.drain(..block_chunk_len).collect();
|
||||
self.cipher.decrypt(&buf)?
|
||||
};
|
||||
self.incoming_data.append(&mut dec_buf);
|
||||
@ -148,27 +147,7 @@ where
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("[client recv {:?}] error {:?} ", client_id, err);
|
||||
|
||||
let mut f = std::fs::File::options().create(true).append(true).open("errors.txt").unwrap();
|
||||
f.write_all(format!("[{client_id:?}] {err:?}").as_bytes()).unwrap();
|
||||
|
||||
// disconnect client on an error
|
||||
for pkt in state.on_disconnect(client_id).await.unwrap() {
|
||||
clients
|
||||
.read()
|
||||
.await
|
||||
.get(&pkt.0)
|
||||
.unwrap()
|
||||
.send(pkt.1)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
clients
|
||||
.write()
|
||||
.await
|
||||
.remove(&client_id);
|
||||
break;
|
||||
warn!("[client recv {:?}] error {:?} ", client_id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,7 +173,7 @@ where
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
error!("[client {:?} recv error] {:?}", client_id, err);
|
||||
warn!("[client {:?} recv error] {:?}", client_id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,7 +206,7 @@ where
|
||||
Ok(pkt) => {
|
||||
info!("[send to {:?}] {:#?}", client_id, pkt);
|
||||
if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await {
|
||||
error!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
|
||||
warn!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
@ -255,7 +234,7 @@ where
|
||||
let (mut socket, addr) = listener.accept().await.unwrap();
|
||||
id += 1;
|
||||
|
||||
let client_id = crate::serverstate::ClientId(id);
|
||||
let client_id = crate::common::serverstate::ClientId(id);
|
||||
info!("new client {:?} {:?} {:?}", client_id, socket, addr);
|
||||
|
||||
let (client_tx, client_rx) = async_std::channel::unbounded();
|
||||
@ -275,9 +254,7 @@ where
|
||||
cipher_out = Some(cout);
|
||||
},
|
||||
OnConnect::Packet(pkt) => {
|
||||
if let Err(err) = send_pkt(&mut socket, &mut NullCipher {}, &pkt).await {
|
||||
error!("error sending on_connect packet {:?}", err);
|
||||
}
|
||||
send_pkt(&mut socket, &mut NullCipher {}, &pkt).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,11 +8,13 @@ use std::collections::HashMap;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::interserver::{ServerId, InterserverActor};
|
||||
use crate::common::interserver::{ServerId, InterserverActor};
|
||||
|
||||
use libpso::crypto::{PSOCipher, NullCipher, CipherError};
|
||||
use crate::serverstate::{ServerState, SendServerPacket, RecvServerPacket};
|
||||
use entity::gateway::entitygateway::EntityGateway;
|
||||
use crate::common::serverstate::{ServerState, SendServerPacket, RecvServerPacket};
|
||||
use crate::login::character::CharacterServerState;
|
||||
//use crate::ship::ship::ShipServerState;
|
||||
use crate::entity::gateway::entitygateway::EntityGateway;
|
||||
|
||||
use async_std::channel;
|
||||
use std::fmt::Debug;
|
||||
@ -147,7 +149,7 @@ where
|
||||
info!("[interserver listen] new server: {:?} {:?}", socket, addr);
|
||||
|
||||
id += 1;
|
||||
let server_id = crate::interserver::ServerId(id);
|
||||
let server_id = crate::common::interserver::ServerId(id);
|
||||
let (client_tx, client_rx) = async_std::channel::unbounded();
|
||||
state.set_sender(server_id, client_tx.clone()).await;
|
||||
|
||||
@ -194,7 +196,7 @@ where
|
||||
}
|
||||
};
|
||||
id += 1;
|
||||
let server_id = crate::interserver::ServerId(id);
|
||||
let server_id = crate::common::interserver::ServerId(id);
|
||||
info!("[interserver connect] found loginserv: {:?} {:?}", server_id, socket);
|
||||
|
||||
let (client_tx, client_rx) = async_std::channel::unbounded();
|
||||
@ -218,8 +220,12 @@ where
|
||||
let mut buf = [0u8; 1];
|
||||
loop {
|
||||
let peek = socket.peek(&mut buf).await;
|
||||
if let Ok(0) = peek {
|
||||
break
|
||||
match peek {
|
||||
Ok(len) if len == 0 => {
|
||||
break
|
||||
},
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
src/common/mod.rs
Normal file
19
src/common/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
pub mod cipherkeys;
|
||||
pub mod serverstate;
|
||||
pub mod mainloop;
|
||||
pub mod leveltable;
|
||||
pub mod interserver;
|
||||
|
||||
// https://www.reddit.com/r/rust/comments/33xhhu/how_to_create_an_array_of_structs_that_havent/
|
||||
#[macro_export]
|
||||
macro_rules! init_array(
|
||||
($ty:ty, $len:expr, $val:expr) => (
|
||||
{
|
||||
let mut array: [$ty; $len] = unsafe { std::mem::uninitialized() };
|
||||
for i in array.iter_mut() {
|
||||
unsafe { ::std::ptr::write(i, $val); }
|
||||
}
|
||||
array
|
||||
}
|
||||
)
|
||||
);
|
@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "drops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
entity = { workspace = true }
|
||||
maps = { workspace = true }
|
||||
stats = { workspace = true }
|
||||
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
enum-utils = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
chrono = { workspace = true }
|
@ -1,298 +0,0 @@
|
||||
#![allow(dead_code, unused_must_use)]
|
||||
// TODO: there is some structure duplication that occurs here:
|
||||
// the rare and box tables instantiate their own copies of the
|
||||
// generic drop tables as they need them to apply their modifiers
|
||||
// to their drops
|
||||
|
||||
|
||||
pub mod rare_drop_table;
|
||||
mod generic_weapon;
|
||||
mod generic_armor;
|
||||
mod generic_shield;
|
||||
mod generic_unit;
|
||||
mod tool_table;
|
||||
mod tech_table;
|
||||
mod box_drop_table;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::io::Read;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
use maps::monster::MonsterType;
|
||||
use maps::room::{Difficulty, Episode};
|
||||
use maps::area::MapArea;
|
||||
use entity::character::SectionID;
|
||||
use crate::generic_weapon::GenericWeaponTable;
|
||||
use crate::generic_armor::GenericArmorTable;
|
||||
use crate::generic_shield::GenericShieldTable;
|
||||
use crate::generic_unit::GenericUnitTable;
|
||||
use crate::tool_table::ToolTable;
|
||||
use crate::rare_drop_table::RareDropTable;
|
||||
use crate::box_drop_table::BoxDropTable;
|
||||
use maps::object::MapObject;
|
||||
use entity::item::{ItemType, weapon, armor, shield, unit, mag, tool, tech, esweapon};
|
||||
|
||||
|
||||
fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
|
||||
let mut path = PathBuf::from("data/drops/");
|
||||
path.push(episode.to_string());
|
||||
path.push(difficulty.to_string().to_lowercase());
|
||||
path.push(section_id.to_string().to_lowercase());
|
||||
path.push(filename);
|
||||
path
|
||||
}
|
||||
|
||||
pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
|
||||
let path = data_file_path(episode, difficulty, section_id, filename);
|
||||
let mut f = File::open(path).unwrap();
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s);
|
||||
|
||||
toml::from_str::<T>(s.as_str()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum MonsterDropType {
|
||||
#[serde(rename = "weapon")]
|
||||
Weapon,
|
||||
#[serde(rename = "armor")]
|
||||
Armor,
|
||||
#[serde(rename = "shield")]
|
||||
Shield,
|
||||
#[serde(rename = "unit")]
|
||||
Unit,
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct MonsterDropStats {
|
||||
pub dar: u32,
|
||||
pub drop_type: MonsterDropType,
|
||||
pub min_meseta: u32,
|
||||
pub max_meseta: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ItemDropType {
|
||||
Weapon(weapon::Weapon),
|
||||
Armor(armor::Armor),
|
||||
Shield(shield::Shield),
|
||||
Unit(unit::Unit),
|
||||
Tool(tool::Tool),
|
||||
//Tools(Vec<tool::Tool>),
|
||||
TechniqueDisk(tech::TechniqueDisk),
|
||||
Mag(mag::Mag),
|
||||
Meseta(u32),
|
||||
}
|
||||
|
||||
impl ItemDropType {
|
||||
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
|
||||
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
|
||||
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
|
||||
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
|
||||
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
|
||||
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
|
||||
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
|
||||
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
|
||||
|
||||
match item_type {
|
||||
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
|
||||
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
|
||||
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
|
||||
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
|
||||
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
|
||||
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ItemDrop {
|
||||
pub map_area: MapArea,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub item: ItemDropType,
|
||||
}
|
||||
|
||||
|
||||
pub trait DropTable {
|
||||
fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType>;
|
||||
fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType>;
|
||||
}
|
||||
|
||||
pub struct StandardDropTable {
|
||||
monster_stats: HashMap<MonsterType, MonsterDropStats>,
|
||||
rare_table: RareDropTable,
|
||||
weapon_table: GenericWeaponTable,
|
||||
armor_table: GenericArmorTable,
|
||||
shield_table: GenericShieldTable,
|
||||
unit_table: GenericUnitTable,
|
||||
tool_table: ToolTable,
|
||||
box_table: BoxDropTable,
|
||||
rng: rand_chacha::ChaCha20Rng,
|
||||
}
|
||||
|
||||
impl StandardDropTable {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
|
||||
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
|
||||
|
||||
Box::new(StandardDropTable {
|
||||
monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
|
||||
rare_table: RareDropTable::new(episode, difficulty, section_id),
|
||||
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
|
||||
armor_table: GenericArmorTable::new(episode, difficulty, section_id),
|
||||
shield_table: GenericShieldTable::new(episode, difficulty, section_id),
|
||||
unit_table: GenericUnitTable::new(episode, difficulty, section_id),
|
||||
tool_table: ToolTable::new(episode, difficulty, section_id),
|
||||
box_table: BoxDropTable::new(episode, difficulty, section_id),
|
||||
rng: rand_chacha::ChaCha20Rng::from_entropy(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn builder() -> DropTableBuilder {
|
||||
DropTableBuilder {
|
||||
monster_stats: None,
|
||||
rare_table: None,
|
||||
weapon_table: None,
|
||||
armor_table: None,
|
||||
shield_table: None,
|
||||
unit_table: None,
|
||||
tool_table: None,
|
||||
box_table: None,
|
||||
rng: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option<ItemDropType> {
|
||||
Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1)))
|
||||
}
|
||||
|
||||
fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
|
||||
match monster.drop_type {
|
||||
MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DropTable for StandardDropTable {
|
||||
fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
|
||||
let monster_stat = *self.monster_stats.get(monster)?;
|
||||
|
||||
let drop_anything = self.rng.gen_range(0, 100);
|
||||
if drop_anything > monster_stat.dar {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(item) = self.rare_table.get_drop(map_area, monster, &mut self.rng) {
|
||||
return Some(item);
|
||||
}
|
||||
|
||||
let drop_type = self.rng.gen_range(0, 3);
|
||||
|
||||
match drop_type {
|
||||
0 => {
|
||||
self.generate_meseta(&monster_stat)
|
||||
},
|
||||
1 => {
|
||||
self.tool_table.get_drop(map_area, &mut self.rng)
|
||||
},
|
||||
2 => {
|
||||
self.generate_typed_drop(map_area, &monster_stat)
|
||||
},
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
|
||||
self.box_table.get_drop(map_area, object, &mut self.rng)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct DropTableBuilder {
|
||||
monster_stats: Option<HashMap<MonsterType, MonsterDropStats>>,
|
||||
rare_table: Option<RareDropTable>,
|
||||
weapon_table: Option<GenericWeaponTable>,
|
||||
armor_table: Option<GenericArmorTable>,
|
||||
shield_table: Option<GenericShieldTable>,
|
||||
unit_table: Option<GenericUnitTable>,
|
||||
tool_table: Option<ToolTable>,
|
||||
box_table: Option<BoxDropTable>,
|
||||
rng: Option<rand_chacha::ChaCha20Rng>,
|
||||
}
|
||||
|
||||
// TODO: add the rest of these later I just need these ones right now
|
||||
impl DropTableBuilder {
|
||||
#[must_use]
|
||||
pub fn monster_stats(mut self, monster_stats: HashMap<MonsterType, MonsterDropStats>) -> DropTableBuilder {
|
||||
self.monster_stats = Some(monster_stats);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn monster_stat(mut self, monster_type: MonsterType, drop_stats: MonsterDropStats) -> DropTableBuilder {
|
||||
match &mut self.monster_stats {
|
||||
Some(monster_stats) => {
|
||||
monster_stats.insert(monster_type, drop_stats);
|
||||
},
|
||||
None => {
|
||||
let mut monster_stats = HashMap::default();
|
||||
monster_stats.insert(monster_type, drop_stats);
|
||||
self.monster_stats = Some(monster_stats);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn rare_table(mut self, rare_table: RareDropTable) -> DropTableBuilder {
|
||||
self.rare_table = Some(rare_table);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
|
||||
Box::new(StandardDropTable {
|
||||
monster_stats: self.monster_stats.unwrap_or_else(|| {
|
||||
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
|
||||
monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect()
|
||||
}),
|
||||
rare_table: self.rare_table.unwrap_or_else(|| RareDropTable::new(episode, difficulty, section_id)),
|
||||
weapon_table: self.weapon_table.unwrap_or_else(|| GenericWeaponTable::new(episode, difficulty, section_id)),
|
||||
armor_table: self.armor_table.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)),
|
||||
shield_table: self.shield_table.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)),
|
||||
unit_table: self.unit_table.unwrap_or_else(|| GenericUnitTable::new(episode, difficulty, section_id)),
|
||||
tool_table: self.tool_table.unwrap_or_else(|| ToolTable::new(episode, difficulty, section_id)),
|
||||
box_table: self.box_table.unwrap_or_else(|| BoxDropTable::new(episode, difficulty, section_id)),
|
||||
rng: self.rng.unwrap_or_else(rand_chacha::ChaCha20Rng::from_entropy),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rand::seq::IteratorRandom;
|
||||
|
||||
#[test]
|
||||
fn test_initializing_drop_table() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
|
||||
let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap();
|
||||
let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate]
|
||||
.into_iter().choose(&mut rng).unwrap();
|
||||
let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
|
||||
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]
|
||||
.into_iter().choose(&mut rng).unwrap();
|
||||
DropTable::new(episode, difficulty, section_id);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "entity"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libpso = { workspace = true }
|
||||
maps = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
async-std = { workspace = true }
|
||||
sqlx = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
enum-utils = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
refinery = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
toml = { workspace = true }
|
@ -2,10 +2,10 @@ use std::convert::{From, Into};
|
||||
use std::collections::HashMap;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
#[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, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TechLevel(pub u8);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -167,14 +167,16 @@ pub struct CharacterTechniques {
|
||||
|
||||
impl CharacterTechniques {
|
||||
pub fn set_tech(&mut self, tech: Technique, level: TechLevel) {
|
||||
self.techs.insert(tech, TechLevel(level.0));
|
||||
self.techs.insert(tech, TechLevel(level.0 - 1));
|
||||
}
|
||||
|
||||
// from_bytes
|
||||
|
||||
pub fn as_bytes(&self) -> [u8; 20] {
|
||||
self.techs.iter()
|
||||
.fold([0xFF; 20], |mut techlist, (tech, level)| {
|
||||
let index = tech.as_value();
|
||||
techlist[index as usize] = level.0 - 1;
|
||||
techlist[index as usize] = level.0;
|
||||
techlist
|
||||
})
|
||||
}
|
||||
@ -223,7 +225,7 @@ impl CharacterInfoboard {
|
||||
}
|
||||
|
||||
pub fn update_infoboard(&mut self, new_board: &WriteInfoboard) {
|
||||
self.board = libpso::util::utf8_to_utf16_array(&new_board.message);
|
||||
self.board = libpso::utf8_to_utf16_array!(new_board.message, 172);
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,6 +264,82 @@ pub struct CharacterMaterials {
|
||||
pub tp: u32,
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
||||
@ -285,10 +363,12 @@ 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) -> NewCharacterEntity {
|
||||
pub fn new(user: UserAccountId, keyboard_config_preset: usize,) -> NewCharacterEntity {
|
||||
NewCharacterEntity {
|
||||
user_id: user,
|
||||
slot: 0,
|
||||
@ -304,6 +384,8 @@ impl NewCharacterEntity {
|
||||
materials: CharacterMaterials::default(),
|
||||
tech_menu: CharacterTechMenu::default(),
|
||||
option_flags: 0,
|
||||
keyboard_config: CharacterKeyboardConfig::new(keyboard_config_preset),
|
||||
gamepad_config: CharacterGamepadConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,6 +411,8 @@ pub struct CharacterEntity {
|
||||
|
||||
pub tech_menu: CharacterTechMenu,
|
||||
pub option_flags: u32,
|
||||
pub keyboard_config: CharacterKeyboardConfig,
|
||||
pub gamepad_config: CharacterGamepadConfig,
|
||||
|
||||
pub playtime: u32,
|
||||
}
|
@ -1,31 +1,32 @@
|
||||
use std::convert::From;
|
||||
use thiserror::Error;
|
||||
use futures::future::{Future, BoxFuture};
|
||||
use futures::Future;
|
||||
|
||||
use crate::account::*;
|
||||
use crate::character::*;
|
||||
use crate::item::*;
|
||||
use crate::room::*;
|
||||
use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::item::*;
|
||||
|
||||
|
||||
// TODO: better granularity?
|
||||
//#[derive(Error, Debug)]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("")]
|
||||
pub enum GatewayError {
|
||||
#[error("unknown error")]
|
||||
Error,
|
||||
#[error("postgres error {0}")]
|
||||
PgError(#[from] sqlx::Error)
|
||||
}
|
||||
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait EntityGateway: Send + Sync {
|
||||
type Transaction<'t>: EntityGatewayTransaction + Clone where Self: 't;
|
||||
type Transaction: EntityGatewayTransaction + Clone;
|
||||
|
||||
fn with_transaction<'a, F, Fut, R>(&'a mut self, _func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
|
||||
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result<R, E>
|
||||
where
|
||||
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
|
||||
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
|
||||
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
|
||||
F: FnOnce(Self::Transaction) -> Fut + Send,
|
||||
R: Send,
|
||||
E: From<GatewayError>,
|
||||
Self: Sized
|
||||
{
|
||||
unimplemented!();
|
||||
@ -96,7 +97,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!();
|
||||
}
|
||||
|
||||
@ -104,7 +105,7 @@ pub trait EntityGateway: Send + Sync {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@ -112,7 +113,7 @@ pub trait EntityGateway: Send + Sync {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _bank: &BankEntity, _bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||
async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _inventory: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@ -132,11 +133,11 @@ pub trait EntityGateway: Send + Sync {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
|
||||
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName) -> Result<Meseta, GatewayError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank_identifier: &BankIdentifier, _amount: Meseta) -> Result<(), GatewayError> {
|
||||
async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName, _amount: Meseta) -> Result<(), GatewayError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@ -147,14 +148,6 @@ 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!();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use futures::future::{Future, BoxFuture};
|
||||
use futures::Future;
|
||||
|
||||
use crate::account::*;
|
||||
use crate::character::*;
|
||||
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
||||
use crate::item::*;
|
||||
use crate::room::*;
|
||||
use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
||||
use crate::entity::item::*;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
|
||||
@ -32,7 +31,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<'t> = InMemoryGatewayTransaction where Self: 't;
|
||||
type Transaction = InMemoryGatewayTransaction;
|
||||
|
||||
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
||||
self.working_gateway.create_user(user).await
|
||||
@ -73,10 +72,6 @@ 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,
|
||||
@ -105,7 +100,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
|
||||
}
|
||||
|
||||
@ -118,11 +113,11 @@ impl EntityGateway for InMemoryGatewayTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
Ok(bank) => Ok(bank),
|
||||
Err(_) => {
|
||||
self.original_gateway.get_character_bank(char_id, bank_identifier).await
|
||||
self.original_gateway.get_character_bank(char_id, bank_name).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,10 +126,11 @@ 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_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||
self.working_gateway.set_character_bank(char_id, bank, bank_identifier).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 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),
|
||||
@ -161,15 +157,15 @@ impl EntityGateway for InMemoryGatewayTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
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 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 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 {
|
||||
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 {
|
||||
Ok(meseta) => Ok(meseta),
|
||||
Err(_) => {
|
||||
self.original_gateway.get_bank_meseta(char_id, bank_identifier).await
|
||||
self.original_gateway.get_bank_meseta(char_id, bank).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,11 +197,9 @@ 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.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.banks.lock().await.extend(self.working_gateway.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());
|
||||
@ -215,24 +209,16 @@ 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, Meseta>>>,
|
||||
shared_bank_meseta: Arc<Mutex<BTreeMap<(UserAccountId, BankName), Meseta>>>,
|
||||
bank_meseta: Arc<Mutex<BTreeMap<(CharacterEntityId, BankName), Meseta>>>,
|
||||
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
|
||||
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, Vec<InventoryItemElement>>>>,
|
||||
character_banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
|
||||
shared_banks: Arc<Mutex<BTreeMap<(UserAccountId, BankName), BankEntity>>>,
|
||||
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
|
||||
banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
|
||||
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
|
||||
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
|
||||
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
|
||||
@ -247,11 +233,9 @@ 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())),
|
||||
character_banks: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
shared_banks: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
banks: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
equips: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
@ -318,57 +302,52 @@ fn apply_modifiers(items: &BTreeMap<ItemEntityId, ItemEntity>,
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for InMemoryGateway {
|
||||
type Transaction<'t> = InMemoryGatewayTransaction where Self: 't;
|
||||
type Transaction = InMemoryGatewayTransaction;
|
||||
|
||||
fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
|
||||
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
|
||||
where
|
||||
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
|
||||
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
|
||||
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
|
||||
F: FnOnce(Self::Transaction) -> Fut + Send,
|
||||
R: Send,
|
||||
E: From<GatewayError>,
|
||||
{
|
||||
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 users = self.users.lock().await.clone();
|
||||
let user_settings = self.user_settings.lock().await.clone();
|
||||
let characters = self.characters.lock().await.clone();
|
||||
let character_meseta = self.character_meseta.lock().await.clone();
|
||||
let bank_meseta = self.bank_meseta.lock().await.clone();
|
||||
let items = self.items.lock().await.clone();
|
||||
let inventories = self.inventories.lock().await.clone();
|
||||
let banks = self.banks.lock().await.clone();
|
||||
let equips = self.equips.lock().await.clone();
|
||||
let mag_modifiers = self.mag_modifiers.lock().await.clone();
|
||||
let weapon_modifiers = self.weapon_modifiers.lock().await.clone();
|
||||
let trades = self.trades.lock().await.clone();
|
||||
|
||||
let working_gateway = InMemoryGateway {
|
||||
users: Arc::new(Mutex::new(users)),
|
||||
user_settings: Arc::new(Mutex::new(user_settings)),
|
||||
characters: Arc::new(Mutex::new(characters)),
|
||||
character_meseta: Arc::new(Mutex::new(character_meseta)),
|
||||
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
|
||||
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 working_gateway = InMemoryGateway {
|
||||
users: Arc::new(Mutex::new(users)),
|
||||
user_settings: Arc::new(Mutex::new(user_settings)),
|
||||
characters: Arc::new(Mutex::new(characters)),
|
||||
character_meseta: Arc::new(Mutex::new(character_meseta)),
|
||||
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
|
||||
items: Arc::new(Mutex::new(items)),
|
||||
inventories: Arc::new(Mutex::new(inventories)),
|
||||
banks: Arc::new(Mutex::new(banks)),
|
||||
equips: Arc::new(Mutex::new(equips)),
|
||||
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
|
||||
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
|
||||
trades: Arc::new(Mutex::new(trades)),
|
||||
};
|
||||
|
||||
let transaction = 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> {
|
||||
@ -440,12 +419,6 @@ 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;
|
||||
@ -480,6 +453,8 @@ 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());
|
||||
@ -540,11 +515,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.clone());
|
||||
.push(modifier);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -556,106 +531,30 @@ impl EntityGateway for InMemoryGateway {
|
||||
Ok(inventories
|
||||
.iter()
|
||||
.find(|(id, _)| **id == *char_id)
|
||||
.map(|(_, inv)| {
|
||||
InventoryEntity {
|
||||
items: inv
|
||||
.iter()
|
||||
.map(|inv_item_id| {
|
||||
match inv_item_id {
|
||||
InventoryItemElement::Individual(individual_id) => {
|
||||
InventoryItemEntity::Individual(items.get(individual_id).unwrap().clone())
|
||||
},
|
||||
InventoryItemElement::Stacked(stacked_ids) => {
|
||||
InventoryItemEntity::Stacked(
|
||||
stacked_ids.iter()
|
||||
.map(|stacked_id| {
|
||||
items.get(stacked_id).unwrap().clone()
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
.map(|(_, inv)| inv.clone())
|
||||
.map(|inv| apply_modifiers(&items, &weapon_modifiers, &mag_modifiers, inv))
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||
match bank_identifier {
|
||||
BankIdentifier::Character => {
|
||||
let character_banks = self.character_banks.lock().await;
|
||||
Ok(character_banks
|
||||
.iter()
|
||||
.find(|(id, _)| **id == *char_id)
|
||||
.map(|(_, b)| b.clone())
|
||||
.unwrap_or_default())
|
||||
},
|
||||
BankIdentifier::Shared(bank_name) => {
|
||||
let user_id = self.characters
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
.find(|(id, _)| **id == *char_id)
|
||||
.unwrap()
|
||||
.1
|
||||
.user_id;
|
||||
let shared_banks = self.shared_banks.lock().await;
|
||||
Ok(shared_banks
|
||||
.iter()
|
||||
.find(|((id, name), _)| *id == user_id && *name == *bank_name)
|
||||
.map(|(_, b)| b.clone())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
async fn 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 set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
|
||||
let mut inventories = self.inventories.lock().await;
|
||||
inventories.insert(*char_id, inventory.items
|
||||
.iter()
|
||||
.map(|inventory_item| {
|
||||
match inventory_item {
|
||||
InventoryItemEntity::Individual(individual) => {
|
||||
InventoryItemElement::Individual(individual.id)
|
||||
},
|
||||
InventoryItemEntity::Stacked(stacked) => {
|
||||
InventoryItemElement::Stacked(
|
||||
stacked.iter()
|
||||
.map(|stacked| {
|
||||
stacked.id
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect());
|
||||
inventories.insert(*char_id, inventory.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||
match bank_identifier {
|
||||
BankIdentifier::Character => {
|
||||
let mut character_banks = self.character_banks.lock().await;
|
||||
character_banks.insert(*char_id, bank.clone());
|
||||
},
|
||||
BankIdentifier::Shared(bank_name) => {
|
||||
let user_id = self.characters
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
.find(|(id, _)| **id == *char_id)
|
||||
.unwrap()
|
||||
.1
|
||||
.user_id;
|
||||
let mut shared_banks = self.shared_banks.lock().await;
|
||||
shared_banks.insert((user_id, bank_name.clone()), bank.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -690,58 +589,19 @@ impl EntityGateway for InMemoryGateway {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError> {
|
||||
match bank_identifier {
|
||||
BankIdentifier::Character => {
|
||||
let mut bank_meseta = self.bank_meseta.lock().await;
|
||||
if let Some(meseta) = bank_meseta.get_mut(char_id) {
|
||||
Ok(*meseta)
|
||||
}
|
||||
else {
|
||||
Err(GatewayError::Error)
|
||||
}
|
||||
},
|
||||
BankIdentifier::Shared(bank_name) => {
|
||||
let mut shared_bank_meseta = self.shared_bank_meseta.lock().await;
|
||||
let user_id = self.characters
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
.find(|(id, _)| **id == *char_id)
|
||||
.unwrap()
|
||||
.1
|
||||
.user_id;
|
||||
if let Some(meseta) = shared_bank_meseta.get_mut(&(user_id, bank_name.clone())) {
|
||||
Ok(*meseta)
|
||||
}
|
||||
else {
|
||||
Ok(Meseta(0))
|
||||
}
|
||||
}
|
||||
async fn 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -767,21 +627,4 @@ 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(())
|
||||
}
|
||||
}
|
15
src/entity/gateway/postgres/migrations/V0004__meseta.sql
Normal file
15
src/entity/gateway/postgres/migrations/V0004__meseta.sql
Normal file
@ -0,0 +1,15 @@
|
||||
create table character_meseta (
|
||||
pchar integer references character (id) not null unique,
|
||||
meseta integer not null,
|
||||
);
|
||||
|
||||
create table bank_meseta (
|
||||
pchar integer references character (id) not null,
|
||||
bank varchar(128) not null,
|
||||
meseta integer not null,
|
||||
unique (pchar, bank)
|
||||
);
|
||||
|
||||
|
||||
alter table player_character
|
||||
drop column meseta, bank_meseta;
|
5
src/entity/gateway/postgres/migrations/V0005__trade.sql
Normal file
5
src/entity/gateway/postgres/migrations/V0005__trade.sql
Normal file
@ -0,0 +1,5 @@
|
||||
create table trades (
|
||||
id serial primary key not null,
|
||||
character1 integer references character (id) not null,
|
||||
character2 integer references character (id) not null,
|
||||
);
|
3
src/entity/gateway/postgres/migrations/mod.rs
Normal file
3
src/entity/gateway/postgres/migrations/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use refinery::include_migration_mods;
|
||||
|
||||
include_migration_mods!("src/entity/gateway/postgres/migrations");
|
@ -4,13 +4,10 @@ use std::convert::Into;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use libpso::character::settings;
|
||||
use libpso::util::vec_to_array;
|
||||
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;
|
||||
use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::item::*;
|
||||
use crate::ship::map::MapArea;
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgUserAccount {
|
||||
@ -52,8 +49,8 @@ pub struct PgUserSettings {
|
||||
id: i32,
|
||||
user_account: i32,
|
||||
blocked_users: Vec<u8>, //[u32; 0x1E],
|
||||
key_config: Vec<u8>, //[u8; 0x16C],
|
||||
joystick_config: Vec<u8>, //[u8; 0x38],
|
||||
keyboard_config: Vec<u8>, //[u8; 0x16C],
|
||||
gamepad_config: Vec<u8>, //[u8; 0x38],
|
||||
option_flags: i32,
|
||||
shortcuts: Vec<u8>, //[u8; 0xA40],
|
||||
symbol_chats: Vec<u8>, //[u8; 0x4E0],
|
||||
@ -67,8 +64,8 @@ impl From<PgUserSettings> for UserSettingsEntity {
|
||||
user_id: UserAccountId(other.user_account as u32),
|
||||
settings: settings::UserSettings {
|
||||
blocked_users: vec_to_array(other.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
|
||||
keyboard_config: vec_to_array(other.key_config),
|
||||
gamepad_config: vec_to_array(other.joystick_config),
|
||||
keyboard_config: vec_to_array(other.keyboard_config),
|
||||
gamepad_config: vec_to_array(other.gamepad_config),
|
||||
option_flags: other.option_flags as u32,
|
||||
shortcuts: vec_to_array(other.shortcuts),
|
||||
symbol_chats: vec_to_array(other.symbol_chats),
|
||||
@ -220,6 +217,8 @@ pub struct PgCharacter {
|
||||
tp: i16,
|
||||
|
||||
tech_menu: Vec<u8>,
|
||||
keyboard_config: Vec<u8>,
|
||||
gamepad_config: Vec<u8>,
|
||||
|
||||
playtime: i32,
|
||||
}
|
||||
@ -247,13 +246,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 + 1)) ).collect()
|
||||
techs: other.techs.iter().enumerate().take(19).filter(|(_, t)| **t != 0xFF).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
|
||||
},
|
||||
config: CharacterConfig {
|
||||
raw_data: vec_to_array(other.config)
|
||||
},
|
||||
info_board: CharacterInfoboard {
|
||||
board: libpso::util::utf8_to_utf16_array(other.infoboard),
|
||||
board: libpso::utf8_to_utf16_array!(other.infoboard, 172),
|
||||
},
|
||||
guildcard: CharacterGuildCard {
|
||||
description: other.guildcard,
|
||||
@ -271,6 +270,12 @@ 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,
|
||||
}
|
||||
}
|
||||
@ -580,16 +585,6 @@ 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,
|
||||
@ -605,19 +600,14 @@ pub enum PgItemNoteDetail {
|
||||
y: f32,
|
||||
z: f32,
|
||||
},
|
||||
Consumed {
|
||||
character_id: u32,
|
||||
},
|
||||
Consumed,
|
||||
FedToMag {
|
||||
character_id: u32,
|
||||
mag: u32,
|
||||
},
|
||||
BoughtAtShop {
|
||||
character_id: u32,
|
||||
},
|
||||
SoldToShop {
|
||||
character_id: u32,
|
||||
},
|
||||
SoldToShop,
|
||||
Trade {
|
||||
trade_id: u32,
|
||||
character_to: u32,
|
||||
@ -625,14 +615,11 @@ pub enum PgItemNoteDetail {
|
||||
},
|
||||
Withdraw {
|
||||
character_id: u32,
|
||||
bank: BankIdentifier,
|
||||
bank: String,
|
||||
},
|
||||
Deposit {
|
||||
character_id: u32,
|
||||
bank: BankIdentifier,
|
||||
},
|
||||
FloorLimitReached {
|
||||
map_area: MapArea,
|
||||
bank: String,
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,16 +629,8 @@ impl From<ItemNote> for PgItemNoteDetail {
|
||||
ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation {
|
||||
character_id: character_id.0,
|
||||
},
|
||||
ItemNote::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
|
||||
ItemNote::EnemyDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
|
||||
character_id: character_id.0,
|
||||
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,
|
||||
},
|
||||
@ -663,19 +642,14 @@ impl From<ItemNote> for PgItemNoteDetail {
|
||||
map_area,
|
||||
x,y,z,
|
||||
},
|
||||
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed {
|
||||
character_id: character_id.0,
|
||||
},
|
||||
ItemNote::FedToMag{character_id, mag} => PgItemNoteDetail::FedToMag{
|
||||
character_id: character_id.0,
|
||||
ItemNote::Consumed => PgItemNoteDetail::Consumed,
|
||||
ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
|
||||
mag: mag.0
|
||||
},
|
||||
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
|
||||
character_id: character_id.0,
|
||||
},
|
||||
ItemNote::SoldToShop{character_id} => PgItemNoteDetail::SoldToShop {
|
||||
character_id: character_id.0,
|
||||
},
|
||||
ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop,
|
||||
ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade {
|
||||
trade_id: trade_id.0,
|
||||
character_to: character_to.0,
|
||||
@ -684,20 +658,15 @@ impl From<ItemNote> for PgItemNoteDetail {
|
||||
ItemNote::Withdraw{character_id, bank} => {
|
||||
PgItemNoteDetail::Withdraw {
|
||||
character_id: character_id.0,
|
||||
bank,
|
||||
bank: bank.0,
|
||||
}
|
||||
},
|
||||
ItemNote::Deposit{character_id, bank} => {
|
||||
PgItemNoteDetail::Deposit {
|
||||
character_id: character_id.0,
|
||||
bank,
|
||||
bank: bank.0,
|
||||
}
|
||||
},
|
||||
ItemNote::FloorLimitReached { map_area } => {
|
||||
PgItemNoteDetail::FloorLimitReached {
|
||||
map_area,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -708,16 +677,8 @@ impl From<PgItemNoteDetail> for ItemNote {
|
||||
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
},
|
||||
PgItemNoteDetail::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => ItemNote::EnemyDrop {
|
||||
PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
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,
|
||||
},
|
||||
@ -729,19 +690,14 @@ impl From<PgItemNoteDetail> for ItemNote {
|
||||
map_area,
|
||||
x,y,z,
|
||||
},
|
||||
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
},
|
||||
PgItemNoteDetail::FedToMag{character_id, mag} => ItemNote::FedToMag{
|
||||
character_id: CharacterEntityId(character_id),
|
||||
PgItemNoteDetail::Consumed => ItemNote::Consumed,
|
||||
PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
|
||||
mag: ItemEntityId(mag)
|
||||
},
|
||||
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
},
|
||||
PgItemNoteDetail::SoldToShop{character_id} => ItemNote::SoldToShop {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
},
|
||||
PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop,
|
||||
PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade {
|
||||
trade_id: TradeId(trade_id),
|
||||
character_to: CharacterEntityId(character_to),
|
||||
@ -749,14 +705,11 @@ impl From<PgItemNoteDetail> for ItemNote {
|
||||
},
|
||||
PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
bank,
|
||||
bank: BankName(bank),
|
||||
},
|
||||
PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit {
|
||||
character_id: CharacterEntityId(character_id),
|
||||
bank,
|
||||
},
|
||||
PgItemNoteDetail::FloorLimitReached { map_area } => ItemNote::FloorLimitReached {
|
||||
map_area,
|
||||
bank: BankName(bank),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -924,27 +877,3 @@ impl From<PgTradeEntity> for TradeEntity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, sqlx::FromRow, Serialize)]
|
||||
pub struct PgRoomEntity {
|
||||
id: i32,
|
||||
name: String,
|
||||
section_id: i8,
|
||||
mode: i8,
|
||||
episode: i8,
|
||||
difficulty: i8,
|
||||
}
|
||||
|
||||
impl From<PgRoomEntity> for RoomEntity {
|
||||
fn from(other: PgRoomEntity) -> RoomEntity {
|
||||
RoomEntity {
|
||||
id: RoomEntityId(other.id as u32),
|
||||
name: other.name,
|
||||
section_id: SectionID::from(other.section_id as u8),
|
||||
mode: RoomEntityMode::from(other.mode as u8),
|
||||
episode: Episode::try_from(other.episode as u8).unwrap(),
|
||||
difficulty: Difficulty::try_from(other.difficulty as u8).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
// 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, Into};
|
||||
use futures::future::{Future, BoxFuture};
|
||||
use futures::stream::{StreamExt, FuturesOrdered};
|
||||
use std::convert::{From, TryFrom, Into};
|
||||
use futures::{Future, TryStreamExt};
|
||||
use async_std::stream::StreamExt;
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use libpso::character::guildcard;
|
||||
use crate::account::*;
|
||||
use crate::character::*;
|
||||
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
||||
use crate::item::*;
|
||||
use crate::room::*;
|
||||
use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
||||
use crate::entity::item::*;
|
||||
use super::models::*;
|
||||
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
@ -19,7 +18,7 @@ use sqlx::Connection;
|
||||
|
||||
mod embedded {
|
||||
use refinery::embed_migrations;
|
||||
embed_migrations!("src/gateway/postgres/migrations");
|
||||
embed_migrations!("src/entity/gateway/postgres/migrations");
|
||||
}
|
||||
|
||||
|
||||
@ -37,7 +36,9 @@ 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()
|
||||
@ -49,12 +50,13 @@ impl<'t> EntityGatewayTransaction for PostgresTransaction<'t> {
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PostgresGateway {
|
||||
pub struct PostgresGateway<'t> {
|
||||
pool: sqlx::Pool<sqlx::Postgres>,
|
||||
_t: std::marker::PhantomData<&'t ()>,
|
||||
}
|
||||
|
||||
impl PostgresGateway {
|
||||
pub fn new(host: &str, dbname: &str, username: &str, password: &str) -> PostgresGateway {
|
||||
impl<'t> PostgresGateway<'t> {
|
||||
pub fn new(host: &str, dbname: &str, username: &str, password: &str) -> PostgresGateway<'t> {
|
||||
let mut conn = refinery::config::Config::new(refinery::config::ConfigDbType::Postgres)
|
||||
.set_db_host(host)
|
||||
.set_db_user(username)
|
||||
@ -65,11 +67,12 @@ impl PostgresGateway {
|
||||
let pool = async_std::task::block_on(async move {
|
||||
PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&format!("postgresql://{username}:{password}@{host}:5432/{dbname}")).await.unwrap()
|
||||
.connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap()
|
||||
});
|
||||
|
||||
PostgresGateway {
|
||||
pool,
|
||||
_t: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,7 +93,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
|
||||
.bind(id.0 as i32)
|
||||
.fetch(conn);
|
||||
|
||||
weapon_modifiers.for_each(|modifier| async move {
|
||||
weapon_modifiers.for_each(|modifier| {
|
||||
if let Ok(modifier) = modifier {
|
||||
weapon.apply_modifier(&modifier.modifier);
|
||||
}
|
||||
@ -98,7 +101,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
|
||||
|
||||
ItemDetail::Weapon(weapon)
|
||||
},
|
||||
ItemDetail::Mag(mag) => {
|
||||
ItemDetail::Mag(mut mag) => {
|
||||
let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell
|
||||
from mag_modifier
|
||||
left join item on item.id = cast (modifier ->> 'FeedMag' as integer)
|
||||
@ -108,7 +111,7 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
|
||||
.bind(id.0 as i32)
|
||||
.fetch(conn);
|
||||
|
||||
let mag = mag_modifiers.fold(mag, |mut mag, modifier| async {
|
||||
mag_modifiers.for_each(|modifier| {
|
||||
let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap();
|
||||
let modifier: mag::MagModifier = modifier.0.into();
|
||||
match modifier {
|
||||
@ -125,8 +128,6 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
|
||||
mag.change_owner(class, section_id)
|
||||
},
|
||||
}
|
||||
|
||||
mag
|
||||
}).await;
|
||||
|
||||
ItemDetail::Mag(mag)
|
||||
@ -140,31 +141,6 @@ async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntit
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_item<T>(conn: &mut sqlx::PgConnection, item: PgInventoryItemEntity, individual: fn(ItemEntity) -> T, stacked: fn(Vec<ItemEntity>) -> T) -> Result<T, GatewayError>
|
||||
{
|
||||
match item {
|
||||
PgInventoryItemEntity::Individual(item) => {
|
||||
let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
|
||||
.bind(item)
|
||||
.fetch_one(&mut *conn).await
|
||||
.map(|item| item.into())
|
||||
.map(|item| apply_item_modifications(&mut *conn, item))?
|
||||
.await;
|
||||
Ok(individual(entity))
|
||||
},
|
||||
PgInventoryItemEntity::Stacked(items) => {
|
||||
let mut stacked_item = Vec::new();
|
||||
for s_item in items {
|
||||
stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1")
|
||||
.bind(s_item)
|
||||
.fetch_one(&mut *conn).await
|
||||
.map(|item| item.into())?)
|
||||
}
|
||||
Ok(stacked(stacked_item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_user(conn: &mut sqlx::PgConnection, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError>
|
||||
{
|
||||
let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password, activated) values ($1, $2, $3, $4) returning *;")
|
||||
@ -179,7 +155,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 as i32)
|
||||
.bind(id.0)
|
||||
.fetch_one(conn).await?;
|
||||
Ok(user.into())
|
||||
}
|
||||
@ -200,8 +176,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 as i32)
|
||||
.bind(user.id.0 as i32)
|
||||
.bind(user.flags)
|
||||
.bind(user.id.0)
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
}
|
||||
@ -210,7 +186,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 as i32)
|
||||
.bind(settings.user_id.0)
|
||||
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
|
||||
.bind(settings.settings.keyboard_config.to_vec())
|
||||
.bind(settings.settings.gamepad_config.to_vec())
|
||||
@ -225,7 +201,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 as i32)
|
||||
.bind(user.id.0)
|
||||
.fetch_one(conn).await?;
|
||||
Ok(settings.into())
|
||||
}
|
||||
@ -236,34 +212,25 @@ async fn save_user_settings(conn: &mut sqlx::PgConnection, settings: &UserSettin
|
||||
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
|
||||
.bind(&settings.settings.keyboard_config.to_vec())
|
||||
.bind(&settings.settings.gamepad_config.to_vec())
|
||||
.bind(settings.settings.option_flags as i32)
|
||||
.bind(settings.settings.option_flags)
|
||||
.bind(&settings.settings.shortcuts.to_vec())
|
||||
.bind(&settings.settings.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 as i32)
|
||||
.execute(conn).await?;
|
||||
.bind(settings.id.0)
|
||||
.fetch_one(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, playtime)
|
||||
(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)
|
||||
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)
|
||||
($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)
|
||||
returning *;"#;
|
||||
let character = sqlx::query_as::<_, PgCharacter>(q)
|
||||
.bind(char.user_id.0 as i32)
|
||||
.bind(char.user_id.0)
|
||||
.bind(char.slot as i16)
|
||||
.bind(char.name)
|
||||
.bind(char.exp as i32)
|
||||
@ -292,7 +259,6 @@ 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())
|
||||
@ -300,17 +266,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 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)
|
||||
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)
|
||||
.fetch(conn);
|
||||
const NONE: Option<CharacterEntity> = None;
|
||||
let mut result = [NONE; 4];
|
||||
while let Some(character) = stream.try_next().await? {
|
||||
let index = character.slot as usize;
|
||||
result[index] = Some(character.into())
|
||||
}
|
||||
|
||||
Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| async move {
|
||||
if let Ok(char) = char {
|
||||
let slot = char.slot as usize;
|
||||
acc[slot] = Some(char.into())
|
||||
}
|
||||
acc
|
||||
}).await)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -> Result<(), GatewayError>
|
||||
@ -319,9 +285,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 as i32) // $1
|
||||
.bind(char.user_id.0) // $1
|
||||
.bind(char.slot as i16) // $2
|
||||
.bind(&char.name) // $3
|
||||
.bind(char.exp as i32) // $4
|
||||
@ -350,7 +316,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) // $30
|
||||
.bind(char.playtime as i32) // $20
|
||||
.bind(char.id.0 as i32) // $31
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
@ -371,7 +337,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 as i32)
|
||||
.bind(item_id.0)
|
||||
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
@ -380,7 +346,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 as i32)
|
||||
.bind(mag_item_id.0)
|
||||
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id})))
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
@ -389,7 +355,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 as i32)
|
||||
.bind(mag_item_id.0)
|
||||
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id))))
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
@ -398,16 +364,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 as i32)
|
||||
.bind(mag_item_id.0)
|
||||
.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 as i32)
|
||||
.bind(item_id.0)
|
||||
.bind(sqlx::types::Json(modifier))
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
@ -415,65 +381,76 @@ async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntity
|
||||
|
||||
async fn get_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError>
|
||||
{
|
||||
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit
|
||||
let mut t = conn.begin().await?;
|
||||
let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1")
|
||||
.bind(char_id.0 as i32)
|
||||
.fetch_one(&mut **conn.lock().await).await?;
|
||||
.bind(char_id.0)
|
||||
.fetch_one(&mut t).await?;
|
||||
|
||||
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
|
||||
// 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())?)
|
||||
}
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?))
|
||||
real_inventory.push(InventoryItemEntity::Stacked(stacked_item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InventoryEntity::new(real_inventory))
|
||||
}
|
||||
|
||||
async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError>
|
||||
async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_name: &BankName) -> Result<BankEntity, GatewayError>
|
||||
{
|
||||
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit
|
||||
let 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(
|
||||
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
|
||||
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)
|
||||
}
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?))
|
||||
real_bank.push(BankItemEntity::Stacked(stacked_item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BankEntity::new(real_bank))
|
||||
}
|
||||
|
||||
async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError>
|
||||
@ -492,15 +469,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 as i32)
|
||||
.bind(char_id.0)
|
||||
.bind(sqlx::types::Json(inventory))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankEntity, bank_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||
async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError>
|
||||
{
|
||||
let bank = bank.items.iter()
|
||||
.map(|item| {
|
||||
match item {
|
||||
@ -514,33 +491,19 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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?;
|
||||
}
|
||||
}
|
||||
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?;
|
||||
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 as i32)
|
||||
.bind(char_id.0)
|
||||
.fetch_one(conn)
|
||||
.await?;
|
||||
|
||||
@ -551,7 +514,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 as i32)
|
||||
.bind(char_id.0)
|
||||
.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))
|
||||
@ -565,11 +528,10 @@ 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 meseta = $2")
|
||||
.bind(char_id.0 as i32)
|
||||
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2")
|
||||
.bind(char_id.0)
|
||||
.bind(meseta.0 as i32)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
@ -580,70 +542,41 @@ 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 pchar = $1"#)
|
||||
.bind(char_id.0 as i32)
|
||||
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#)
|
||||
.bind(char_id.0)
|
||||
.fetch_one(conn)
|
||||
.await?;
|
||||
Ok(Meseta(meseta.0 as u32))
|
||||
}
|
||||
|
||||
async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError>
|
||||
async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError>
|
||||
{
|
||||
match bank_identifier {
|
||||
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?;
|
||||
}
|
||||
}
|
||||
|
||||
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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<Meseta, GatewayError>
|
||||
async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError>
|
||||
{
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct PgMeseta(i32);
|
||||
|
||||
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))
|
||||
}
|
||||
};
|
||||
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?;
|
||||
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 as i32)
|
||||
.bind(char_id2.0 as i32)
|
||||
.bind(char_id1.0)
|
||||
.bind(char_id2.0)
|
||||
.fetch_one(conn)
|
||||
.await?;
|
||||
Ok(trade.into())
|
||||
@ -651,54 +584,31 @@ 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(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)
|
||||
sqlx::query_as::<_, PgTradeEntity>(r#"update player_character set playtime=$2 where id=$1;"#)
|
||||
.bind(char_id.0)
|
||||
.bind(playtime)
|
||||
.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 EntityGateway for PostgresGateway {
|
||||
type Transaction<'t> = PostgresTransaction<'t> where Self: 't;
|
||||
impl<'t> EntityGateway for PostgresGateway<'t> {
|
||||
type Transaction = PostgresTransaction<'t>;
|
||||
|
||||
fn with_transaction<'a, F, Fut, R>(&'a mut self, func: F) -> BoxFuture<'a, Result<R, anyhow::Error>>
|
||||
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
|
||||
where
|
||||
Fut: Future<Output = Result<(Self::Transaction<'a>, R), anyhow::Error>> + Send + 'a,
|
||||
F: FnOnce(Self::Transaction<'a>) -> Fut + Send + 'a,
|
||||
Fut: Future<Output = Result<(Self::Transaction, R), E>> + Send + 'a,
|
||||
F: FnOnce(Self::Transaction) -> Fut + Send,
|
||||
R: Send,
|
||||
E: From<GatewayError>,
|
||||
{
|
||||
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)
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
||||
@ -769,7 +679,7 @@ impl EntityGateway for PostgresGateway {
|
||||
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
|
||||
}
|
||||
|
||||
@ -777,16 +687,16 @@ impl EntityGateway for PostgresGateway {
|
||||
get_character_inventory(&mut *self.pool.acquire().await?, char_id).await
|
||||
}
|
||||
|
||||
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||
get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_identifier).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 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_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||
set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_identifier).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 get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
|
||||
@ -805,12 +715,12 @@ impl EntityGateway for PostgresGateway {
|
||||
get_character_meseta(&mut *self.pool.acquire().await?, char_id).await
|
||||
}
|
||||
|
||||
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
|
||||
set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank_identifier, meseta).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 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 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 create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
|
||||
@ -820,20 +730,12 @@ impl EntityGateway for PostgresGateway {
|
||||
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<'t> = PostgresTransaction<'c> where Self: 't;
|
||||
type Transaction = PostgresTransaction<'c>;
|
||||
|
||||
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
||||
create_user(&mut *self.pgtransaction.lock().await, user).await
|
||||
@ -903,7 +805,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
|
||||
}
|
||||
|
||||
@ -911,16 +813,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_identifier: &BankIdentifier) -> Result<BankEntity, GatewayError> {
|
||||
get_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank_identifier).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 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_identifier: &BankIdentifier) -> Result<(), GatewayError> {
|
||||
set_character_bank(&mut *self.pgtransaction.lock().await, char_id, bank, bank_identifier).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 get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
|
||||
@ -939,12 +841,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_identifier: &BankIdentifier, meseta: Meseta) -> Result<(), GatewayError> {
|
||||
set_bank_meseta(&mut *self.pgtransaction.lock().await, char_id, bank_identifier, meseta).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 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 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 create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
|
||||
@ -954,13 +856,5 @@ 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
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::item::ItemEntityId;
|
||||
use crate::entity::item::ItemEntityId;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ItemParseError {
|
@ -1,9 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use std::collections::HashMap;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::item::tool::ToolType;
|
||||
use crate::character::{CharacterClass, SectionID};
|
||||
use crate::item::ItemEntityId;
|
||||
use crate::entity::item::tool::ToolType;
|
||||
use crate::entity::character::{CharacterClass, SectionID};
|
||||
use crate::entity::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
|
@ -9,11 +9,9 @@ pub mod mag;
|
||||
pub mod esweapon;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::character::CharacterEntityId;
|
||||
use crate::room::RoomEntityId;
|
||||
use maps::area::MapArea;
|
||||
use maps::monster::MonsterType;
|
||||
//use crate::ship::drops::ItemDropType;
|
||||
use crate::entity::character::CharacterEntityId;
|
||||
use crate::ship::map::MapArea;
|
||||
use crate::ship::drops::ItemDropType;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct ItemEntityId(pub u32);
|
||||
@ -24,12 +22,6 @@ pub struct BankName(pub String);
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
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 {
|
||||
@ -37,16 +29,8 @@ pub enum ItemNote {
|
||||
},
|
||||
EnemyDrop {
|
||||
character_id: CharacterEntityId,
|
||||
room_id: RoomEntityId,
|
||||
monster_type: MonsterType,
|
||||
map_area: MapArea,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
},
|
||||
BoxDrop {
|
||||
character_id: CharacterEntityId,
|
||||
room_id: RoomEntityId,
|
||||
//monster_type: MonsterType,
|
||||
//droprate: f32,
|
||||
map_area: MapArea,
|
||||
x: f32,
|
||||
y: f32,
|
||||
@ -62,19 +46,15 @@ pub enum ItemNote {
|
||||
y: f32,
|
||||
z: f32,
|
||||
},
|
||||
Consumed {
|
||||
character_id: CharacterEntityId,
|
||||
},
|
||||
Consumed, // TODO: character_id
|
||||
FedToMag {
|
||||
character_id: CharacterEntityId,
|
||||
//character_id: CharacterEntityId,
|
||||
mag: ItemEntityId,
|
||||
},
|
||||
BoughtAtShop {
|
||||
character_id: CharacterEntityId,
|
||||
},
|
||||
SoldToShop {
|
||||
character_id: CharacterEntityId,
|
||||
},
|
||||
SoldToShop,
|
||||
Trade {
|
||||
trade_id: TradeId,
|
||||
character_to: CharacterEntityId,
|
||||
@ -82,14 +62,11 @@ pub enum ItemNote {
|
||||
},
|
||||
Withdraw {
|
||||
character_id: CharacterEntityId,
|
||||
bank: BankIdentifier,
|
||||
bank: BankName,
|
||||
},
|
||||
Deposit {
|
||||
character_id: CharacterEntityId,
|
||||
bank: BankIdentifier,
|
||||
},
|
||||
FloorLimitReached {
|
||||
map_area: MapArea,
|
||||
bank: BankName,
|
||||
},
|
||||
}
|
||||
|
||||
@ -156,6 +133,26 @@ impl ItemDetail {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
|
||||
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
|
||||
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
|
||||
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
|
||||
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
|
||||
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
|
||||
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
|
||||
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
|
||||
|
||||
match item_type {
|
||||
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
|
||||
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
|
||||
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
|
||||
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
|
||||
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
|
||||
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_client_bytes(&self) -> [u8; 16] {
|
||||
match self {
|
||||
ItemDetail::Weapon(w) => w.as_bytes(),
|
@ -1,4 +1,4 @@
|
||||
use crate::item::ItemEntityId;
|
||||
use crate::entity::item::ItemEntityId;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@ -1479,52 +1479,44 @@ impl Weapon {
|
||||
}
|
||||
|
||||
pub fn apply_modifier(&mut self, modifier: &WeaponModifier) {
|
||||
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
|
||||
if let WeaponModifier::Tekked{special, percent, grind} = modifier {
|
||||
match special {
|
||||
TekSpecialModifier::Plus => {
|
||||
self.special = self.special.map(|special| {
|
||||
special.rank_up()
|
||||
});
|
||||
}
|
||||
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
|
||||
},
|
||||
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
|
||||
});
|
||||
}
|
||||
self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8;
|
||||
self.tekked = true;
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,3 @@ pub mod gateway;
|
||||
pub mod account;
|
||||
pub mod character;
|
||||
pub mod item;
|
||||
pub mod room;
|
@ -1,16 +0,0 @@
|
||||
create table character_meseta (
|
||||
pchar integer references player_character (id) not null unique,
|
||||
meseta integer not null
|
||||
);
|
||||
|
||||
create table bank_meseta (
|
||||
pchar integer references player_character (id) not null,
|
||||
bank varchar(128) not null,
|
||||
meseta integer not null,
|
||||
unique (pchar, bank)
|
||||
);
|
||||
|
||||
|
||||
alter table player_character
|
||||
drop column meseta,
|
||||
drop column bank_meseta;
|
@ -1,5 +0,0 @@
|
||||
create table trades (
|
||||
id serial primary key not null,
|
||||
character1 integer references player_character (id) not null,
|
||||
character2 integer references player_character (id) not null
|
||||
);
|
@ -1,3 +0,0 @@
|
||||
alter table player_character
|
||||
add keyboard_config bytea not null,
|
||||
add gamepad_config bytea not null;
|
@ -1,5 +0,0 @@
|
||||
alter table player_character
|
||||
drop column playtime;
|
||||
|
||||
alter table player_character
|
||||
add playtime integer not null;
|
@ -1,3 +0,0 @@
|
||||
alter table player_character
|
||||
drop column keyboard_config,
|
||||
drop column gamepad_config;
|
@ -1,2 +0,0 @@
|
||||
alter table player_character
|
||||
add created_at timestamptz default current_timestamp not null;
|
@ -1,16 +0,0 @@
|
||||
|
||||
create table shared_bank (
|
||||
user_account integer references user_accounts (id) not null,
|
||||
items jsonb not null,
|
||||
name varchar(128) not null,
|
||||
unique (user_account, name)
|
||||
);
|
||||
|
||||
|
||||
create table shared_bank_meseta (
|
||||
user_account integer references user_accounts (id) not null,
|
||||
name varchar(128) not null,
|
||||
meseta integer not null,
|
||||
unique (user_account, name)
|
||||
);
|
||||
|
@ -1,14 +0,0 @@
|
||||
create table room (
|
||||
id serial primary key not null,
|
||||
name varchar(32) not null,
|
||||
section_id char not null,
|
||||
mode char not null,
|
||||
episode char not null,
|
||||
difficulty char not null
|
||||
);
|
||||
|
||||
create table room_note (
|
||||
room integer references room (id) not null,
|
||||
note jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
@ -1,17 +0,0 @@
|
||||
drop table room_note;
|
||||
drop table room;
|
||||
|
||||
create table room (
|
||||
id serial primary key not null,
|
||||
name varchar(32) not null,
|
||||
section_id "char" not null,
|
||||
mode "char" not null,
|
||||
episode "char" not null,
|
||||
difficulty "char" not null
|
||||
);
|
||||
|
||||
create table room_note (
|
||||
room integer references room (id) not null,
|
||||
note jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
@ -1,3 +0,0 @@
|
||||
use refinery::include_migration_mods;
|
||||
|
||||
include_migration_mods!("src/gateway/postgres/migrations");
|
@ -1,83 +0,0 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
|
||||
use crate::character::{CharacterEntityId, SectionID};
|
||||
use maps::room::{Episode, Difficulty};
|
||||
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct RoomEntityId(pub u32);
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum RoomEntityMode {
|
||||
Multi,
|
||||
Single,
|
||||
Challenge,
|
||||
Battle,
|
||||
}
|
||||
|
||||
impl From<u8> for RoomEntityMode {
|
||||
fn from(other: u8) -> RoomEntityMode {
|
||||
match other {
|
||||
0 => RoomEntityMode::Multi,
|
||||
1 => RoomEntityMode::Single,
|
||||
2 => RoomEntityMode::Challenge,
|
||||
3 => RoomEntityMode::Battle,
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomEntityMode> for u8 {
|
||||
fn from(other: RoomEntityMode) -> u8 {
|
||||
match other {
|
||||
RoomEntityMode::Multi => 0,
|
||||
RoomEntityMode::Single => 1,
|
||||
RoomEntityMode::Challenge => 2,
|
||||
RoomEntityMode::Battle => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoomEntity {
|
||||
pub id: RoomEntityId,
|
||||
pub name: String,
|
||||
pub section_id: SectionID,
|
||||
pub mode: RoomEntityMode,
|
||||
pub episode: Episode,
|
||||
pub difficulty: Difficulty,
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NewRoomEntity {
|
||||
pub name: String,
|
||||
pub section_id: SectionID,
|
||||
pub mode: RoomEntityMode,
|
||||
pub episode: Episode,
|
||||
pub difficulty: Difficulty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
pub enum RoomNote {
|
||||
Create {
|
||||
character_id: CharacterEntityId,
|
||||
},
|
||||
PlayerJoin {
|
||||
character_id: CharacterEntityId,
|
||||
},
|
||||
PlayerLeave {
|
||||
character_id: CharacterEntityId,
|
||||
},
|
||||
QuestStart {
|
||||
// quest id
|
||||
},
|
||||
QuestComplete {
|
||||
// quest id
|
||||
},
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::account::UserAccountId;
|
||||
|
||||
// [2022-10-23 00:11:18][elseware::common::mainloop::client][WARN] error RecvServerPacket::from_bytes: WrongPacketForServerType(490, [40, 0, 234, 1, 0, 0, 0, 0, 9, 0, 74, 0, 97, 0, 115, 0, 100, 0, 102, 0, 0, 0, 0, 0, 192, 52, 67, 3, 60, 159, 129, 0, 32, 64, 233, 10, 196, 156, 152, 0])
|
||||
// [2022-10-23 00:20:14][elseware::common::mainloop::client][WARN] error RecvServerPacket::from_bytes: WrongPacketForServerType(490, [40, 0, 234, 1, 0, 0, 0, 0, 9, 0, 74, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 97, 0, 0, 0, 152, 0])
|
||||
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct TeamEntityId(pub u32);
|
||||
|
||||
pub struct NewTeamEntity {
|
||||
pub created_by: UserAccountId,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TeamEntity {
|
||||
pub id: TeamEntityId,
|
||||
pub owner: UserAccountId,
|
||||
pub name: String,
|
||||
|
||||
pub team_flag: [u8; 2048],
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "items"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
entity = { workspace = true }
|
||||
maps = { workspace = true }
|
||||
shops = { workspace = true }
|
||||
location = { workspace = true }
|
||||
drops = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
enum-utils = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
async-recursion = { workspace = true }
|
||||
async-std = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
@ -1,38 +0,0 @@
|
||||
use crate::ClientItemId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TradeItem {
|
||||
Individual(ClientItemId),
|
||||
Stacked(ClientItemId, usize),
|
||||
}
|
||||
|
||||
impl TradeItem {
|
||||
pub fn stacked(&self) -> Option<(ClientItemId, usize)> {
|
||||
match self {
|
||||
TradeItem::Stacked(item_id, amount) => Some((*item_id, *amount)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stacked_mut(&mut self) -> Option<(ClientItemId, &mut usize)> {
|
||||
match self {
|
||||
TradeItem::Stacked(item_id, ref mut amount) => Some((*item_id, amount)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_id(&self) -> ClientItemId {
|
||||
match self {
|
||||
TradeItem::Individual(item_id) => *item_id,
|
||||
TradeItem::Stacked(item_id, _) => *item_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(&self) -> usize {
|
||||
match self {
|
||||
TradeItem::Individual(_) => 1,
|
||||
TradeItem::Stacked(_, amount) => *amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
src/lib.rs
Normal file
15
src/lib.rs
Normal file
@ -0,0 +1,15 @@
|
||||
#![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;
|
@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "location"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
networking = { workspace = true }
|
||||
|
||||
async-std = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
futures= { workspace = true }
|
||||
thiserror = { workspace = true }
|
@ -12,45 +12,39 @@ 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 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::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 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::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 crate::login::get_login_status;
|
||||
use networking::interserver::AuthToken;
|
||||
|
||||
use pktbuilder::ship::SHIP_MENU_ID;
|
||||
use crate::login::login::{get_login_status};
|
||||
use crate::common::interserver::AuthToken;
|
||||
|
||||
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),
|
||||
#[error("could not load settings {0}")]
|
||||
CouldNotLoadSettings(GatewayError),
|
||||
#[error("could not load characters")]
|
||||
CouldNotLoadSettings,
|
||||
CouldNotLoadCharacters,
|
||||
#[error("could not load guildcard")]
|
||||
CouldNotLoadGuildcard,
|
||||
#[error("gateway error {0}")]
|
||||
GatewayError(#[from] GatewayError),
|
||||
}
|
||||
|
||||
@ -150,7 +144,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()),
|
||||
filename: utf8_to_array!(param.file_name().unwrap().to_str().unwrap(), 0x40),
|
||||
});
|
||||
|
||||
buffer.append(&mut filebuf);
|
||||
@ -177,7 +171,7 @@ impl ClientState {
|
||||
user: None,
|
||||
characters: None,
|
||||
guildcard_data_buffer: None,
|
||||
session: Session::default(),
|
||||
session: Session::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,7 +206,6 @@ 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,
|
||||
@ -267,8 +260,6 @@ 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(
|
||||
@ -306,7 +297,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(), &BankIdentifier::Character).await?;
|
||||
entity_gateway.set_character_bank(&character.id, &BankEntity::default(), &BankName("".into())).await?;
|
||||
let equipped = EquippedEntity {
|
||||
weapon: Some(weapon.id),
|
||||
armor: Some(armor.id),
|
||||
@ -342,15 +333,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::default()))]);
|
||||
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]);
|
||||
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::default());
|
||||
let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new());
|
||||
response.guildcard = user.guildcard;
|
||||
response.team_id = user.team_id.map_or(0, |ti| ti);
|
||||
|
||||
@ -367,7 +358,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
|
||||
Ok(vec![SendCharacterPacket::LoginResponse(response)])
|
||||
},
|
||||
Err(err) => {
|
||||
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::default()))])
|
||||
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::new()))])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -379,7 +370,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)
|
||||
name: utf8_to_utf16_array!(s.name, 0x11)
|
||||
}
|
||||
}).collect()))
|
||||
])
|
||||
@ -394,7 +385,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)?
|
||||
}
|
||||
};
|
||||
|
||||
@ -485,7 +476,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 user = client.user.as_mut().unwrap();
|
||||
let mut user = client.user.as_mut().unwrap();
|
||||
user.flags = setflag.flags;
|
||||
self.entity_gateway.save_user(user).await.unwrap();
|
||||
Ok(None.into_iter())
|
||||
@ -516,7 +507,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 user = client.user.as_mut().unwrap();
|
||||
let mut user = client.user.as_mut().unwrap();
|
||||
if user.flags == USERFLAG_NEWCHAR {
|
||||
new_character(&mut self.entity_gateway, user, preview).await?
|
||||
}
|
||||
@ -750,7 +741,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);
|
||||
let mut character = NewCharacterEntity::new(user.id, 1); // it should not be possible for the client to specify the kbm config preset from the char create screen
|
||||
character.slot = preview.slot;
|
||||
character.name = String::from_utf16_lossy(&preview.character.name).trim_matches(char::from(0)).into();
|
||||
character.section_id = preview.character.section_id.into();
|
||||
@ -824,7 +815,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),
|
||||
name: utf8_to_utf16_array!(character.name, 16),
|
||||
play_time: character.playtime,
|
||||
..character::SelectScreenCharacter::default()
|
||||
}
|
||||
@ -835,21 +826,9 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use entity::account::*;
|
||||
use crate::entity::account::*;
|
||||
use libpso::character::{settings, character};
|
||||
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;
|
||||
}
|
||||
|
||||
use crate::entity::gateway::{InMemoryGateway, GatewayError};
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_option_send() {
|
||||
@ -859,7 +838,7 @@ mod test {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for TestData {
|
||||
type Transaction<'a> = CharTestDb where Self: 'a;
|
||||
type Transaction = ();
|
||||
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
|
||||
Ok(UserSettingsEntity {
|
||||
id: UserSettingsId(0),
|
||||
@ -902,7 +881,7 @@ mod test {
|
||||
#[derive(Clone)]
|
||||
struct TestData;
|
||||
impl EntityGateway for TestData {
|
||||
type Transaction<'a> = CharTestDb where Self: 'a;
|
||||
type Transaction = ();
|
||||
}
|
||||
let mut server = CharacterServerState::new(TestData {}, AuthToken("".into()));
|
||||
let send = server.handle(ClientId(1), RecvCharacterPacket::Checksum(Checksum {checksum: 1234,
|
@ -11,18 +11,18 @@ use libpso::{PacketParseError, PSOPacket};
|
||||
use libpso::crypto::bb::PSOBBCipher;
|
||||
use libpso::util::array_to_utf8;
|
||||
|
||||
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
|
||||
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
|
||||
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
|
||||
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
|
||||
|
||||
use entity::gateway::EntityGateway;
|
||||
use entity::account::{UserAccountEntity};
|
||||
use crate::entity::gateway::EntityGateway;
|
||||
use crate::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,13 +83,21 @@ 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>, // TODO: this should be arc/mutex'd?
|
||||
clients: HashMap<ClientId, String>,
|
||||
}
|
||||
|
||||
impl<EG: EntityGateway + Clone> LoginServerState<EG> {
|
||||
@ -111,7 +119,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::character::CHARACTER_PORT))])
|
||||
SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::login::character::CHARACTER_PORT))])
|
||||
},
|
||||
Err(err) => {
|
||||
Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))])
|
||||
@ -170,8 +178,8 @@ impl<EG: EntityGateway + Clone> ServerState for LoginServerState<EG> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use entity::account::{UserAccountId};
|
||||
use entity::gateway::{EntityGatewayTransaction, GatewayError};
|
||||
use crate::entity::account::{UserAccountId};
|
||||
use crate::entity::gateway::{EntityGatewayTransaction, GatewayError};
|
||||
|
||||
const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login {
|
||||
tag: 65536,
|
||||
@ -196,16 +204,13 @@ mod test {
|
||||
character_slot: 0,
|
||||
}
|
||||
});
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LoginTestDb;
|
||||
|
||||
impl EntityGateway for LoginTestDb {
|
||||
type Transaction<'t> = LoginTestDb where Self: 't;
|
||||
|
||||
impl EntityGateway for () {
|
||||
type Transaction = ();
|
||||
}
|
||||
|
||||
impl EntityGatewayTransaction for LoginTestDb {
|
||||
type ParentGateway = LoginTestDb;
|
||||
impl EntityGatewayTransaction for () {
|
||||
type ParentGateway = ();
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@ -216,7 +221,7 @@ mod test {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for TestData {
|
||||
type Transaction<'t> = LoginTestDb where Self: 't;
|
||||
type Transaction = ();
|
||||
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
|
||||
assert!(name == "testuser");
|
||||
Ok(UserAccountEntity {
|
||||
@ -275,7 +280,7 @@ mod test {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for TestData {
|
||||
type Transaction<'t> = LoginTestDb where Self: 't;
|
||||
type Transaction = ();
|
||||
async fn get_user_by_name(&mut self, _name: String) -> Result<UserAccountEntity, GatewayError> {
|
||||
Err(GatewayError::Error)
|
||||
}
|
||||
@ -310,7 +315,7 @@ mod test {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for TestData {
|
||||
type Transaction<'t> = LoginTestDb where Self: 't;
|
||||
type Transaction = ();
|
||||
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
|
||||
assert!(name == "testuser");
|
||||
Ok(UserAccountEntity {
|
||||
@ -360,7 +365,7 @@ mod test {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for TestData {
|
||||
type Transaction<'t> = LoginTestDb where Self: 't;
|
||||
type Transaction = ();
|
||||
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
|
||||
assert!(name == "testuser");
|
||||
Ok(UserAccountEntity {
|
3
src/login/mod.rs
Normal file
3
src/login/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod login;
|
||||
pub mod character;
|
92
src/login/models.rs
Normal file
92
src/login/models.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::time::SystemTime;
|
||||
use std::io::Write;
|
||||
//use diesel::sql_types::Timestamp;
|
||||
use diesel::{Insertable, Queryable, Identifiable, Associations, AsExpression, FromSqlRow};
|
||||
//use bcrypt::{DEFAULT_COST, hash};
|
||||
use diesel::pg::Pg;
|
||||
use diesel::sql_types;
|
||||
use diesel::deserialize::{self, FromSql};
|
||||
use diesel::serialize::{self, ToSql, Output, IsNull};
|
||||
use diesel::backend::Backend;
|
||||
|
||||
use libpso::character::settings;
|
||||
|
||||
use elseware::schema::*;
|
||||
|
||||
//const ELSEWHERE_COST: u32 = bcrypt::DEFAULT_COST;
|
||||
const ELSEWHERE_COST: u32 = 5;
|
||||
|
||||
#[derive(Debug, AsExpression, FromSqlRow)]
|
||||
#[sql_type="sql_types::Binary"]
|
||||
pub struct EUserSettings(pub settings::UserSettings);
|
||||
|
||||
impl std::ops::Deref for EUserSettings {
|
||||
type Target = settings::UserSettings;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Queryable, Identifiable, Debug)]
|
||||
pub struct UserAccount {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub guildcard: Option<i32>,
|
||||
pub team_id: Option<i32>,
|
||||
pub banned: bool,
|
||||
pub muted_until: SystemTime,
|
||||
pub created_at: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[table_name="user_accounts"]
|
||||
pub struct NewUser {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl NewUser {
|
||||
pub fn new(username: String, password: String) -> NewUser {
|
||||
let crypt_password = bcrypt::hash(password, ELSEWHERE_COST).expect("could not hash password?");
|
||||
NewUser {
|
||||
username: username,
|
||||
password: crypt_password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Queryable, Identifiable, Associations)]
|
||||
#[belongs_to(UserAccount, foreign_key="user_id")]
|
||||
#[table_name="user_settings"]
|
||||
pub struct UserSettings {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
//settings: Vec<u8>,
|
||||
pub settings: EUserSettings,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Debug)]
|
||||
#[table_name="user_settings"]
|
||||
pub struct NewUserSettings {
|
||||
pub user_id: i32,
|
||||
pub settings: EUserSettings,
|
||||
}
|
||||
|
||||
impl ToSql<sql_types::Binary, Pg> for EUserSettings {
|
||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
||||
out.write_all(&self.0.as_bytes()[..])
|
||||
.map(|_| IsNull::No)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql<sql_types::Binary, Pg> for EUserSettings {
|
||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
||||
let bytes_vec: Vec<u8> = <Vec<u8> as FromSql<sql_types::Binary, Pg>>::from_sql(bytes)?;
|
||||
let mut static_bytes = [0u8; 0x1160];
|
||||
static_bytes[..0x1160].clone_from_slice(&bytes_vec);
|
||||
Ok(EUserSettings(settings::UserSettings::from_bytes(static_bytes)))
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "login_server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
entity = { workspace = true }
|
||||
networking = { workspace = true }
|
||||
pktbuilder = { workspace = true }
|
||||
stats = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
async-std = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
bcrypt = { workspace = true }
|
||||
crc = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
rand= { workspace = true }
|
@ -1,2 +0,0 @@
|
||||
pub mod login;
|
||||
pub mod character;
|
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "maps"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
byteorder = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
enum-utils = { workspace = true }
|
||||
derive_more = { workspace = true }
|
@ -1,59 +0,0 @@
|
||||
pub mod area;
|
||||
pub mod enemy;
|
||||
pub mod object;
|
||||
pub mod variant;
|
||||
pub mod maps;
|
||||
pub mod monster;
|
||||
pub mod room;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Holiday {
|
||||
None,
|
||||
Christmas,
|
||||
Valentines,
|
||||
Easter,
|
||||
Halloween,
|
||||
Sonic,
|
||||
NewYear,
|
||||
Summer,
|
||||
White,
|
||||
Wedding,
|
||||
Fall,
|
||||
Spring,
|
||||
Summer2,
|
||||
Spring2,
|
||||
}
|
||||
|
||||
|
||||
impl From<Holiday> for u32 {
|
||||
fn from(other: Holiday) -> u32 {
|
||||
u16::from(other) as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Holiday> for u16 {
|
||||
fn from(other: Holiday) -> u16 {
|
||||
u8::from(other) as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Holiday> for u8 {
|
||||
fn from(other: Holiday) -> u8 {
|
||||
match other {
|
||||
Holiday::None => 0,
|
||||
Holiday::Christmas => 1,
|
||||
Holiday::Valentines => 3,
|
||||
Holiday::Easter => 4,
|
||||
Holiday::Halloween => 5,
|
||||
Holiday::Sonic => 6,
|
||||
Holiday::NewYear => 7,
|
||||
Holiday::Summer => 8,
|
||||
Holiday::White => 9,
|
||||
Holiday::Wedding => 10,
|
||||
Holiday::Fall => 11,
|
||||
Holiday::Spring => 12,
|
||||
Holiday::Summer2 => 13,
|
||||
Holiday::Spring2 => 14,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
#[derive(Debug, Copy, Clone, derive_more::Display)]
|
||||
pub enum Episode {
|
||||
#[display(fmt="ep1")]
|
||||
One,
|
||||
#[display(fmt="ep2")]
|
||||
Two,
|
||||
#[display(fmt="ep4")]
|
||||
Four,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Episode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Episode, ()> {
|
||||
match value {
|
||||
1 => Ok(Episode::One),
|
||||
2 => Ok(Episode::Two),
|
||||
3 => Ok(Episode::Four),
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Episode> for u8 {
|
||||
fn from(other: Episode) -> u8 {
|
||||
match other {
|
||||
Episode::One => 1,
|
||||
Episode::Two => 2,
|
||||
Episode::Four => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Episode {
|
||||
pub fn from_quest(value: u8) -> Option<Episode> {
|
||||
match value {
|
||||
0 => Some(Episode::One),
|
||||
1 => Some(Episode::Two),
|
||||
2 => Some(Episode::Four),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||
pub enum Difficulty {
|
||||
Normal,
|
||||
Hard,
|
||||
VeryHard,
|
||||
Ultimate,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Difficulty {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Difficulty, ()> {
|
||||
match value {
|
||||
0 => Ok(Difficulty::Normal),
|
||||
1 => Ok(Difficulty::Hard),
|
||||
2 => Ok(Difficulty::VeryHard),
|
||||
3 => Ok(Difficulty::Ultimate),
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Difficulty> for u8 {
|
||||
fn from(other: Difficulty) -> u8 {
|
||||
match other {
|
||||
Difficulty::Normal => 0,
|
||||
Difficulty::Hard => 1,
|
||||
Difficulty::VeryHard => 2,
|
||||
Difficulty::Ultimate => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PlayerMode {
|
||||
Single,
|
||||
Multi,
|
||||
}
|
||||
|
||||
impl PlayerMode {
|
||||
pub fn value(&self) -> u8 {
|
||||
match self {
|
||||
PlayerMode::Single => 1,
|
||||
PlayerMode::Multi => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, derive_more::Display)]
|
||||
pub enum RoomMode {
|
||||
#[display(fmt="single")]
|
||||
Single {
|
||||
episode: Episode,
|
||||
difficulty: Difficulty,
|
||||
},
|
||||
#[display(fmt="multi")]
|
||||
Multi {
|
||||
episode: Episode,
|
||||
difficulty: Difficulty,
|
||||
},
|
||||
#[display(fmt="challenge")]
|
||||
Challenge {
|
||||
episode: Episode,
|
||||
},
|
||||
#[display(fmt="battle")]
|
||||
Battle {
|
||||
episode: Episode,
|
||||
difficulty: Difficulty,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl RoomMode {
|
||||
pub fn difficulty(&self) -> Difficulty {
|
||||
match self {
|
||||
RoomMode::Single {difficulty, ..} => *difficulty,
|
||||
RoomMode::Multi {difficulty, ..} => *difficulty,
|
||||
RoomMode::Battle {difficulty, ..} => *difficulty,
|
||||
RoomMode::Challenge {..} => Difficulty::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn episode(&self) -> Episode {
|
||||
match self {
|
||||
RoomMode::Single {episode, ..} => *episode,
|
||||
RoomMode::Multi {episode, ..} => *episode,
|
||||
RoomMode::Battle {episode, ..} => *episode,
|
||||
RoomMode::Challenge {episode, ..} => *episode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn battle(&self) -> bool {
|
||||
matches!(self, RoomMode::Battle {..})
|
||||
}
|
||||
|
||||
pub fn challenge(&self) -> bool {
|
||||
matches!(self, RoomMode::Challenge {..})
|
||||
}
|
||||
|
||||
pub fn player_mode(&self) -> PlayerMode {
|
||||
match self {
|
||||
RoomMode::Single {..} => PlayerMode::Single,
|
||||
_ => PlayerMode::Multi,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "networking"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
entity = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
async-std = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
derive_more = { workspace = true }
|
@ -1,4 +0,0 @@
|
||||
pub mod cipherkeys;
|
||||
pub mod serverstate;
|
||||
pub mod mainloop;
|
||||
pub mod interserver;
|
2
src/patch/mod.rs
Normal file
2
src/patch/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod patch;
|
@ -11,8 +11,8 @@ use libpso::crypto::pc::PSOPCCipher;
|
||||
use ron::de::from_str;
|
||||
use serde::Deserialize;
|
||||
|
||||
use networking::mainloop::{NetworkError};
|
||||
use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
|
||||
use crate::common::mainloop::{NetworkError};
|
||||
use crate::common::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::default()))
|
||||
Some(SendPatchPacket::EndFileSend(EndFileSend::new()))
|
||||
}
|
||||
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
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "patch_server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
networking = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
async-trait = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
crc = { workspace = true }
|
||||
ron = { workspace = true }
|
||||
serde = { workspace = true }
|
@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "pktbuilder"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
quests = { workspace = true }
|
||||
stats = { workspace = true }
|
||||
location = { workspace = true }
|
||||
client = { workspace = true }
|
||||
items = { workspace = true }
|
||||
networking = { workspace = true }
|
||||
maps = { workspace = true }
|
||||
room = { workspace = true }
|
||||
shops = { workspace = true }
|
||||
entity = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
thiserror = { workspace = true }
|
@ -1,107 +0,0 @@
|
||||
use futures::stream::{FuturesOrdered, StreamExt};
|
||||
use libpso::packet::ship::*;
|
||||
use crate::common::serverstate::ClientId;
|
||||
use crate::entity::gateway::EntityGateway;
|
||||
use crate::ship::client::{Clients, ClientState};
|
||||
use crate::ship::teams::Teams;
|
||||
use crate::ship::location::ClientLocation;
|
||||
use crate::ship::ship::ShipError;
|
||||
use crate::entity::team::TeamEntity;
|
||||
|
||||
|
||||
pub fn client_team_state_changed(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> ClientTeamStateChanged {
|
||||
ClientTeamStateChanged {
|
||||
unknown: 0,
|
||||
guildcard: client.user.guildcard(),
|
||||
team_id: team.id.0,
|
||||
unknown2: [0;2],
|
||||
privilege: 0x40, // TODO: improve
|
||||
team_name: libpso::utf8_to_utf16_array!(team.name, 14),
|
||||
unknown3: 0x00986C84, // TODO: what if we omit this?
|
||||
}
|
||||
}
|
||||
|
||||
fn player_team_info(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> PlayerTeamInfo {
|
||||
PlayerTeamInfo {
|
||||
guildcard: client.user.guildcard(),
|
||||
team_id: team.id.0,
|
||||
info: 0,
|
||||
info2: 0,
|
||||
privilege: 0x40, // TODO: improve
|
||||
team_name: libpso::utf8_to_utf16_array!(team.name, 14),
|
||||
unknown: 0x00986C84, // TODO: what if we omit this?
|
||||
guildcard_again: client.user.guildcard(),
|
||||
client_id: client_id.0 as u32,
|
||||
character_name: libpso::utf8_to_utf16_array!(client.character.name, 12),
|
||||
unknown2: 0,
|
||||
unknown3: 0,
|
||||
team_flag: team.team_flag,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn team_info_individual(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> TeamInfo {
|
||||
TeamInfo {
|
||||
clients: vec![player_team_info(client_id, client, team)]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn player_team_info_list<EG>(id: ClientId,
|
||||
client_location: &ClientLocation,
|
||||
clients: &Clients,
|
||||
teams: &Teams<EG>,
|
||||
) -> Result<Vec<PlayerTeamInfo>, ShipError>
|
||||
where
|
||||
EG: EntityGateway + Clone + 'static,
|
||||
{
|
||||
Ok(futures::stream::iter(client_location.get_all_clients_by_client(id).await?.into_iter())
|
||||
.filter_map(|area_client| {
|
||||
let clients = clients.clone();
|
||||
async move {
|
||||
clients.with(area_client.client, |client| {
|
||||
let mut teams = teams.clone();
|
||||
Box::pin(async move {
|
||||
let team = teams.get_team(area_client.client).await.ok()??;
|
||||
Some(player_team_info(area_client.client, client, &team))
|
||||
})}).await.ok()?
|
||||
}})
|
||||
.collect::<Vec<_>>()
|
||||
.await)
|
||||
}
|
||||
|
||||
pub async fn team_info<EG>(id: ClientId,
|
||||
client_location: &ClientLocation,
|
||||
clients: &Clients,
|
||||
teams: &Teams<EG>,
|
||||
) -> Result<TeamInfo, ShipError>
|
||||
where
|
||||
EG: EntityGateway + Clone + 'static,
|
||||
{
|
||||
Ok(TeamInfo {
|
||||
clients: player_team_info_list(id, client_location, clients, teams).await?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn lobby_team_list<EG>(id: ClientId,
|
||||
client_location: &ClientLocation,
|
||||
clients: &Clients,
|
||||
teams: &Teams<EG>,
|
||||
) -> Result<TeamLobbyList, ShipError>
|
||||
where
|
||||
EG: EntityGateway + Clone + 'static,
|
||||
{
|
||||
Ok(TeamLobbyList {
|
||||
clients: player_team_info_list(id, client_location, clients, teams).await?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn team_invitation_info(client_id: ClientId, client: &ClientState, team: &TeamEntity) -> TeamInvitationInfo {
|
||||
TeamInvitationInfo {
|
||||
guildcard: client.user.guildcard(),
|
||||
team_id: team.id.0,
|
||||
unknown: [0; 2],
|
||||
team_name: libpso::utf8_to_utf16_array!(team.name, 14),
|
||||
unknown2: 0x00986C84, // TODO: what if we omit this?
|
||||
team_flag: team.team_flag,
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "quests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
maps = { workspace = true }
|
||||
|
||||
libpso = { workspace = true }
|
||||
|
||||
async-std = { workspace = true }
|
||||
ages-prs = { workspace = true }
|
||||
log = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
serde = { workspace = true }
|
@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "room"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
maps = { workspace = true }
|
||||
entity = { workspace = true }
|
||||
quests = { workspace = true }
|
||||
location = { workspace = true }
|
||||
drops = { workspace = true }
|
||||
|
||||
rand = { workspace = true }
|
||||
async-std = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
@ -1,274 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{From, Into};
|
||||
use async_std::sync::{Arc, RwLock, RwLockReadGuard};
|
||||
use futures::future::BoxFuture;
|
||||
use futures::stream::{FuturesOrdered, Stream};
|
||||
|
||||
use thiserror::Error;
|
||||
use rand::Rng;
|
||||
|
||||
use quests::{QuestList, QuestLoadError};
|
||||
use maps::maps::Maps;
|
||||
use drops::DropTable;
|
||||
use entity::character::SectionID;
|
||||
use entity::room::{RoomEntityId, RoomEntityMode};
|
||||
use maps::monster::{load_monster_stats_table, MonsterType, MonsterStats};
|
||||
use maps::area::MapAreaLookup;
|
||||
use maps::Holiday;
|
||||
use location::{MAX_ROOMS, RoomId};
|
||||
|
||||
use maps::room::{Episode, Difficulty, RoomMode};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RoomError {
|
||||
#[error("invalid room id {0}")]
|
||||
Invalid(u32),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Rooms([Arc<RwLock<Option<RoomState>>>; MAX_ROOMS]);
|
||||
|
||||
|
||||
impl Default for Rooms {
|
||||
fn default() -> Rooms {
|
||||
Rooms(core::array::from_fn(|_| Arc::new(RwLock::new(None))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rooms {
|
||||
pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> {
|
||||
*self.0
|
||||
.get(room_id.0)
|
||||
.ok_or(RoomError::Invalid(room_id.0 as u32))?
|
||||
.write()
|
||||
.await = Some(room);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove(&self, room_id: RoomId) {
|
||||
if let Some(room) = self.0.get(room_id.0) {
|
||||
*room
|
||||
.write()
|
||||
.await = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exists(&self, room_id: RoomId) -> bool {
|
||||
match self.0.get(room_id.0) {
|
||||
Some(room) => {
|
||||
room
|
||||
.read()
|
||||
.await
|
||||
.is_some()
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
|
||||
where
|
||||
T: Send,
|
||||
F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a
|
||||
{
|
||||
let room = self.0
|
||||
.get(room_id.0)
|
||||
.ok_or(RoomError::Invalid(room_id.0 as u32))?
|
||||
.read()
|
||||
.await;
|
||||
if let Some(room) = room.as_ref() {
|
||||
Ok(func(room).await)
|
||||
}
|
||||
else {
|
||||
Err(RoomError::Invalid(room_id.0 as u32).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
|
||||
where
|
||||
T: Send,
|
||||
F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a
|
||||
{
|
||||
let mut room = self.0
|
||||
.get(room_id.0)
|
||||
.ok_or(RoomError::Invalid(room_id.0 as u32))?
|
||||
.write()
|
||||
.await;
|
||||
|
||||
if let Some(room) = room.as_mut() {
|
||||
Ok(func(room).await)
|
||||
}
|
||||
else {
|
||||
Err(RoomError::Invalid(room_id.0 as u32).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard<Option<RoomState>> {
|
||||
self.0
|
||||
.get(room_id.0)
|
||||
.unwrap()
|
||||
.read()
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn stream(&self) -> impl Stream<Item = RwLockReadGuard<Option<RoomState>>> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|room| async move {
|
||||
room
|
||||
.read()
|
||||
.await
|
||||
})
|
||||
.collect::<FuturesOrdered<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("")]
|
||||
pub enum RoomCreationError {
|
||||
InvalidMode,
|
||||
InvalidEpisode(u8),
|
||||
InvalidDifficulty(u8),
|
||||
CouldNotLoadMonsterStats(RoomMode),
|
||||
CouldNotLoadQuests,
|
||||
}
|
||||
|
||||
|
||||
pub enum QuestCategoryType {
|
||||
Standard,
|
||||
Government,
|
||||
}
|
||||
|
||||
impl From<usize> for QuestCategoryType {
|
||||
fn from(f: usize) -> QuestCategoryType {
|
||||
match f {
|
||||
0 => QuestCategoryType::Standard,
|
||||
_ => QuestCategoryType::Government,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<u32> for QuestCategoryType {
|
||||
fn from(f: u32) -> QuestCategoryType {
|
||||
match f {
|
||||
0 => QuestCategoryType::Standard,
|
||||
_ => QuestCategoryType::Government,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QuestCategoryType {
|
||||
pub fn value(&self) -> usize {
|
||||
match self {
|
||||
QuestCategoryType::Standard => 0,
|
||||
QuestCategoryType::Government => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RoomState {
|
||||
pub room_id: RoomEntityId,
|
||||
pub mode: RoomMode,
|
||||
pub name: String,
|
||||
pub password: [u16; 16],
|
||||
pub maps: Maps,
|
||||
pub drop_table: Box<dyn DropTable + Send + Sync>,
|
||||
pub section_id: SectionID,
|
||||
pub random_seed: u32,
|
||||
pub bursting: bool,
|
||||
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
|
||||
pub map_areas: MapAreaLookup,
|
||||
pub quest_group: QuestCategoryType,
|
||||
pub standard_quests: QuestList,
|
||||
pub government_quests: QuestList,
|
||||
// enemy info
|
||||
}
|
||||
|
||||
impl RoomState {
|
||||
pub fn get_flags_for_room_list(&self) -> u8 {
|
||||
let mut flags = 0u8;
|
||||
|
||||
match self.mode {
|
||||
RoomMode::Single {..} => {flags += 0x04}
|
||||
RoomMode::Battle {..} => {flags += 0x10},
|
||||
RoomMode::Challenge {..} => {flags += 0x20},
|
||||
_ => {flags += 0x40},
|
||||
};
|
||||
|
||||
if self.password[0] > 0 {
|
||||
flags += 0x02;
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
pub fn get_episode_for_room_list(&self) -> u8 {
|
||||
let episode: u8 = self.mode.episode().into();
|
||||
|
||||
match self.mode {
|
||||
RoomMode::Single {..} => episode + 0x10,
|
||||
_ => episode + 0x40,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_difficulty_for_room_list(&self) -> u8 {
|
||||
let difficulty: u8 = self.mode.difficulty().into();
|
||||
difficulty + 0x22
|
||||
}
|
||||
|
||||
pub fn quests(&self) -> &QuestList {
|
||||
match self.quest_group {
|
||||
QuestCategoryType::Standard => &self.standard_quests,
|
||||
QuestCategoryType::Government => &self.government_quests,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
pub fn new (room_id: RoomEntityId,
|
||||
mode: RoomEntityMode,
|
||||
episode: Episode,
|
||||
difficulty: Difficulty,
|
||||
section_id: SectionID,
|
||||
name: String,
|
||||
password: [u16; 16],
|
||||
event: Holiday,
|
||||
map_builder: Arc<Box<dyn Fn(RoomMode, Holiday) -> Maps + Send + Sync>>,
|
||||
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> Box<dyn DropTable + Send + Sync> + Send + Sync>>,
|
||||
standard_quest_builder: Arc<Box<dyn Fn(RoomMode) -> Result<QuestList, QuestLoadError> + Send + Sync>>,
|
||||
government_quest_builder: Arc<Box<dyn Fn(RoomMode) -> Result<QuestList, QuestLoadError> + Send + Sync>>,
|
||||
) -> Result<RoomState, anyhow::Error> {
|
||||
let mode = match mode {
|
||||
RoomEntityMode::Single => RoomMode::Single {
|
||||
episode,
|
||||
difficulty,
|
||||
},
|
||||
RoomEntityMode::Multi => RoomMode::Multi {
|
||||
episode,
|
||||
difficulty,
|
||||
},
|
||||
RoomEntityMode::Challenge => RoomMode::Challenge {
|
||||
episode,
|
||||
},
|
||||
RoomEntityMode::Battle => RoomMode::Battle {
|
||||
episode,
|
||||
difficulty,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(RoomState {
|
||||
room_id,
|
||||
monster_stats: Box::new(load_monster_stats_table(&mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(mode))?),
|
||||
mode,
|
||||
random_seed: rand::thread_rng().gen(),
|
||||
name,
|
||||
password,
|
||||
maps: map_builder(mode, event),
|
||||
section_id,
|
||||
drop_table: drop_table_builder(episode, difficulty, section_id),
|
||||
bursting: false,
|
||||
map_areas: MapAreaLookup::new(&episode),
|
||||
quest_group: QuestCategoryType::Standard,
|
||||
standard_quests: standard_quest_builder(mode)?,
|
||||
government_quests: government_quest_builder(mode)?,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
use libpso::character::character;
|
||||
use stats::leveltable::CharacterStats;
|
||||
use entity::character::CharacterEntity;
|
||||
use items::bank::BankState;
|
||||
use items::inventory::InventoryState;
|
||||
use entity::item::Meseta;
|
||||
use crate::common::leveltable::CharacterStats;
|
||||
use crate::entity::character::CharacterEntity;
|
||||
//use crate::ship::items::{CharacterInventory, CharacterBank};
|
||||
use crate::ship::items::bank::BankState;
|
||||
use crate::ship::items::inventory::InventoryState;
|
||||
use crate::entity::item::Meseta;
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
@ -53,7 +54,7 @@ impl<'a> CharacterBytesBuilder<'a> {
|
||||
let level = self.level.unwrap();
|
||||
let meseta = self.meseta.unwrap();
|
||||
character::Character {
|
||||
name: libpso::util::utf8_to_utf16_array(&character.name),
|
||||
name: libpso::utf8_to_utf16_array!(character.name, 16),
|
||||
hp: stats.hp,
|
||||
atp: stats.atp + character.materials.power as u16 * 2,
|
||||
mst: stats.mst + character.materials.mind as u16 * 2,
|
@ -6,20 +6,15 @@ use futures::future::BoxFuture;
|
||||
use libpso::packet::ship::*;
|
||||
use libpso::packet::login::Session;
|
||||
|
||||
use networking::serverstate::ClientId;
|
||||
use entity::account::{UserAccountEntity, UserSettingsEntity};
|
||||
use entity::character::CharacterEntity;
|
||||
use entity::item;
|
||||
use crate::common::serverstate::ClientId;
|
||||
use crate::entity::account::{UserAccountEntity, UserSettingsEntity};
|
||||
use crate::entity::character::CharacterEntity;
|
||||
use crate::entity::item;
|
||||
|
||||
use maps::area::MapArea;
|
||||
use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
|
||||
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("not found {0}")]
|
||||
NotFound(ClientId),
|
||||
}
|
||||
use crate::ship::ship::ShipError;
|
||||
use crate::ship::items;
|
||||
use crate::ship::map::MapArea;
|
||||
use crate::ship::shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
|
||||
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@ -41,7 +36,7 @@ impl Clients {
|
||||
.into_inner())
|
||||
}
|
||||
|
||||
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
|
||||
pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, ShipError>
|
||||
where
|
||||
T: Send,
|
||||
F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a,
|
||||
@ -51,14 +46,14 @@ impl Clients {
|
||||
.await;
|
||||
let client = clients
|
||||
.get(&client_id)
|
||||
.ok_or(ClientError::NotFound(client_id))?
|
||||
.ok_or_else(|| ShipError::ClientNotFound(client_id))?
|
||||
.read()
|
||||
.await;
|
||||
|
||||
Ok(func(&client).await)
|
||||
}
|
||||
|
||||
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, anyhow::Error>
|
||||
pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result<T, ShipError>
|
||||
where
|
||||
T: Send,
|
||||
F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a,
|
||||
@ -74,20 +69,23 @@ impl Clients {
|
||||
for (cindex, client_id) in client_ids.iter().enumerate() {
|
||||
let c = clients
|
||||
.get(client_id)
|
||||
.ok_or(ClientError::NotFound(*client_id))?
|
||||
.ok_or_else(|| ShipError::ClientNotFound(*client_id))?
|
||||
.read()
|
||||
.await;
|
||||
client_states[cindex].write(c);
|
||||
}
|
||||
|
||||
let client_states = unsafe {
|
||||
std::mem::transmute_copy(&client_states)
|
||||
// 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)
|
||||
};
|
||||
|
||||
Ok(func(client_states).await)
|
||||
}
|
||||
|
||||
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, anyhow::Error>
|
||||
pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result<T, ShipError>
|
||||
where
|
||||
T: Send,
|
||||
F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a,
|
||||
@ -97,7 +95,7 @@ impl Clients {
|
||||
.await;
|
||||
let mut client = clients
|
||||
.get(&client_id)
|
||||
.ok_or(ClientError::NotFound(client_id))?
|
||||
.ok_or_else(|| ShipError::ClientNotFound(client_id))?
|
||||
.write()
|
||||
.await;
|
||||
|
@ -2,17 +2,18 @@
|
||||
use rand::{Rng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
use serde::{Serialize, Deserialize};
|
||||
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;
|
||||
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;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct BoxDropRate {
|
||||
@ -175,8 +176,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),
|
||||
@ -203,7 +204,7 @@ impl BoxDropTable {
|
||||
FixedBoxDropType::Specific(value) => {
|
||||
let mut buf: [u8; 16] = [0; 16];
|
||||
buf[0..4].copy_from_slice(&u32::to_be_bytes(value));
|
||||
ItemDropType::parse_item_from_bytes(buf)
|
||||
ItemDetail::parse_item_from_bytes(buf)
|
||||
},
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::Rng;
|
||||
use rand::{Rng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
|
||||
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};
|
||||
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};
|
||||
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::Rng;
|
||||
use rand::{Rng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
|
||||
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};
|
||||
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};
|
||||
|
||||
|
||||
#[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 {
|
@ -1,14 +1,14 @@
|
||||
use std::collections::BTreeMap;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::Rng;
|
||||
use rand::{Rng};
|
||||
use rand::seq::IteratorRandom;
|
||||
|
||||
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};
|
||||
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};
|
||||
|
||||
|
||||
|
@ -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 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};
|
||||
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};
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
208
src/ship/drops/mod.rs
Normal file
208
src/ship/drops/mod.rs
Normal file
@ -0,0 +1,208 @@
|
||||
#![allow(dead_code, unused_must_use)]
|
||||
// TODO: there is some structure duplication that occurs here:
|
||||
// the rare and box tables instantiate their own copies of the
|
||||
// generic drop tables as they need them to apply their modifiers
|
||||
// to their drops
|
||||
|
||||
|
||||
mod drop_table;
|
||||
mod rare_drop_table;
|
||||
mod generic_weapon;
|
||||
mod generic_armor;
|
||||
mod generic_shield;
|
||||
mod generic_unit;
|
||||
mod tool_table;
|
||||
mod tech_table;
|
||||
mod box_drop_table;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::io::Read;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
use crate::ship::monster::MonsterType;
|
||||
use crate::ship::room::{Difficulty, Episode};
|
||||
use crate::ship::map::MapArea;
|
||||
use crate::entity::character::SectionID;
|
||||
use crate::ship::drops::generic_weapon::GenericWeaponTable;
|
||||
use crate::ship::drops::generic_armor::GenericArmorTable;
|
||||
use crate::ship::drops::generic_shield::GenericShieldTable;
|
||||
use crate::ship::drops::generic_unit::GenericUnitTable;
|
||||
use crate::ship::drops::tool_table::ToolTable;
|
||||
use crate::ship::drops::rare_drop_table::RareDropTable;
|
||||
use crate::ship::drops::box_drop_table::BoxDropTable;
|
||||
use crate::ship::map::MapObject;
|
||||
use crate::entity::item::{weapon, armor, shield, unit, mag, tool, tech};
|
||||
|
||||
|
||||
fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
|
||||
let mut path = PathBuf::from("data/drops/");
|
||||
path.push(episode.to_string());
|
||||
path.push(difficulty.to_string().to_lowercase());
|
||||
path.push(section_id.to_string().to_lowercase());
|
||||
path.push(filename);
|
||||
path
|
||||
}
|
||||
|
||||
pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> T {
|
||||
let path = data_file_path(episode, difficulty, section_id, filename);
|
||||
let mut f = File::open(path).unwrap();
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s);
|
||||
|
||||
toml::from_str::<T>(s.as_str()).unwrap()
|
||||
}
|
||||
|
||||
// this is just copypaste
|
||||
pub fn load_rare_monster_file<T: serde::de::DeserializeOwned>(episode: Episode) -> T {
|
||||
// TODO: where does the rare monster toml file actually live
|
||||
let mut path = PathBuf::from("data/battle_param/");
|
||||
path.push(episode.to_string().to_lowercase() + "_rare_monster.toml");
|
||||
|
||||
let mut f = File::open(path).unwrap();
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s);
|
||||
toml::from_str::<T>(s.as_str()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum MonsterDropType {
|
||||
#[serde(rename = "weapon")]
|
||||
Weapon,
|
||||
#[serde(rename = "armor")]
|
||||
Armor,
|
||||
#[serde(rename = "shield")]
|
||||
Shield,
|
||||
#[serde(rename = "unit")]
|
||||
Unit,
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub struct MonsterDropStats {
|
||||
pub dar: u32,
|
||||
pub drop_type: MonsterDropType,
|
||||
pub min_meseta: u32,
|
||||
pub max_meseta: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ItemDropType {
|
||||
Weapon(weapon::Weapon),
|
||||
Armor(armor::Armor),
|
||||
Shield(shield::Shield),
|
||||
Unit(unit::Unit),
|
||||
Tool(tool::Tool),
|
||||
//Tools(Vec<tool::Tool>),
|
||||
TechniqueDisk(tech::TechniqueDisk),
|
||||
Mag(mag::Mag),
|
||||
Meseta(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ItemDrop {
|
||||
pub map_area: MapArea,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub item: ItemDropType,
|
||||
}
|
||||
|
||||
|
||||
pub struct DropTable<R: Rng + SeedableRng> {
|
||||
monster_stats: HashMap<MonsterType, MonsterDropStats>,
|
||||
rare_table: RareDropTable,
|
||||
weapon_table: GenericWeaponTable,
|
||||
armor_table: GenericArmorTable,
|
||||
shield_table: GenericShieldTable,
|
||||
unit_table: GenericUnitTable,
|
||||
tool_table: ToolTable,
|
||||
box_table: BoxDropTable,
|
||||
rng: R,
|
||||
}
|
||||
|
||||
impl<R: Rng + SeedableRng> DropTable<R> {
|
||||
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable<R> {
|
||||
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
|
||||
|
||||
DropTable {
|
||||
monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
|
||||
rare_table: RareDropTable::new(episode, difficulty, section_id),
|
||||
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
|
||||
armor_table: GenericArmorTable::new(episode, difficulty, section_id),
|
||||
shield_table: GenericShieldTable::new(episode, difficulty, section_id),
|
||||
unit_table: GenericUnitTable::new(episode, difficulty, section_id),
|
||||
tool_table: ToolTable::new(episode, difficulty, section_id),
|
||||
box_table: BoxDropTable::new(episode, difficulty, section_id),
|
||||
rng: R::from_entropy(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_meseta(&mut self, monster: &MonsterDropStats) -> Option<ItemDropType> {
|
||||
Some(ItemDropType::Meseta(self.rng.gen_range(monster.min_meseta, monster.max_meseta + 1)))
|
||||
}
|
||||
|
||||
fn generate_typed_drop(&mut self, map_area: &MapArea, monster: &MonsterDropStats) -> Option<ItemDropType> {
|
||||
match monster.drop_type {
|
||||
MonsterDropType::Weapon => self.weapon_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Armor => self.armor_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Shield => self.shield_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::Unit => self.unit_table.get_drop(map_area, &mut self.rng),
|
||||
MonsterDropType::None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
|
||||
let monster_stat = *self.monster_stats.get(monster)?;
|
||||
|
||||
let drop_anything = self.rng.gen_range(0, 100);
|
||||
if drop_anything > monster_stat.dar {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(item) = self.rare_table.get_drop(map_area, monster, &mut self.rng) {
|
||||
return Some(item);
|
||||
}
|
||||
|
||||
let drop_type = self.rng.gen_range(0, 3);
|
||||
|
||||
match drop_type {
|
||||
0 => {
|
||||
self.generate_meseta(&monster_stat)
|
||||
},
|
||||
1 => {
|
||||
self.tool_table.get_drop(map_area, &mut self.rng)
|
||||
},
|
||||
2 => {
|
||||
self.generate_typed_drop(map_area, &monster_stat)
|
||||
},
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
|
||||
self.box_table.get_drop(map_area, object, &mut self.rng)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rand::seq::IteratorRandom;
|
||||
|
||||
#[test]
|
||||
fn test_initializing_drop_table() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_entropy();
|
||||
let episode = vec![Episode::One, Episode::Two].into_iter().choose(&mut rng).unwrap();
|
||||
let difficulty = vec![Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate]
|
||||
.into_iter().choose(&mut rng).unwrap();
|
||||
let section_id = vec![SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
|
||||
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill]
|
||||
.into_iter().choose(&mut rng).unwrap();
|
||||
DropTable::<rand_chacha::ChaCha20Rng>::new(episode, difficulty, section_id);
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
use std::collections::HashMap;
|
||||
use rand::Rng;
|
||||
use serde::{Serialize, Deserialize};
|
||||
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;
|
||||
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;
|
||||
|
||||
type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>;
|
||||
|
||||
@ -50,9 +50,9 @@ impl RareDropItem {
|
||||
}
|
||||
|
||||
|
||||
pub struct RareDropRate {
|
||||
pub rate: f32,
|
||||
pub item: RareDropItem
|
||||
struct RareDropRate {
|
||||
rate: f32,
|
||||
item: RareDropItem
|
||||
}
|
||||
|
||||
|
||||
@ -71,41 +71,30 @@ 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: load_default_monster_rates(episode, difficulty, section_id),
|
||||
rates,
|
||||
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) => {
|
||||
@ -166,46 +155,3 @@ impl RareDropTable {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct RareDropTableBuilder {
|
||||
rates: Option<HashMap<MonsterType, Vec<RareDropRate>>>,
|
||||
attribute_table: Option<AttributeTable>,
|
||||
armor_stats: Option<GenericArmorTable>,
|
||||
shield_stats: Option<GenericShieldTable>,
|
||||
}
|
||||
|
||||
|
||||
// TODO: add the rest of these later I just need these ones right now
|
||||
impl RareDropTableBuilder {
|
||||
pub fn rates(mut self, rates: HashMap<MonsterType, Vec<RareDropRate>>) -> RareDropTableBuilder {
|
||||
self.rates = Some(rates);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn rate(mut self, monster_type: MonsterType, drop_rate: RareDropRate) -> RareDropTableBuilder {
|
||||
match &mut self.rates {
|
||||
Some(rates) => {
|
||||
rates.entry(monster_type)
|
||||
.or_insert(Vec::new())
|
||||
.push(drop_rate);
|
||||
},
|
||||
None => {
|
||||
let mut rates = HashMap::default();
|
||||
rates.insert(monster_type, vec![drop_rate]);
|
||||
self.rates = Some(rates);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> RareDropTable {
|
||||
RareDropTable {
|
||||
rates: self.rates.unwrap_or_else(|| load_default_monster_rates(episode, difficulty, section_id)),
|
||||
attribute_table: self.attribute_table.unwrap_or_else(|| AttributeTable::new(episode, difficulty, section_id)),
|
||||
armor_stats: self.armor_stats.unwrap_or_else(|| GenericArmorTable::new(episode, difficulty, section_id)),
|
||||
shield_stats: self.shield_stats.unwrap_or_else(|| GenericShieldTable::new(episode, difficulty, section_id)),
|
||||
}
|
||||
}
|
||||
}
|
@ -3,11 +3,11 @@ use serde::{Serialize, Deserialize};
|
||||
use rand::{Rng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
|
||||
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};
|
||||
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};
|
||||
|
||||
|
||||
|
@ -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 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;
|
||||
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;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, enum_utils::FromStr)]
|
@ -4,13 +4,13 @@ use serde::{Serialize, Deserialize};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use entity::item::weapon::WeaponType;
|
||||
use entity::item::armor::ArmorType;
|
||||
use entity::item::shield::ShieldType;
|
||||
use entity::item::unit::UnitType;
|
||||
use entity::item::mag::MagType;
|
||||
use entity::item::tool::ToolType;
|
||||
use entity::item::tech::Technique;
|
||||
use crate::entity::item::weapon::WeaponType;
|
||||
use crate::entity::item::armor::ArmorType;
|
||||
use crate::entity::item::shield::ShieldType;
|
||||
use crate::entity::item::unit::UnitType;
|
||||
use crate::entity::item::mag::MagType;
|
||||
use crate::entity::item::tool::ToolType;
|
||||
use crate::entity::item::tech::Technique;
|
||||
|
||||
|
||||
lazy_static::lazy_static! {
|
@ -1,48 +1,44 @@
|
||||
// TODO: replace various u32s and usizes denoting item amounts for ItemAmount(u32) for consistency
|
||||
use crate::ClientItemId;
|
||||
use entity::item::{Meseta, ItemNote};
|
||||
use crate::ship::items::ClientItemId;
|
||||
use crate::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 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;
|
||||
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>>;
|
||||
|
||||
pub enum TriggerCreateItem {
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum CreateItem {
|
||||
Individual(AreaClient, ClientItemId, IndividualItemDetail),
|
||||
Stacked(AreaClient, ClientItemId, Tool, usize),
|
||||
}
|
||||
|
||||
pub(super) fn take_item_from_floor<'a, EG, TR>(
|
||||
pub(super) fn take_item_from_floor<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_id: ClientItemId
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + 'a
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway + Send,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction): (ItemStateProxy, TR) , _| {
|
||||
Box::pin(async move {
|
||||
@ -55,13 +51,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_floor_item_to_inventory<'a, EG, TR>(
|
||||
pub(super) fn add_floor_item_to_inventory<EG, TR>(
|
||||
character: &CharacterEntity
|
||||
) -> impl Fn((ItemStateProxy, TR), FloorItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), TriggerCreateItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), TriggerCreateItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'static,
|
||||
{
|
||||
let character = character.clone();
|
||||
move |(mut item_state, transaction), floor_item| {
|
||||
@ -101,49 +97,21 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn remove_item_from_inventory<'a, EG, TR>(
|
||||
|
||||
pub(super) fn take_item_from_inventory<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_id: ClientItemId,
|
||||
amount: u32,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItemDetail), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
Box::pin(async move {
|
||||
let mut inventory = item_state.inventory(&character_id).await?;
|
||||
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:#?}"))?;
|
||||
let item = inventory.take_item(&item_id, amount).ok_or_else(|| ItemStateError::NoFloorItem(item_id))?;
|
||||
|
||||
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
|
||||
item_state.set_inventory(inventory).await;
|
||||
@ -154,15 +122,15 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn add_inventory_item_to_shared_floor<'a, EG, TR>(
|
||||
pub(super) fn add_inventory_item_to_shared_floor<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
map_area: MapArea,
|
||||
drop_position: (f32, f32, f32),
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), inventory_item| {
|
||||
Box::pin(async move {
|
||||
@ -188,14 +156,14 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn take_meseta_from_inventory<'a, EG, TR>(
|
||||
pub(super) fn take_meseta_from_inventory<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
amount: u32,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
Box::pin(async move {
|
||||
@ -209,14 +177,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_meseta_to_inventory<'a, EG, TR>(
|
||||
pub(super) fn add_meseta_to_inventory<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
amount: u32
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
Box::pin(async move {
|
||||
@ -230,16 +198,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_meseta_to_shared_floor<'a, EG, TR>(
|
||||
pub(super) fn add_meseta_to_shared_floor<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
amount: u32,
|
||||
map_area: MapArea,
|
||||
drop_position: (f32, f32)
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), _| {
|
||||
Box::pin(async move {
|
||||
@ -261,42 +229,40 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn take_meseta_from_bank<'a, EG, TR>(
|
||||
pub(super) fn take_meseta_from_bank<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
amount: u32,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
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.identifier, bank.meseta).await?;
|
||||
item_state.set_bank(bank).await;
|
||||
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
|
||||
|
||||
Ok(((item_state, transaction), ()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_meseta_from_bank_to_inventory<'a, EG, TR>(
|
||||
pub(super) fn add_meseta_from_bank_to_inventory<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
amount: u32,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
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), ()))
|
||||
})
|
||||
@ -304,21 +270,20 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn add_meseta_to_bank<'a, EG, TR>(
|
||||
pub(super) fn add_meseta_to_bank<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
amount: u32,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
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.identifier, bank.meseta).await?;
|
||||
item_state.set_bank(bank).await;
|
||||
transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?;
|
||||
|
||||
Ok(((item_state, transaction), ()))
|
||||
})
|
||||
@ -326,23 +291,21 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn take_item_from_bank<'a, EG, TR>(
|
||||
pub(super) fn take_item_from_bank<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_id: ClientItemId,
|
||||
amount: u32,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), BankItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), BankItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
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)
|
||||
.await
|
||||
.ok_or_else(|| ItemStateError::NoBankItem(item_id))?;
|
||||
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.identifier).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?;
|
||||
item_state.set_bank(bank).await;
|
||||
|
||||
Ok(((item_state, transaction), item))
|
||||
@ -350,28 +313,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_bank_item_to_inventory<'a, EG, TR>(
|
||||
pub(super) fn add_bank_item_to_inventory<EG, TR>(
|
||||
character: &CharacterEntity,
|
||||
) -> impl Fn((ItemStateProxy, TR), BankItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
let character = character.clone();
|
||||
move |(mut item_state, transaction), bank_item| {
|
||||
let character = character.clone();
|
||||
Box::pin(async move {
|
||||
let bank_identifier = item_state.bank(&character.id).await?.identifier;
|
||||
let bank_name = item_state.bank(&character.id).await?.name;
|
||||
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_identifier = bank_identifier.clone();
|
||||
let bank_name = bank_name.clone();
|
||||
async move {
|
||||
transaction.gateway().add_item_note(&entity_id, ItemNote::Withdraw {
|
||||
character_id,
|
||||
bank: bank_identifier,
|
||||
bank: bank_name,
|
||||
}).await?;
|
||||
Ok(transaction)
|
||||
}}).await?;
|
||||
@ -401,48 +364,49 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn add_inventory_item_to_bank<'a, EG, TR>(
|
||||
pub(super) fn add_inventory_item_to_bank<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), inventory_item| {
|
||||
Box::pin(async move {
|
||||
let mut bank = item_state.bank(&character_id).await?;
|
||||
let bank_identifier = bank.identifier.clone();
|
||||
let bank_name = bank.name.clone();
|
||||
let mut transaction = inventory_item.with_entity_id(transaction, move |mut transaction, entity_id| {
|
||||
let bank_identifier = bank_identifier.clone();
|
||||
let bank_name = bank_name.clone();
|
||||
async move {
|
||||
transaction.gateway().add_item_note(&entity_id, ItemNote::Deposit {
|
||||
character_id,
|
||||
bank: bank_identifier,
|
||||
bank: bank_name,
|
||||
}).await?;
|
||||
Ok(transaction)
|
||||
}}).await?;
|
||||
|
||||
bank.add_inventory_item(inventory_item)?;
|
||||
|
||||
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.identifier).await?;
|
||||
transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?;
|
||||
item_state.set_bank(bank).await;
|
||||
|
||||
|
||||
Ok(((item_state, transaction), ()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn equip_inventory_item<'a, EG, TR>(
|
||||
pub(super) fn equip_inventory_item<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_id: ClientItemId,
|
||||
equip_slot: u8,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
Box::pin(async move {
|
||||
@ -457,14 +421,14 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn unequip_inventory_item<'a, EG, TR>(
|
||||
pub(super) fn unequip_inventory_item<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_id: ClientItemId,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
Box::pin(async move {
|
||||
@ -480,14 +444,14 @@ where
|
||||
|
||||
|
||||
|
||||
pub(super) fn sort_inventory_items<'a, EG, TR>(
|
||||
pub(super) fn sort_inventory_items<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_ids: Vec<ClientItemId>,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), ()), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
let item_ids = item_ids.clone();
|
||||
@ -503,13 +467,13 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn use_consumed_item<'a, EG, TR>(
|
||||
pub(super) fn use_consumed_item<EG, TR>(
|
||||
character: &CharacterEntity,
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItemDetail)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), anyhow::Error>>
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<ApplyItemAction>), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway + Clone + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
EG: EntityGateway + Clone + 'static,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
let character = character.clone();
|
||||
move |(mut item_state, transaction), inventory_item| {
|
||||
@ -517,9 +481,7 @@ 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 {
|
||||
character_id: character.id,
|
||||
}).await?;
|
||||
transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed).await?;
|
||||
Ok(transaction)
|
||||
}}).await?;
|
||||
|
||||
@ -531,14 +493,14 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn feed_mag_item<'a, EG, TR>(
|
||||
pub(super) fn feed_mag_item<EG, TR>(
|
||||
character: CharacterEntity,
|
||||
mag_item_id: ClientItemId,
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), tool| {
|
||||
let character = character.clone();
|
||||
@ -554,7 +516,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?;
|
||||
@ -589,10 +551,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), anyhow::Error>> + Send + 'a>>
|
||||
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Send + 'a>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
Box::pin(async move {
|
||||
@ -619,8 +581,7 @@ where
|
||||
tool,
|
||||
})
|
||||
};
|
||||
inventory.add_item(inventory_item.clone())
|
||||
.with_context(|| format!("inventory {inventory:?}\nitem {inventory_item:?}"))?.1
|
||||
inventory.add_item(inventory_item)?.1
|
||||
},
|
||||
item_detail => {
|
||||
let item_entity = transaction.gateway().create_item(NewItemEntity {
|
||||
@ -637,8 +598,7 @@ where
|
||||
item: item_detail,
|
||||
})
|
||||
};
|
||||
inventory.add_item(inventory_item.clone())
|
||||
.with_context(|| format!("inventory {inventory:?}\nitem {inventory_item:?}"))?.1
|
||||
inventory.add_item(inventory_item)?.1
|
||||
},
|
||||
};
|
||||
|
||||
@ -650,13 +610,13 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn sell_inventory_item<'a, EG, TR>(
|
||||
pub(super) fn sell_inventory_item<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), inventory_item| {
|
||||
Box::pin(async move {
|
||||
@ -666,9 +626,7 @@ 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 {
|
||||
character_id,
|
||||
}).await?;
|
||||
transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop).await?;
|
||||
Ok(transaction)
|
||||
}}).await?;
|
||||
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
|
||||
@ -685,7 +643,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>), anyhow::Error>
|
||||
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
|
||||
where
|
||||
'a: 'async_recursion,
|
||||
EG: EntityGateway,
|
||||
@ -693,9 +651,9 @@ where
|
||||
I: Send,
|
||||
O: Send,
|
||||
T: Clone + Send + Sync,
|
||||
F: Fn(I) -> FR + Send + Sync + Clone + 'a,
|
||||
F: Fn(I) -> FR + Send + Sync + Clone + 'static,
|
||||
FR: Fn((ItemStateProxy, TR), T)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
|
||||
{
|
||||
let item = match input.pop() {
|
||||
Some(item) => item,
|
||||
@ -711,20 +669,20 @@ where
|
||||
Ok((state, output))
|
||||
}
|
||||
|
||||
pub(super) fn iterate<'a, EG, TR, I, O, T, F, FR>(
|
||||
pub(super) fn iterate<EG, TR, I, O, T, F, FR>(
|
||||
input: Vec<I>,
|
||||
func: F,
|
||||
) -> impl Fn((ItemStateProxy, TR), T)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
O: Send,
|
||||
I: Send + Clone + 'static + std::fmt::Debug,
|
||||
T: Send + Clone + 'static + std::fmt::Debug,
|
||||
F: Fn(I) -> FR + Send + Sync + Clone + 'a,
|
||||
F: Fn(I) -> FR + Send + Sync + Clone + 'static,
|
||||
FR: Fn((ItemStateProxy, TR), T)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
|
||||
T: Clone + Send + Sync,
|
||||
{
|
||||
move |(item_state, transaction), arg| {
|
||||
@ -743,16 +701,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>), anyhow::Error>
|
||||
) -> Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>
|
||||
where
|
||||
'a: 'async_recursion,
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
O: Send,
|
||||
T: Send,
|
||||
F: Fn((ItemStateProxy, TR), T)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync,
|
||||
I: Iterator<Item = T> + Send + Sync + 'a,
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync,
|
||||
I: Iterator<Item = T> + Send + Sync + 'static,
|
||||
{
|
||||
let item = match input.next() {
|
||||
Some(item) => item,
|
||||
@ -767,19 +725,19 @@ where
|
||||
Ok((state, output))
|
||||
}
|
||||
|
||||
pub(super) fn foreach<'a, EG, TR, O, T, F, I>(
|
||||
pub(super) fn foreach<EG, TR, O, T, F, I>(
|
||||
func: F
|
||||
) -> impl Fn((ItemStateProxy, TR), I)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<O>), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<O>), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
O: Send,
|
||||
T: Send + Clone + 'static + std::fmt::Debug,
|
||||
F: Fn((ItemStateProxy, TR), T)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), O), anyhow::Error>> + Send + Sync + 'a,
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), O), ItemStateError>> + Send + Sync + 'static,
|
||||
T: Send + Sync,
|
||||
I: IntoIterator<Item = T> + Send + Sync + 'a,
|
||||
I: IntoIterator<Item = T> + Send + Sync + 'static,
|
||||
I::IntoIter: Send + Sync,
|
||||
{
|
||||
let func = Arc::new(func);
|
||||
@ -796,10 +754,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), anyhow::Error>> + Send + 'a>>
|
||||
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy, TR), T), ItemStateError>> + Send + 'a>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
T: Send + Clone + 'a,
|
||||
{
|
||||
move |state, _| {
|
||||
@ -810,17 +768,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fork<'a, EG, TR, F1, F2, T, O1, O2>(
|
||||
pub(super) fn fork<EG, TR, F1, F2, T, O1, O2>(
|
||||
func1: F1,
|
||||
func2: F2,
|
||||
) -> impl Fn((ItemStateProxy, TR), T)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), (O1, O2)), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), (O1, O2)), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
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,
|
||||
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,
|
||||
O1: Send,
|
||||
O2: Send,
|
||||
{
|
||||
@ -838,13 +796,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_item_to_inventory<'a, EG, TR>(
|
||||
pub(super) fn add_item_to_inventory<EG, TR>(
|
||||
character: CharacterEntity,
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), inventory_item| {
|
||||
let character = character.clone();
|
||||
@ -866,15 +824,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn record_trade<'a, EG, TR>(
|
||||
pub(super) fn record_trade<EG, TR>(
|
||||
trade_id: TradeId,
|
||||
character_to: CharacterEntityId,
|
||||
character_from: CharacterEntityId,
|
||||
) -> impl Fn((ItemStateProxy, TR), Vec<InventoryItem>)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<InventoryItem>), anyhow::Error>> + Clone
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<InventoryItem>), ItemStateError>> + Clone
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(item_state, mut transaction), traded_items| {
|
||||
Box::pin(async move {
|
||||
@ -895,12 +853,12 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn assign_new_item_id<'a, EG, TR>(
|
||||
pub(super) fn assign_new_item_id<EG, TR>(
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>> + Clone
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>> + Clone
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction), mut inventory_item| {
|
||||
Box::pin(async move {
|
||||
@ -911,13 +869,14 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn convert_item_drop_to_floor_item<'a, EG, TR>(
|
||||
pub(super) fn convert_item_drop_to_floor_item<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
item_drop: ItemDrop,
|
||||
) -> impl Fn((ItemStateProxy, TR), ())
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>> + Clone
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), _| {
|
||||
let item_drop = item_drop.clone();
|
||||
@ -953,6 +912,13 @@ 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 {
|
||||
@ -969,6 +935,13 @@ 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{
|
||||
@ -998,95 +971,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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<'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::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>(
|
||||
pub(super) fn add_item_to_local_floor<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
) -> impl Fn((ItemStateProxy, TR), FloorItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), FloorItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, transaction) , floor_item| {
|
||||
Box::pin(async move {
|
||||
@ -1099,13 +990,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_modifier_to_inventory_item<'a, EG, TR>(
|
||||
pub(super) fn apply_modifier_to_inventory_item<EG, TR>(
|
||||
modifier: ItemModifier,
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), InventoryItem), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), InventoryItem), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(item_state, mut transaction), mut inventory_item| {
|
||||
let modifier = modifier.clone();
|
||||
@ -1113,9 +1004,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.into())
|
||||
_ => return Err(ItemStateError::InvalidModifier)
|
||||
}
|
||||
|
||||
Ok(((item_state, transaction), inventory_item))
|
||||
@ -1123,18 +1014,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn as_individual_item<'a, EG, TR>(
|
||||
pub(super) fn as_individual_item<EG, TR>(
|
||||
) -> impl Fn((ItemStateProxy, TR), InventoryItem)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), IndividualItemDetail), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), IndividualItemDetail), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
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).into())
|
||||
_ => return Err(ItemStateError::WrongItemType(inventory_item.item_id))
|
||||
};
|
||||
|
||||
Ok(((item_state, transaction), item))
|
||||
@ -1143,14 +1034,14 @@ where
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn apply_item_action_packets<'a, EG, TR>(
|
||||
pub(super) fn apply_item_action_packets<EG, TR>(
|
||||
character_id: CharacterEntityId,
|
||||
area_client: AreaClient,
|
||||
) -> impl Fn((ItemStateProxy, TR), ApplyItemAction)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<CreateItem>), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), Vec<SendShipPacket>), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
move |(mut item_state, mut transaction), apply_item_action| {
|
||||
Box::pin(async move {
|
||||
@ -1164,7 +1055,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 = CreateItem::Stacked(area_client, item_id, tool, 1);
|
||||
let create_item = builder::message::create_stacked_item(area_client, item_id, &tool, 1).map_err(|_err| ItemStateError::Dummy)?;
|
||||
let item_detail = StackedItemDetail {
|
||||
entity_ids: vec![new_item.id],
|
||||
tool
|
||||
@ -1176,7 +1067,7 @@ where
|
||||
entity_id: new_item.id,
|
||||
item: item_detail,
|
||||
};
|
||||
let create_item = CreateItem::Individual(area_client, item_id, item_detail.clone());
|
||||
let create_item = builder::message::create_individual_item(area_client, item_id, &item_detail).map_err(|_err| ItemStateError::Dummy)?;
|
||||
(InventoryItemDetail::Individual(item_detail), create_item)
|
||||
};
|
||||
|
||||
@ -1190,8 +1081,7 @@ 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![create_item]
|
||||
vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
|
||||
}
|
||||
else {
|
||||
Vec::new()
|
||||
@ -1202,13 +1092,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_item_action_character<'a, EG, TR>(
|
||||
pub(super) fn apply_item_action_character<EG, TR>(
|
||||
character: &CharacterEntity
|
||||
) -> impl Fn((ItemStateProxy, TR), Vec<ApplyItemAction>)
|
||||
-> BoxFuture<'a, Result<((ItemStateProxy, TR), CharacterEntity), anyhow::Error>>
|
||||
-> BoxFuture<Result<((ItemStateProxy, TR), CharacterEntity), ItemStateError>>
|
||||
where
|
||||
EG: EntityGateway,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
|
||||
TR: EntityGatewayTransaction<ParentGateway = EG> + 'static,
|
||||
{
|
||||
let character = character.clone();
|
||||
move |(item_state, transaction), apply_item_actions| {
|
||||
@ -1224,25 +1114,3 @@ 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), ()))
|
||||
})
|
||||
}
|
||||
}
|
@ -1,18 +1,15 @@
|
||||
use std::convert::TryInto;
|
||||
use futures::future::join_all;
|
||||
use thiserror::Error;
|
||||
use anyhow::Context;
|
||||
use rand::SeedableRng;
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
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;
|
||||
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};
|
||||
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -21,12 +18,14 @@ pub enum ApplyItemError {
|
||||
NoCharacter,
|
||||
#[error("item not equipped")]
|
||||
ItemNotEquipped,
|
||||
#[error("could not use item invalid item")]
|
||||
#[error("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),
|
||||
}
|
||||
@ -35,47 +34,53 @@ pub enum ApplyItemError {
|
||||
pub enum ApplyItemAction {
|
||||
UpdateCharacter(Box<CharacterEntity>),
|
||||
CreateItem(ItemDetail),
|
||||
//TransformItem(ItemDetail),
|
||||
//TransformItem,
|
||||
//RemoveItem,
|
||||
}
|
||||
|
||||
async fn power_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, anyhow::Error> {
|
||||
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> {
|
||||
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>, anyhow::Error> {
|
||||
async fn mind_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
|
||||
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>, anyhow::Error> {
|
||||
async fn evade_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
|
||||
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>, anyhow::Error> {
|
||||
async fn def_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
|
||||
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>, anyhow::Error> {
|
||||
async fn luck_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
|
||||
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>, anyhow::Error> {
|
||||
async fn hp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
|
||||
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>, anyhow::Error> {
|
||||
async fn tp_material<EG: EntityGateway + ?Sized>(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<Vec<ApplyItemAction>, ApplyItemError> {
|
||||
character.materials.tp += 1;
|
||||
entity_gateway.save_character(character).await.unwrap();
|
||||
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||
@ -108,7 +113,7 @@ async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy,
|
||||
character: &CharacterEntity,
|
||||
cell_entity_id: ItemEntityId,
|
||||
mag_cell_type: MagCell)
|
||||
-> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||
-> Result<Vec<ApplyItemAction>, ApplyItemError>
|
||||
where
|
||||
EG: EntityGateway + ?Sized,
|
||||
{
|
||||
@ -224,9 +229,9 @@ pub async fn liberta_kit<EG: EntityGateway>(entity_gateway: &mut EG, used_cell:
|
||||
*/
|
||||
|
||||
|
||||
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||
fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, ApplyItemError>
|
||||
{
|
||||
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,
|
||||
@ -242,36 +247,12 @@ fn jack_o_lantern() -> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||
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>, anyhow::Error>
|
||||
-> Result<Vec<ApplyItemAction>, ApplyItemError>
|
||||
where
|
||||
EG: EntityGateway + ?Sized,
|
||||
{
|
||||
@ -289,17 +270,6 @@ 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
|
||||
@ -329,48 +299,25 @@ where
|
||||
}
|
||||
ToolType::JackOLantern => jack_o_lantern(),
|
||||
// TODO: rest of these
|
||||
_ => Err(anyhow::Error::from(ApplyItemError::InvalidTool))
|
||||
.with_context(|| {
|
||||
format!("invalid tool {tool:?}")
|
||||
})
|
||||
|
||||
_ => Err(ApplyItemError::InvalidItem)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn apply_tech<'a, EG>(_item_state: &mut ItemStateProxy,
|
||||
entity_gateway: &mut EG,
|
||||
character: &mut CharacterEntity,
|
||||
_entity_id: ItemEntityId,
|
||||
tech: TechniqueDisk)
|
||||
-> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||
where
|
||||
EG: EntityGateway + ?Sized,
|
||||
{
|
||||
// TODO: make sure the class can learn that specific tech
|
||||
character.techs.set_tech(tech.tech, TechLevel(tech.level as u8));
|
||||
entity_gateway.save_character(character).await.unwrap();
|
||||
Ok(vec![ApplyItemAction::UpdateCharacter(Box::new(character.clone()))])
|
||||
|
||||
}
|
||||
|
||||
pub async fn apply_item<'a, EG>(item_state: &'a mut ItemStateProxy,
|
||||
entity_gateway: &'a mut EG,
|
||||
character: &'a mut CharacterEntity,
|
||||
item: InventoryItemDetail
|
||||
) -> Result<Vec<ApplyItemAction>, anyhow::Error>
|
||||
pub async fn apply_item<'a, EG>(item_state: &mut ItemStateProxy,
|
||||
entity_gateway: &mut EG,
|
||||
character: &mut CharacterEntity,
|
||||
item: InventoryItem
|
||||
) -> Result<Vec<ApplyItemAction>, ApplyItemError>
|
||||
where
|
||||
EG: EntityGateway + ?Sized + Clone + 'a
|
||||
EG: EntityGateway + ?Sized + Clone + 'static
|
||||
{
|
||||
match item {
|
||||
match item.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,
|
||||
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:?}")
|
||||
})
|
||||
_ => Err(ApplyItemError::InvalidItem)
|
||||
}
|
||||
},
|
||||
InventoryItemDetail::Stacked(stacked_item) => {
|
@ -1,15 +1,13 @@
|
||||
use std::cmp::Ordering;
|
||||
use libpso::character::character;
|
||||
use crate::ClientItemId;
|
||||
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity};
|
||||
use crate::ship::items::ClientItemId;
|
||||
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity, BankName};
|
||||
use std::future::Future;
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
|
||||
use entity::character::CharacterEntityId;
|
||||
use entity::item::BankIdentifier;
|
||||
use crate::state::ItemStateError;
|
||||
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
|
||||
use crate::inventory::{InventoryItem, InventoryItemDetail};
|
||||
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};
|
||||
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@ -23,7 +21,6 @@ pub enum BankError {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BankItemDetail {
|
||||
Individual(IndividualItemDetail),
|
||||
@ -67,10 +64,10 @@ pub struct BankItem {
|
||||
}
|
||||
|
||||
impl BankItem {
|
||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
||||
where
|
||||
F: FnMut(T, ItemEntityId) -> Fut,
|
||||
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
||||
{
|
||||
match &self.item {
|
||||
BankItemDetail::Individual(individual_item) => {
|
||||
@ -99,27 +96,19 @@ impl Bank {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BankState {
|
||||
pub character_id: CharacterEntityId,
|
||||
pub item_id_counter: Arc<Mutex<u32>>,
|
||||
pub identifier: BankIdentifier,
|
||||
pub item_id_counter: u32,
|
||||
pub name: BankName,
|
||||
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, identifier: BankIdentifier, mut bank: Bank, meseta: Meseta) -> BankState {
|
||||
pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState {
|
||||
bank.0.sort();
|
||||
BankState {
|
||||
character_id,
|
||||
item_id_counter: Arc::new(Mutex::new(0)),
|
||||
identifier,
|
||||
item_id_counter: 0,
|
||||
name,
|
||||
bank,
|
||||
meseta,
|
||||
}
|
||||
@ -129,37 +118,34 @@ impl BankState {
|
||||
self.bank.0.len()
|
||||
}
|
||||
|
||||
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;
|
||||
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
|
||||
for (i, item) in self.bank.0.iter_mut().enumerate() {
|
||||
item.item_id = ClientItemId(*bitem_id + i as u32);
|
||||
item.item_id = ClientItemId(base_item_id + i as u32);
|
||||
}
|
||||
|
||||
*bitem_id += self.bank.0.len() as u32;
|
||||
self.item_id_counter = base_item_id + self.bank.0.len() as u32;
|
||||
}
|
||||
|
||||
pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
||||
if self.meseta.0 + amount > 999999 {
|
||||
return Err(ItemStateError::FullOfMeseta.into())
|
||||
return Err(ItemStateError::FullOfMeseta)
|
||||
}
|
||||
self.meseta.0 += amount;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
||||
if amount > self.meseta.0 {
|
||||
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
|
||||
return Err(ItemStateError::InvalidMesetaRemoval(amount))
|
||||
}
|
||||
self.meseta.0 -= amount;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, anyhow::Error> {
|
||||
pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result<AddItemResult, BankError> {
|
||||
match item.item {
|
||||
InventoryItemDetail::Individual(iitem) => {
|
||||
if self.bank.0.len() >= 30 {
|
||||
Err(BankError::BankFull.into())
|
||||
Err(BankError::BankFull)
|
||||
}
|
||||
else {
|
||||
self.bank.0.push(BankItem {
|
||||
@ -180,7 +166,7 @@ impl BankState {
|
||||
match existing_stack {
|
||||
Some(existing_stack) => {
|
||||
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
||||
Err(BankError::StackFull.into())
|
||||
Err(BankError::StackFull)
|
||||
}
|
||||
else {
|
||||
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
||||
@ -189,7 +175,7 @@ impl BankState {
|
||||
},
|
||||
None => {
|
||||
if self.bank.0.len() >= 30 {
|
||||
Err(BankError::BankFull.into())
|
||||
Err(BankError::BankFull)
|
||||
}
|
||||
else {
|
||||
self.bank.0.push(BankItem {
|
||||
@ -205,7 +191,7 @@ impl BankState {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option<BankItem> {
|
||||
pub 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)?;
|
||||
@ -225,8 +211,9 @@ impl BankState {
|
||||
}
|
||||
else {
|
||||
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
||||
self.item_id_counter += 1;
|
||||
Some(BankItem {
|
||||
item_id: new_item_id(&self.item_id_counter).await,
|
||||
item_id: ClientItemId(self.item_id_counter),
|
||||
item: BankItemDetail::Stacked(StackedItemDetail {
|
||||
entity_ids,
|
||||
tool: stacked_item.tool,
|
||||
@ -305,14 +292,12 @@ impl BankState {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl std::cmp::PartialEq for BankItemDetail {
|
||||
fn eq(&self, other: &BankItemDetail) -> bool {
|
||||
impl std::cmp::PartialEq for BankItem {
|
||||
fn eq(&self, other: &BankItem) -> bool {
|
||||
let mut self_bytes = [0u8; 4];
|
||||
let mut other_bytes = [0u8; 4];
|
||||
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
|
||||
other_bytes.copy_from_slice(&other.as_client_bytes()[0..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);
|
||||
@ -321,46 +306,35 @@ impl std::cmp::PartialEq for BankItemDetail {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Eq for BankItemDetail {}
|
||||
|
||||
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 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.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);
|
||||
|
||||
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))
|
||||
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::Ord for BankItem {
|
||||
fn cmp(&self, other: &BankItem) -> std::cmp::Ordering {
|
||||
self.item.cmp(&other.item)
|
||||
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_le_bytes(self_bytes);
|
||||
let other_value = u32::from_le_bytes(other_bytes);
|
||||
|
||||
self_value.cmp(&other_value)
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
use crate::ClientItemId;
|
||||
use entity::item::{Meseta, ItemEntityId, ItemDetail};
|
||||
use crate::ship::items::ClientItemId;
|
||||
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail};
|
||||
use std::future::Future;
|
||||
|
||||
use maps::area::MapArea;
|
||||
use entity::character::CharacterEntityId;
|
||||
use entity::item::mag::Mag;
|
||||
use crate::ship::map::MapArea;
|
||||
use crate::entity::character::CharacterEntityId;
|
||||
use crate::entity::item::mag::Mag;
|
||||
|
||||
use crate::state::ItemStateError;
|
||||
use crate::state::{IndividualItemDetail, StackedItemDetail};
|
||||
use crate::inventory::{InventoryItem, InventoryItemDetail};
|
||||
use crate::ship::items::state::ItemStateError;
|
||||
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail};
|
||||
use crate::ship::items::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, anyhow::Error>
|
||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
||||
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, anyhow::Error>
|
||||
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
||||
where
|
||||
F: FnMut(T, ItemEntityId, Mag) -> Fut,
|
||||
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
||||
{
|
||||
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
|
||||
.extract_if(|item| {
|
||||
.drain_filter(|item| {
|
||||
item.item_id == *item_id
|
||||
})
|
||||
.next();
|
||||
item.or_else(|| {
|
||||
self.shared.0
|
||||
.extract_if(|item| {
|
||||
.drain_filter(|item| {
|
||||
item.item_id == *item_id
|
||||
})
|
||||
.next()
|
@ -1,18 +1,16 @@
|
||||
use std::cmp::Ordering;
|
||||
use libpso::character::character;
|
||||
use crate::ClientItemId;
|
||||
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
|
||||
use crate::ship::items::ClientItemId;
|
||||
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
|
||||
use std::future::Future;
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
|
||||
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};
|
||||
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};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InventoryItemDetail {
|
||||
@ -62,7 +60,7 @@ impl InventoryItemDetail {
|
||||
}
|
||||
|
||||
// TODO: this should probably go somewhere a bit more fundamental like ItemDetail
|
||||
pub fn sell_price(&self) -> Result<u32, anyhow::Error> {
|
||||
pub fn sell_price(&self) -> Result<u32, ItemStateError> {
|
||||
match self {
|
||||
InventoryItemDetail::Individual(individual_item) => {
|
||||
match &individual_item.item {
|
||||
@ -104,7 +102,7 @@ impl InventoryItemDetail {
|
||||
Ok((ToolShopItem::from(d).price() / 8) as u32)
|
||||
},
|
||||
ItemDetail::Mag(_m) => {
|
||||
Err(ItemStateError::ItemNotSellable.into())
|
||||
Err(ItemStateError::ItemNotSellable)
|
||||
},
|
||||
ItemDetail::ESWeapon(_e) => {
|
||||
Ok(10u32)
|
||||
@ -118,12 +116,22 @@ impl InventoryItemDetail {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, anyhow::Error>
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InventoryItem {
|
||||
pub item_id: ClientItemId,
|
||||
pub item: InventoryItemDetail,
|
||||
}
|
||||
|
||||
impl InventoryItem {
|
||||
pub async fn with_entity_id<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
||||
where
|
||||
F: FnMut(T, ItemEntityId) -> Fut,
|
||||
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
||||
{
|
||||
match &self {
|
||||
match &self.item {
|
||||
InventoryItemDetail::Individual(individual_item) => {
|
||||
param = func(param, individual_item.entity_id).await?;
|
||||
},
|
||||
@ -136,28 +144,11 @@ impl InventoryItemDetail {
|
||||
|
||||
Ok(param)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[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>
|
||||
pub async fn with_mag<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
|
||||
where
|
||||
F: FnMut(T, ItemEntityId, Mag) -> Fut,
|
||||
Fut: Future<Output=Result<T, anyhow::Error>>,
|
||||
Fut: Future<Output=Result<T, ItemStateError>>,
|
||||
{
|
||||
if let InventoryItemDetail::Individual(individual_item) = &self.item {
|
||||
if let ItemDetail::Mag(mag) = &individual_item.item {
|
||||
@ -191,49 +182,34 @@ pub enum InventoryError {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InventoryState {
|
||||
pub character_id: CharacterEntityId,
|
||||
pub item_id_counter: Arc<Mutex<u32>>,
|
||||
pub item_id_counter: 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 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;
|
||||
|
||||
pub fn initialize_item_ids(&mut self, base_item_id: u32) {
|
||||
for (i, item) in self.inventory.0.iter_mut().enumerate() {
|
||||
item.item_id = ClientItemId(*bitem_id + i as u32);
|
||||
item.item_id = ClientItemId(base_item_id + i as u32);
|
||||
}
|
||||
|
||||
*bitem_id += self.inventory.0.len() as u32;
|
||||
self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1;
|
||||
}
|
||||
|
||||
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 new_item_id(&mut self) -> ClientItemId {
|
||||
self.item_id_counter += 1;
|
||||
ClientItemId(self.item_id_counter)
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.inventory.0.len()
|
||||
}
|
||||
|
||||
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, anyhow::Error> {
|
||||
pub fn add_floor_item(&mut self, item: FloorItem) -> Result<AddItemResult, InventoryError> {
|
||||
match item.item {
|
||||
FloorItemDetail::Individual(iitem) => {
|
||||
if self.inventory.0.len() >= 30 {
|
||||
Err(InventoryError::InventoryFull.into())
|
||||
Err(InventoryError::InventoryFull)
|
||||
}
|
||||
else {
|
||||
self.inventory.0.push(InventoryItem {
|
||||
@ -253,7 +229,7 @@ impl InventoryState {
|
||||
match existing_stack {
|
||||
Some(existing_stack) => {
|
||||
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
||||
Err(InventoryError::StackFull.into())
|
||||
Err(InventoryError::StackFull)
|
||||
}
|
||||
else {
|
||||
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
||||
@ -262,7 +238,7 @@ impl InventoryState {
|
||||
},
|
||||
None => {
|
||||
if self.inventory.0.len() >= 30 {
|
||||
Err(InventoryError::InventoryFull.into())
|
||||
Err(InventoryError::InventoryFull)
|
||||
}
|
||||
else {
|
||||
self.inventory.0.push(InventoryItem {
|
||||
@ -277,7 +253,7 @@ impl InventoryState {
|
||||
},
|
||||
FloorItemDetail::Meseta(meseta) => {
|
||||
if self.meseta == Meseta(999999) {
|
||||
Err(InventoryError::MesetaFull.into())
|
||||
Err(InventoryError::MesetaFull)
|
||||
}
|
||||
else {
|
||||
self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999);
|
||||
@ -287,11 +263,11 @@ impl InventoryState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), anyhow::Error> {
|
||||
pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> {
|
||||
match &item.item {
|
||||
InventoryItemDetail::Individual(_) => {
|
||||
if self.inventory.0.len() >= 30 {
|
||||
Err(InventoryError::InventoryFull.into())
|
||||
Err(InventoryError::InventoryFull)
|
||||
}
|
||||
else {
|
||||
self.inventory.0.push(item);
|
||||
@ -314,7 +290,7 @@ impl InventoryState {
|
||||
match existing_stack {
|
||||
Some(existing_stack) => {
|
||||
if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() {
|
||||
Err(InventoryError::StackFull.into())
|
||||
Err(InventoryError::StackFull)
|
||||
}
|
||||
else {
|
||||
existing_stack.entity_ids.append(&mut sitem.entity_ids.clone());
|
||||
@ -331,7 +307,7 @@ impl InventoryState {
|
||||
},
|
||||
None => {
|
||||
if self.inventory.0.len() >= 30 {
|
||||
Err(InventoryError::InventoryFull.into())
|
||||
Err(InventoryError::InventoryFull)
|
||||
}
|
||||
else {
|
||||
self.inventory.0.push(item);
|
||||
@ -349,36 +325,7 @@ impl InventoryState {
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
pub 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)?;
|
||||
@ -398,8 +345,9 @@ impl InventoryState {
|
||||
}
|
||||
else {
|
||||
let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect();
|
||||
self.item_id_counter += 1;
|
||||
Some(InventoryItem {
|
||||
item_id: new_item_id(&self.item_id_counter).await,
|
||||
item_id: ClientItemId(self.item_id_counter),
|
||||
item: InventoryItemDetail::Stacked(StackedItemDetail {
|
||||
entity_ids,
|
||||
tool: stacked_item.tool,
|
||||
@ -422,25 +370,25 @@ impl InventoryState {
|
||||
.find(|i| i.item_id == *item_id)
|
||||
}
|
||||
|
||||
pub fn add_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||
pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
||||
if self.meseta.0 == 999999 {
|
||||
return Err(ItemStateError::FullOfMeseta.into())
|
||||
return Err(ItemStateError::FullOfMeseta)
|
||||
}
|
||||
self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||
pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
||||
if self.meseta.0 + amount > 999999 {
|
||||
return Err(ItemStateError::FullOfMeseta.into())
|
||||
return Err(ItemStateError::FullOfMeseta)
|
||||
}
|
||||
self.meseta.0 += amount;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), anyhow::Error> {
|
||||
pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> {
|
||||
if amount > self.meseta.0 {
|
||||
return Err(ItemStateError::InvalidMesetaRemoval(amount).into())
|
||||
return Err(ItemStateError::InvalidMesetaRemoval(amount))
|
||||
}
|
||||
self.meseta.0 -= amount;
|
||||
Ok(())
|
||||
@ -505,18 +453,6 @@ 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);
|
@ -34,40 +34,38 @@ where
|
||||
S: Send + Sync,
|
||||
E: Send + Sync,
|
||||
{
|
||||
pub fn act<'a, O, F, Fut>(self, f: F) -> ItemActionStage<'a, O, ItemStateAction<T, S, E>, F, Fut, S, E>
|
||||
pub fn act<O, F, Fut>(self, f: F) -> ItemActionStage<O, ItemStateAction<T, S, E>, F, Fut, S, E>
|
||||
where
|
||||
F: Fn(S, ()) -> Fut + Send + Sync + 'a,
|
||||
Fut: Future<Output=Result<(S, O), E>> + Send + 'a
|
||||
F: Fn(S, ()) -> Fut + Send + Sync,
|
||||
Fut: Future<Output=Result<(S, O), E>> + Send
|
||||
{
|
||||
ItemActionStage {
|
||||
_s: Default::default(),
|
||||
_e: std::marker::PhantomData,
|
||||
_a: Default::default(),
|
||||
prev: self,
|
||||
actionf: f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ItemActionStage<'a, O, P, F, Fut, S, E>
|
||||
pub struct ItemActionStage<O, P, F, Fut, S, E>
|
||||
where
|
||||
P: ItemAction,
|
||||
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
|
||||
Fut: Future<Output=Result<(S, O) , E>> + Send + 'a,
|
||||
F: Fn(S, P::Output) -> Fut + Send + Sync,
|
||||
Fut: Future<Output=Result<(S, O) , E>> + Send,
|
||||
{
|
||||
_s: std::marker::PhantomData<S>,
|
||||
_e: std::marker::PhantomData<E>,
|
||||
_a: std::marker::PhantomData<&'a ()>,
|
||||
prev: P,
|
||||
actionf: F,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a, O, P: ItemAction, F, Fut, S, E> ItemAction for ItemActionStage<'a, O, P, F, Fut, S, E>
|
||||
impl<O, P: ItemAction, F, Fut, S, E> ItemAction for ItemActionStage<O, P, F, Fut, S, E>
|
||||
where
|
||||
P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
|
||||
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
|
||||
Fut: Future<Output=Result<(S, O), E>> + Send + 'a,
|
||||
F: Fn(S, P::Output) -> Fut + Send + Sync,
|
||||
Fut: Future<Output=Result<(S, O), E>> + Send,
|
||||
S: Send + Sync,
|
||||
P::Output: Send + Sync,
|
||||
E: Send + Sync,
|
||||
@ -89,11 +87,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, O, P: ItemAction, F, Fut, S, E> ItemActionStage<'a, O, P, F, Fut, S, E>
|
||||
impl<O, P: ItemAction, F, Fut, S, E> ItemActionStage<O, P, F, Fut, S, E>
|
||||
where
|
||||
P: ItemAction<Start = S, Error = E> + Send + Sync,
|
||||
F: Fn(S, P::Output) -> Fut + Send + Sync + 'a,
|
||||
Fut: Future<Output=Result<(S, O), E>> + Send + 'a,
|
||||
F: Fn(S, P::Output) -> Fut + Send + Sync,
|
||||
Fut: Future<Output=Result<(S, O), E>> + Send,
|
||||
S: Send + Sync,
|
||||
P::Output: Send + Sync,
|
||||
E: Send + Sync,
|
||||
@ -101,17 +99,16 @@ where
|
||||
P::Error: Send + Sync,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
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>
|
||||
pub fn act<O2, G, GFut>(self, g: G) -> ItemActionStage<O2, ItemActionStage<O, P, F, Fut, S, E>, G, GFut, S, E>
|
||||
where
|
||||
S: Send + Sync,
|
||||
G: Fn(S, <ItemActionStage<'a, O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync + 'b,
|
||||
GFut: Future<Output=Result<(S, O2), E>> + Send + 'b,
|
||||
G: Fn(S, <ItemActionStage<O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync,
|
||||
GFut: Future<Output=Result<(S, O2), E>> + Send,
|
||||
O2: Send + Sync,
|
||||
{
|
||||
ItemActionStage {
|
||||
_s: Default::default(),
|
||||
_e: Default::default(),
|
||||
_a: Default::default(),
|
||||
prev: self,
|
||||
actionf: g,
|
||||
}
|
1380
src/ship/items/manager.rs
Normal file
1380
src/ship/items/manager.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user