right-side text support #135

Closed
andy wants to merge 58 commits from andy/right-text into master
  1. 16
      .drone.yml
  2. 2390
      Cargo.lock
  3. 68
      Cargo.toml
  4. 14
      src/bin/login.rs
  5. 52
      src/bin/main.rs
  6. 10
      src/bin/patch.rs
  7. 14
      src/bin/ship.rs
  8. 20
      src/client/Cargo.toml
  9. 27
      src/client/src/lib.rs
  10. 17
      src/drops/Cargo.toml
  11. 29
      src/drops/src/box_drop_table.rs
  12. 22
      src/drops/src/generic_armor.rs
  13. 18
      src/drops/src/generic_shield.rs
  14. 14
      src/drops/src/generic_unit.rs
  15. 18
      src/drops/src/generic_weapon.rs
  16. 94
      src/drops/src/lib.rs
  17. 28
      src/drops/src/rare_drop_table.rs
  18. 10
      src/drops/src/tech_table.rs
  19. 16
      src/drops/src/tool_table.rs
  20. 23
      src/entity/Cargo.toml
  21. 3
      src/entity/gateway/postgres/migrations/mod.rs
  22. 0
      src/entity/src/account.rs
  23. 4
      src/entity/src/character.rs
  24. 16
      src/entity/src/gateway/entitygateway.rs
  25. 26
      src/entity/src/gateway/inmemory.rs
  26. 0
      src/entity/src/gateway/mod.rs
  27. 0
      src/entity/src/gateway/postgres/migrations/V0001__initial.sql
  28. 0
      src/entity/src/gateway/postgres/migrations/V0002__equips.sql
  29. 0
      src/entity/src/gateway/postgres/migrations/V0003__item_notes.sql
  30. 0
      src/entity/src/gateway/postgres/migrations/V0004__meseta.sql
  31. 0
      src/entity/src/gateway/postgres/migrations/V0005__trade.sql
  32. 0
      src/entity/src/gateway/postgres/migrations/V0006__playtime.sql
  33. 0
      src/entity/src/gateway/postgres/migrations/V0007__player_keyconfig.sql
  34. 0
      src/entity/src/gateway/postgres/migrations/V0008__playtime_not_null.sql
  35. 0
      src/entity/src/gateway/postgres/migrations/V0009__no_player_keyconfig.sql
  36. 0
      src/entity/src/gateway/postgres/migrations/V0010__char_create_timestamp.sql
  37. 0
      src/entity/src/gateway/postgres/migrations/V0011__shared_bank.sql
  38. 14
      src/entity/src/gateway/postgres/migrations/V0012__room.sql
  39. 17
      src/entity/src/gateway/postgres/migrations/V0013__room2.sql
  40. 3
      src/entity/src/gateway/postgres/migrations/mod.rs
  41. 0
      src/entity/src/gateway/postgres/mod.rs
  42. 109
      src/entity/src/gateway/postgres/models.rs
  43. 117
      src/entity/src/gateway/postgres/postgres.rs
  44. 2
      src/entity/src/item/armor.rs
  45. 0
      src/entity/src/item/esweapon.rs
  46. 8
      src/entity/src/item/mag.rs
  47. 53
      src/entity/src/item/mod.rs
  48. 0
      src/entity/src/item/shield.rs
  49. 0
      src/entity/src/item/tech.rs
  50. 0
      src/entity/src/item/tool.rs
  51. 0
      src/entity/src/item/unit.rs
  52. 2
      src/entity/src/item/weapon.rs
  53. 1
      src/entity/src/lib.rs
  54. 83
      src/entity/src/room.rs
  55. 31
      src/entity/src/team.rs
  56. 25
      src/items/Cargo.toml
  57. 180
      src/items/src/actions.rs
  58. 20
      src/items/src/apply_item.rs
  59. 30
      src/items/src/bank.rs
  60. 20
      src/items/src/floor.rs
  61. 20
      src/items/src/inventory.rs
  62. 0
      src/items/src/itemstateaction.rs
  63. 3
      src/items/src/lib.rs
  64. 26
      src/items/src/state.rs
  65. 94
      src/items/src/tasks.rs
  66. 38
      src/items/src/trade.rs
  67. 17
      src/lib.rs
  68. 12
      src/location/Cargo.toml
  69. 2
      src/location/src/lib.rs
  70. 3
      src/login/mod.rs
  71. 92
      src/login/models.rs
  72. 21
      src/login_server/Cargo.toml
  73. 59
      src/login_server/src/character.rs
  74. 2
      src/login_server/src/lib.rs
  75. 45
      src/login_server/src/login.rs
  76. 14
      src/maps/Cargo.toml
  77. 2
      src/maps/src/area.rs
  78. 37
      src/maps/src/enemy.rs
  79. 59
      src/maps/src/lib.rs
  80. 28
      src/maps/src/maps.rs
  81. 4
      src/maps/src/monster.rs
  82. 8
      src/maps/src/object.rs
  83. 150
      src/maps/src/room.rs
  84. 3
      src/maps/src/variant.rs
  85. 18
      src/networking/Cargo.toml
  86. 0
      src/networking/src/cipherkeys.rs
  87. 4
      src/networking/src/interserver.rs
  88. 1
      src/networking/src/lib.rs
  89. 6
      src/networking/src/mainloop/client.rs
  90. 20
      src/networking/src/mainloop/interserver.rs
  91. 0
      src/networking/src/mainloop/mod.rs
  92. 0
      src/networking/src/serverstate.rs
  93. 2
      src/patch/mod.rs
  94. 15
      src/patch_server/Cargo.toml
  95. 4
      src/patch_server/src/lib.rs
  96. 22
      src/pktbuilder/Cargo.toml
  97. 11
      src/pktbuilder/src/character.rs
  98. 11
      src/pktbuilder/src/lib.rs
  99. 15
      src/pktbuilder/src/lobby.rs
  100. 106
      src/pktbuilder/src/message.rs

16
.drone.yml

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

2390
Cargo.lock
File diff suppressed because it is too large
View File

68
Cargo.toml

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

14
src/bin/login.rs

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

52
src/bin/main.rs

@ -1,18 +1,18 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use log::{info}; use log::{info};
use elseware::common::interserver::AuthToken;
use elseware::login::login::LoginServerState;
use elseware::login::character::CharacterServerState;
use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd};
use elseware::ship::ship::{ShipServerStateBuilder, ShipEvent};
use networking::interserver::AuthToken;
use login_server::login::LoginServerState;
use login_server::character::CharacterServerState;
use patch_server::{PatchServerState, generate_patch_tree, load_config, load_motd};
use ship_server::ShipServerStateBuilder;
#[allow(unused_imports)]
use elseware::entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
use elseware::entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
use elseware::entity::character::NewCharacterEntity;
use elseware::entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
use elseware::entity::item;
use maps::Holiday;
use entity::gateway::{EntityGateway, InMemoryGateway};
use entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
use entity::character::NewCharacterEntity;
use entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
use entity::item;
fn setup_logger() { fn setup_logger() {
let colors = fern::colors::ColoredLevelConfig::new() let colors = fern::colors::ColoredLevelConfig::new()
@ -338,73 +338,73 @@ fn main() {
let (patch_file_tree, patch_file_lookup) = generate_patch_tree(patch_config.path.as_str()); let (patch_file_tree, patch_file_lookup) = generate_patch_tree(patch_config.path.as_str());
let patch_state = PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd); let patch_state = PatchServerState::new(patch_file_tree, patch_file_lookup, patch_motd);
let patch_loop = async_std::task::spawn(async move { let patch_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(patch_state, patch_config.port).await;
networking::mainloop::run_server(patch_state, patch_config.port).await;
}); });
info!("[auth] starting server"); info!("[auth] starting server");
let login_state = LoginServerState::new(entity_gateway.clone(), "127.0.0.1".parse().unwrap()); let login_state = LoginServerState::new(entity_gateway.clone(), "127.0.0.1".parse().unwrap());
let login_loop = async_std::task::spawn(async move { let login_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(login_state, elseware::login::login::LOGIN_PORT).await;
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await;
}); });
info!("[character] starting server"); info!("[character] starting server");
let char_state = CharacterServerState::new(entity_gateway.clone(), AuthToken("".into())); let char_state = CharacterServerState::new(entity_gateway.clone(), AuthToken("".into()));
let sub_char_state = char_state.clone(); let sub_char_state = char_state.clone();
let character_loop = async_std::task::spawn(async move { let character_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await;
}); });
let sub_char_state = char_state.clone(); let sub_char_state = char_state.clone();
let inter_character_loop = async_std::task::spawn(async move { let inter_character_loop = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_listen(sub_char_state, elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_listen(sub_char_state, login_server::login::COMMUNICATION_PORT).await;
}); });
info!("[ship] starting servers"); info!("[ship] starting servers");
let ship_state = ShipServerStateBuilder::default() let ship_state = ShipServerStateBuilder::default()
.name("US/Sona-Nyl".into()) .name("US/Sona-Nyl".into())
.ip(Ipv4Addr::new(127,0,0,1)) .ip(Ipv4Addr::new(127,0,0,1))
.port(elseware::ship::ship::SHIP_PORT)
.event(ShipEvent::Halloween)
.port(ship_server::SHIP_PORT)
.event(Holiday::Halloween)
.gateway(entity_gateway.clone()) .gateway(entity_gateway.clone())
.build(); .build();
let sub_ship_state = ship_state.clone(); let sub_ship_state = ship_state.clone();
let ship_loop1 = async_std::task::spawn(async move { let ship_loop1 = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await;
}); });
let sub_ship_state = ship_state.clone(); let sub_ship_state = ship_state.clone();
let inter_ship_loop1 = async_std::task::spawn(async move { let inter_ship_loop1 = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
}); });
let ship_state = ShipServerStateBuilder::default() let ship_state = ShipServerStateBuilder::default()
.name("EU/Dylath-Leen".into()) .name("EU/Dylath-Leen".into())
.ip(Ipv4Addr::new(127,0,0,1)) .ip(Ipv4Addr::new(127,0,0,1))
.port(elseware::ship::ship::SHIP_PORT+2000)
.event(ShipEvent::Christmas)
.port(ship_server::SHIP_PORT+2000)
.event(Holiday::Christmas)
.gateway(entity_gateway.clone()) .gateway(entity_gateway.clone())
.build(); .build();
let sub_ship_state = ship_state.clone(); let sub_ship_state = ship_state.clone();
let ship_loop2 = async_std::task::spawn(async move { let ship_loop2 = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+2000).await;
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+2000).await;
}); });
let sub_ship_state = ship_state.clone(); let sub_ship_state = ship_state.clone();
let inter_ship_loop2 = async_std::task::spawn(async move { let inter_ship_loop2 = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
}); });
let ship_state = ShipServerStateBuilder::default() let ship_state = ShipServerStateBuilder::default()
.name("JP/Thalarion".into()) .name("JP/Thalarion".into())
.ip(Ipv4Addr::new(127,0,0,1)) .ip(Ipv4Addr::new(127,0,0,1))
.port(elseware::ship::ship::SHIP_PORT+3000)
.port(ship_server::SHIP_PORT+3000)
.gateway(entity_gateway.clone()) .gateway(entity_gateway.clone())
.build(); .build();
let sub_ship_state = ship_state.clone(); let sub_ship_state = ship_state.clone();
let ship_loop3 = async_std::task::spawn(async move { let ship_loop3 = async_std::task::spawn(async move {
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+3000).await;
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+3000).await;
}); });
let sub_ship_state = ship_state.clone(); let sub_ship_state = ship_state.clone();
let inter_ship_loop3 = async_std::task::spawn(async move { let inter_ship_loop3 = async_std::task::spawn(async move {
elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await;
}); });
futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop, futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop,

10
src/bin/patch.rs

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

14
src/bin/ship.rs

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

20
src/client/Cargo.toml

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

27
src/ship/client.rs → src/client/src/lib.rs

@ -6,15 +6,20 @@ use futures::future::BoxFuture;
use libpso::packet::ship::*; use libpso::packet::ship::*;
use libpso::packet::login::Session; use libpso::packet::login::Session;
use crate::common::serverstate::ClientId;
use crate::entity::account::{UserAccountEntity, UserSettingsEntity};
use crate::entity::character::CharacterEntity;
use crate::entity::item;
use networking::serverstate::ClientId;
use entity::account::{UserAccountEntity, UserSettingsEntity};
use entity::character::CharacterEntity;
use entity::item;
use crate::ship::ship::ShipError;
use crate::ship::items;
use crate::ship::map::MapArea;
use crate::ship::shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
use maps::area::MapArea;
use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("not found {0}")]
NotFound(ClientId),
}
#[derive(Clone, Default)] #[derive(Clone, Default)]
@ -46,7 +51,7 @@ impl Clients {
.await; .await;
let client = clients let client = clients
.get(&client_id) .get(&client_id)
.ok_or_else(|| ShipError::ClientNotFound(client_id))?
.ok_or(ClientError::NotFound(client_id))?
.read() .read()
.await; .await;
@ -69,7 +74,7 @@ impl Clients {
for (cindex, client_id) in client_ids.iter().enumerate() { for (cindex, client_id) in client_ids.iter().enumerate() {
let c = clients let c = clients
.get(client_id) .get(client_id)
.ok_or_else(|| ShipError::ClientNotFound(*client_id))?
.ok_or(ClientError::NotFound(*client_id))?
.read() .read()
.await; .await;
client_states[cindex].write(c); client_states[cindex].write(c);
@ -95,7 +100,7 @@ impl Clients {
.await; .await;
let mut client = clients let mut client = clients
.get(&client_id) .get(&client_id)
.ok_or_else(|| ShipError::ClientNotFound(client_id))?
.ok_or(ClientError::NotFound(client_id))?
.write() .write()
.await; .await;

17
src/drops/Cargo.toml

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

29
src/ship/drops/box_drop_table.rs → src/drops/src/box_drop_table.rs

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

22
src/ship/drops/generic_armor.rs → src/drops/src/generic_armor.rs

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

18
src/ship/drops/generic_shield.rs → src/drops/src/generic_shield.rs

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

14
src/ship/drops/generic_unit.rs → src/drops/src/generic_unit.rs

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

18
src/ship/drops/generic_weapon.rs → src/drops/src/generic_weapon.rs

@ -1,14 +1,14 @@
use std::collections::{HashMap, BTreeMap}; use std::collections::{HashMap, BTreeMap};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use rand::{Rng};
use rand::Rng;
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use crate::entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::{ItemDropType, load_data_file};
use entity::character::SectionID;
use entity::item::weapon::{Weapon, WeaponType, Attribute, WeaponAttribute, WeaponSpecial};
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
@ -240,7 +240,7 @@ impl AttributeTable {
fn generate_attribute<R: Rng>(&self, pattern: &PercentPatternType, rates: &AttributeRate, rng: &mut R) -> Option<WeaponAttribute> { fn generate_attribute<R: Rng>(&self, pattern: &PercentPatternType, rates: &AttributeRate, rng: &mut R) -> Option<WeaponAttribute> {
let attribute_weights = WeightedIndex::new(&[rates.none, rates.native, rates.abeast, rates.machine, rates.dark, rates.hit]).unwrap();
let attribute_weights = WeightedIndex::new([rates.none, rates.native, rates.abeast, rates.machine, rates.dark, rates.hit]).unwrap();
let attr = match attribute_weights.sample(rng) { let attr = match attribute_weights.sample(rng) {
0 => return None, 0 => return None,
1 => Attribute::Native, 1 => Attribute::Native,
@ -253,7 +253,7 @@ impl AttributeTable {
let percents = self.percent_rates.get_by_pattern(pattern); let percents = self.percent_rates.get_by_pattern(pattern);
let value_weights = WeightedIndex::new(&percents.as_array()).unwrap();
let value_weights = WeightedIndex::new(percents.as_array()).unwrap();
let value = value_weights.sample(rng); let value = value_weights.sample(rng);
let percent = ((value + 1) * 5) as i8; let percent = ((value + 1) * 5) as i8;
@ -477,7 +477,7 @@ impl GenericWeaponTable {
let pattern = std::cmp::min(area % ratio.inc, 3); let pattern = std::cmp::min(area % ratio.inc, 3);
let weights = self.grind_rates.grind_rate[pattern as usize]; let weights = self.grind_rates.grind_rate[pattern as usize];
let grind_choice = WeightedIndex::new(&weights).unwrap();
let grind_choice = WeightedIndex::new(weights).unwrap();
grind_choice.sample(rng) grind_choice.sample(rng)
} }

94
src/ship/drops/mod.rs → src/drops/src/lib.rs

@ -5,7 +5,6 @@
// to their drops // to their drops
mod drop_table;
pub mod rare_drop_table; pub mod rare_drop_table;
mod generic_weapon; mod generic_weapon;
mod generic_armor; mod generic_armor;
@ -22,19 +21,19 @@ use std::io::Read;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use rand::{Rng, SeedableRng}; 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};
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 { fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
@ -55,18 +54,6 @@ pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficul
toml::from_str::<T>(s.as_str()).unwrap() 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)] #[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub enum MonsterDropType { pub enum MonsterDropType {
#[serde(rename = "weapon")] #[serde(rename = "weapon")]
@ -102,6 +89,28 @@ pub enum ItemDropType {
Meseta(u32), 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)] #[derive(Clone, Debug)]
pub struct ItemDrop { pub struct ItemDrop {
pub map_area: MapArea, pub map_area: MapArea,
@ -112,7 +121,12 @@ pub struct ItemDrop {
} }
pub struct DropTable {
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>, monster_stats: HashMap<MonsterType, MonsterDropStats>,
rare_table: RareDropTable, rare_table: RareDropTable,
weapon_table: GenericWeaponTable, weapon_table: GenericWeaponTable,
@ -124,11 +138,12 @@ pub struct DropTable {
rng: rand_chacha::ChaCha20Rng, rng: rand_chacha::ChaCha20Rng,
} }
impl DropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
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"); let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
DropTable {
Box::new(StandardDropTable {
monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(), monster_stats: monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect(),
rare_table: RareDropTable::new(episode, difficulty, section_id), rare_table: RareDropTable::new(episode, difficulty, section_id),
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id), weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
@ -138,7 +153,7 @@ impl DropTable {
tool_table: ToolTable::new(episode, difficulty, section_id), tool_table: ToolTable::new(episode, difficulty, section_id),
box_table: BoxDropTable::new(episode, difficulty, section_id), box_table: BoxDropTable::new(episode, difficulty, section_id),
rng: rand_chacha::ChaCha20Rng::from_entropy(), rng: rand_chacha::ChaCha20Rng::from_entropy(),
}
})
} }
pub fn builder() -> DropTableBuilder { pub fn builder() -> DropTableBuilder {
@ -168,8 +183,10 @@ impl DropTable {
MonsterDropType::None => None, MonsterDropType::None => None,
} }
} }
}
pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
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 monster_stat = *self.monster_stats.get(monster)?;
let drop_anything = self.rng.gen_range(0, 100); let drop_anything = self.rng.gen_range(0, 100);
@ -182,7 +199,7 @@ impl DropTable {
} }
let drop_type = self.rng.gen_range(0, 3); let drop_type = self.rng.gen_range(0, 3);
match drop_type { match drop_type {
0 => { 0 => {
self.generate_meseta(&monster_stat) self.generate_meseta(&monster_stat)
@ -197,7 +214,7 @@ impl DropTable {
} }
} }
pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
self.box_table.get_drop(map_area, object, &mut self.rng) self.box_table.get_drop(map_area, object, &mut self.rng)
} }
} }
@ -244,8 +261,8 @@ impl DropTableBuilder {
self self
} }
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
DropTable {
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(|| { monster_stats: self.monster_stats.unwrap_or_else(|| {
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); 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() monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect()
@ -258,11 +275,10 @@ impl DropTableBuilder {
tool_table: self.tool_table.unwrap_or_else(|| ToolTable::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)), 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), rng: self.rng.unwrap_or_else(rand_chacha::ChaCha20Rng::from_entropy),
}
})
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

28
src/ship/drops/rare_drop_table.rs → src/drops/src/rare_drop_table.rs

@ -1,20 +1,20 @@
use std::collections::HashMap; use std::collections::HashMap;
use rand::Rng; use rand::Rng;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::entity::item::weapon::{Weapon, WeaponType};
use crate::entity::item::armor::{Armor, ArmorType};
use crate::entity::item::shield::{Shield, ShieldType};
use crate::entity::item::unit::{Unit, UnitType};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::mag::{Mag, MagType};
use crate::entity::character::SectionID;
use crate::ship::monster::MonsterType;
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::ship::drops::{ItemDropType, load_data_file};
use crate::ship::drops::generic_weapon::AttributeTable;
use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::ship::drops::generic_shield::GenericShieldTable;
use entity::item::weapon::{Weapon, WeaponType};
use entity::item::armor::{Armor, ArmorType};
use entity::item::shield::{Shield, ShieldType};
use entity::item::unit::{Unit, UnitType};
use entity::item::tool::{Tool, ToolType};
use entity::item::mag::{Mag, MagType};
use entity::character::SectionID;
use maps::monster::MonsterType;
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use crate::{ItemDropType, load_data_file};
use crate::generic_weapon::AttributeTable;
use crate::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable;
type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>; type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>;

10
src/ship/drops/tech_table.rs → src/drops/src/tech_table.rs

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

16
src/ship/drops/tool_table.rs → src/drops/src/tool_table.rs

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

23
src/entity/Cargo.toml

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

3
src/entity/gateway/postgres/migrations/mod.rs

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

0
src/entity/account.rs → src/entity/src/account.rs

4
src/entity/character.rs → src/entity/src/character.rs

@ -4,8 +4,8 @@ use serde::{Serialize, Deserialize};
use libpso::packet::ship::{UpdateConfig, WriteInfoboard}; use libpso::packet::ship::{UpdateConfig, WriteInfoboard};
use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU}; use libpso::character::settings::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU};
use crate::entity::item::tech::Technique;
use crate::entity::account::UserAccountId;
use crate::item::tech::Technique;
use crate::account::UserAccountId;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize, Default)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize, Default)]
pub enum CharacterClass { pub enum CharacterClass {

16
src/entity/gateway/entitygateway.rs → src/entity/src/gateway/entitygateway.rs

@ -1,13 +1,13 @@
use thiserror::Error; use thiserror::Error;
use futures::future::{Future, BoxFuture}; use futures::future::{Future, BoxFuture};
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::account::*;
use crate::character::*;
use crate::item::*;
use crate::room::*;
// TODO: better granularity? // TODO: better granularity?
//#[derive(Error, Debug)]
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum GatewayError { pub enum GatewayError {
#[error("unknown error")] #[error("unknown error")]
@ -147,6 +147,14 @@ pub trait EntityGateway: Send + Sync {
async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> { async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> {
unimplemented!(); unimplemented!();
} }
async fn create_room(&mut self, _room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
unimplemented!();
}
async fn add_room_note(&mut self, _room_id: RoomEntityId, _note: RoomNote) -> Result<(), GatewayError> {
unimplemented!();
}
} }

26
src/entity/gateway/inmemory.rs → src/entity/src/gateway/inmemory.rs

@ -2,10 +2,11 @@ use std::collections::BTreeMap;
use std::convert::TryInto; use std::convert::TryInto;
use futures::future::{Future, BoxFuture}; use futures::future::{Future, BoxFuture};
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
@ -766,4 +767,21 @@ impl EntityGateway for InMemoryGateway {
Err(GatewayError::Error) Err(GatewayError::Error)
} }
} }
// I do not care to replicate this in testing
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
Ok(RoomEntity {
id: RoomEntityId(0),
name: room.name,
section_id: room.section_id,
episode: room.episode,
difficulty: room.difficulty,
mode: room.mode,
})
}
// I do not care to replicate this in testing
async fn add_room_note(&mut self, _room_id: RoomEntityId, _note: RoomNote) -> Result<(), GatewayError> {
Ok(())
}
} }

0
src/entity/gateway/mod.rs → src/entity/src/gateway/mod.rs

0
src/entity/gateway/postgres/migrations/V0001__initial.sql → src/entity/src/gateway/postgres/migrations/V0001__initial.sql

0
src/entity/gateway/postgres/migrations/V0002__equips.sql → src/entity/src/gateway/postgres/migrations/V0002__equips.sql

0
src/entity/gateway/postgres/migrations/V0003__item_notes.sql → src/entity/src/gateway/postgres/migrations/V0003__item_notes.sql

0
src/entity/gateway/postgres/migrations/V0004__meseta.sql → src/entity/src/gateway/postgres/migrations/V0004__meseta.sql

0
src/entity/gateway/postgres/migrations/V0005__trade.sql → src/entity/src/gateway/postgres/migrations/V0005__trade.sql

0
src/entity/gateway/postgres/migrations/V0006__playtime.sql → src/entity/src/gateway/postgres/migrations/V0006__playtime.sql

0
src/entity/gateway/postgres/migrations/V0007__player_keyconfig.sql → src/entity/src/gateway/postgres/migrations/V0007__player_keyconfig.sql

0
src/entity/gateway/postgres/migrations/V0008__playtime_not_null.sql → src/entity/src/gateway/postgres/migrations/V0008__playtime_not_null.sql

0
src/entity/gateway/postgres/migrations/V0009__no_player_keyconfig.sql → src/entity/src/gateway/postgres/migrations/V0009__no_player_keyconfig.sql

0
src/entity/gateway/postgres/migrations/V0010__char_create_timestamp.sql → src/entity/src/gateway/postgres/migrations/V0010__char_create_timestamp.sql

0
src/entity/gateway/postgres/migrations/V0011__shared_bank.sql → src/entity/src/gateway/postgres/migrations/V0011__shared_bank.sql

14
src/entity/src/gateway/postgres/migrations/V0012__room.sql

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

17
src/entity/src/gateway/postgres/migrations/V0013__room2.sql

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

3
src/entity/src/gateway/postgres/migrations/mod.rs

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

0
src/entity/gateway/postgres/mod.rs → src/entity/src/gateway/postgres/mod.rs

109
src/entity/gateway/postgres/models.rs → src/entity/src/gateway/postgres/models.rs

@ -4,10 +4,13 @@ use std::convert::Into;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use libpso::character::settings; use libpso::character::settings;
use libpso::util::vec_to_array; use libpso::util::vec_to_array;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::ship::map::MapArea;
use crate::account::*;
use crate::character::*;
use crate::item::*;
use crate::room::*;
use maps::area::MapArea;
use maps::room::{Episode, Difficulty};
use maps::monster::MonsterType;
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount { pub struct PgUserAccount {
@ -577,6 +580,16 @@ pub enum PgItemNoteDetail {
}, },
EnemyDrop { EnemyDrop {
character_id: u32, character_id: u32,
room_id: u32,
monster_type: MonsterType,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: u32,
room_id: u32,
map_area: MapArea, map_area: MapArea,
x: f32, x: f32,
y: f32, y: f32,
@ -592,14 +605,19 @@ pub enum PgItemNoteDetail {
y: f32, y: f32,
z: f32, z: f32,
}, },
Consumed,
Consumed {
character_id: u32,
},
FedToMag { FedToMag {
character_id: u32,
mag: u32, mag: u32,
}, },
BoughtAtShop { BoughtAtShop {
character_id: u32, character_id: u32,
}, },
SoldToShop,
SoldToShop {
character_id: u32,
},
Trade { Trade {
trade_id: u32, trade_id: u32,
character_to: u32, character_to: u32,
@ -612,6 +630,9 @@ pub enum PgItemNoteDetail {
Deposit { Deposit {
character_id: u32, character_id: u32,
bank: BankIdentifier, bank: BankIdentifier,
},
FloorLimitReached {
map_area: MapArea,
} }
} }
@ -621,8 +642,16 @@ impl From<ItemNote> for PgItemNoteDetail {
ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation { ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation {
character_id: character_id.0, character_id: character_id.0,
}, },
ItemNote::EnemyDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
ItemNote::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
character_id: character_id.0,
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, character_id: character_id.0,
room_id: room_id.0,
map_area, map_area,
x,y,z, x,y,z,
}, },
@ -634,14 +663,19 @@ impl From<ItemNote> for PgItemNoteDetail {
map_area, map_area,
x,y,z, x,y,z,
}, },
ItemNote::Consumed => PgItemNoteDetail::Consumed,
ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed {
character_id: character_id.0,
},
ItemNote::FedToMag{character_id, mag} => PgItemNoteDetail::FedToMag{
character_id: character_id.0,
mag: mag.0 mag: mag.0
}, },
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop { ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
character_id: character_id.0, character_id: character_id.0,
}, },
ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop,
ItemNote::SoldToShop{character_id} => PgItemNoteDetail::SoldToShop {
character_id: character_id.0,
},
ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade { ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade {
trade_id: trade_id.0, trade_id: trade_id.0,
character_to: character_to.0, character_to: character_to.0,
@ -658,7 +692,12 @@ impl From<ItemNote> for PgItemNoteDetail {
character_id: character_id.0, character_id: character_id.0,
bank, bank,
} }
}
},
ItemNote::FloorLimitReached { map_area } => {
PgItemNoteDetail::FloorLimitReached {
map_area,
}
},
} }
} }
} }
@ -669,8 +708,16 @@ impl From<PgItemNoteDetail> for ItemNote {
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation { PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
}, },
PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
PgItemNoteDetail::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => ItemNote::EnemyDrop {
character_id: CharacterEntityId(character_id),
room_id: RoomEntityId(room_id),
monster_type,
map_area,
x,y,z,
},
PgItemNoteDetail::BoxDrop{character_id, room_id, map_area, x, y, z} => ItemNote::BoxDrop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
room_id: RoomEntityId(room_id),
map_area, map_area,
x,y,z, x,y,z,
}, },
@ -682,14 +729,19 @@ impl From<PgItemNoteDetail> for ItemNote {
map_area, map_area,
x,y,z, x,y,z,
}, },
PgItemNoteDetail::Consumed => ItemNote::Consumed,
PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::FedToMag{character_id, mag} => ItemNote::FedToMag{
character_id: CharacterEntityId(character_id),
mag: ItemEntityId(mag) mag: ItemEntityId(mag)
}, },
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop { PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
}, },
PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop,
PgItemNoteDetail::SoldToShop{character_id} => ItemNote::SoldToShop {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade { PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade {
trade_id: TradeId(trade_id), trade_id: TradeId(trade_id),
character_to: CharacterEntityId(character_to), character_to: CharacterEntityId(character_to),
@ -703,6 +755,9 @@ impl From<PgItemNoteDetail> for ItemNote {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
bank, bank,
}, },
PgItemNoteDetail::FloorLimitReached { map_area } => ItemNote::FloorLimitReached {
map_area,
},
} }
} }
} }
@ -869,3 +924,27 @@ impl From<PgTradeEntity> for TradeEntity {
} }
} }
} }
#[derive(Debug, sqlx::FromRow, Serialize)]
pub struct PgRoomEntity {
id: i32,
name: String,
section_id: i8,
mode: i8,
episode: i8,
difficulty: i8,
}
impl From<PgRoomEntity> for RoomEntity {
fn from(other: PgRoomEntity) -> RoomEntity {
RoomEntity {
id: RoomEntityId(other.id as u32),
name: other.name,
section_id: SectionID::from(other.section_id as u8),
mode: RoomEntityMode::from(other.mode as u8),
episode: Episode::try_from(other.episode as u8).unwrap(),
difficulty: Difficulty::try_from(other.difficulty as u8).unwrap(),
}
}
}

117
src/entity/gateway/postgres/postgres.rs → src/entity/src/gateway/postgres/postgres.rs

@ -1,15 +1,16 @@
// this lint is currently bugged and suggests incorrect code https://github.com/rust-lang/rust-clippy/issues/9123 // this lint is currently bugged and suggests incorrect code https://github.com/rust-lang/rust-clippy/issues/9123
#![allow(clippy::explicit_auto_deref)] #![allow(clippy::explicit_auto_deref)]
use std::convert::{From, TryFrom, Into};
use std::convert::{From, Into};
use futures::future::{Future, BoxFuture}; use futures::future::{Future, BoxFuture};
use futures::stream::{StreamExt, FuturesOrdered}; use futures::stream::{StreamExt, FuturesOrdered};
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
use libpso::character::guildcard; use libpso::character::guildcard;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use super::models::*; use super::models::*;
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
@ -18,7 +19,7 @@ use sqlx::Connection;
mod embedded { mod embedded {
use refinery::embed_migrations; use refinery::embed_migrations;
embed_migrations!("src/entity/gateway/postgres/migrations");
embed_migrations!("src/gateway/postgres/migrations");
} }
@ -178,7 +179,7 @@ async fn create_user(conn: &mut sqlx::PgConnection, user: NewUserAccountEntity)
async fn get_user_by_id(conn: &mut sqlx::PgConnection, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> async fn get_user_by_id(conn: &mut sqlx::PgConnection, id: UserAccountId) -> Result<UserAccountEntity, GatewayError>
{ {
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1") let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1")
.bind(id.0)
.bind(id.0 as i32)
.fetch_one(conn).await?; .fetch_one(conn).await?;
Ok(user.into()) Ok(user.into())
} }
@ -199,8 +200,8 @@ async fn save_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> R
.bind(&user.password) .bind(&user.password)
.bind(user.banned_until) .bind(user.banned_until)
.bind(user.muted_until) .bind(user.muted_until)
.bind(user.flags)
.bind(user.id.0)
.bind(user.flags as i32)
.bind(user.id.0 as i32)
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
} }
@ -209,7 +210,7 @@ async fn create_user_settings(conn: &mut sqlx::PgConnection, settings: NewUserSe
{ {
let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name) let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name)
values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;") values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;")
.bind(settings.user_id.0)
.bind(settings.user_id.0 as i32)
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.settings.keyboard_config.to_vec()) .bind(settings.settings.keyboard_config.to_vec())
.bind(settings.settings.gamepad_config.to_vec()) .bind(settings.settings.gamepad_config.to_vec())
@ -224,7 +225,7 @@ async fn create_user_settings(conn: &mut sqlx::PgConnection, settings: NewUserSe
async fn get_user_settings_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> async fn get_user_settings_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError>
{ {
let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1") let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1")
.bind(user.id.0)
.bind(user.id.0 as i32)
.fetch_one(conn).await?; .fetch_one(conn).await?;
Ok(settings.into()) Ok(settings.into())
} }
@ -235,11 +236,11 @@ async fn save_user_settings(conn: &mut sqlx::PgConnection, settings: &UserSettin
.bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(&settings.settings.keyboard_config.to_vec()) .bind(&settings.settings.keyboard_config.to_vec())
.bind(&settings.settings.gamepad_config.to_vec()) .bind(&settings.settings.gamepad_config.to_vec())
.bind(settings.settings.option_flags)
.bind(settings.settings.option_flags as i32)
.bind(&settings.settings.shortcuts.to_vec()) .bind(&settings.settings.shortcuts.to_vec())
.bind(&settings.settings.symbol_chats.to_vec()) .bind(&settings.settings.symbol_chats.to_vec())
.bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>()) .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.id.0)
.bind(settings.id.0 as i32)
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
} }
@ -262,7 +263,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
$26, $27, $28, $29, $30) $26, $27, $28, $29, $30)
returning *;"#; returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q) let character = sqlx::query_as::<_, PgCharacter>(q)
.bind(char.user_id.0)
.bind(char.user_id.0 as i32)
.bind(char.slot as i16) .bind(char.slot as i16)
.bind(char.name) .bind(char.name)
.bind(char.exp as i32) .bind(char.exp as i32)
@ -300,7 +301,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError>
{ {
let stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by created_at;") let stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by created_at;")
.bind(user.id.0)
.bind(user.id.0 as i32)
.fetch(conn); .fetch(conn);
Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| async move { Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| async move {
@ -320,7 +321,7 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29, playtime=$30 evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29, playtime=$30
where id=$31;"#; where id=$31;"#;
sqlx::query(q) sqlx::query(q)
.bind(char.user_id.0) // $1
.bind(char.user_id.0 as i32) // $1
.bind(char.slot as i16) // $2 .bind(char.slot as i16) // $2
.bind(&char.name) // $3 .bind(&char.name) // $3
.bind(char.exp as i32) // $4 .bind(char.exp as i32) // $4
@ -370,7 +371,7 @@ async fn create_item(conn: &mut sqlx::PgConnection, item: NewItemEntity) -> Resu
async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError>
{ {
sqlx::query("insert into item_note(item, note) values ($1, $2)") sqlx::query("insert into item_note(item, note) values ($1, $2)")
.bind(item_id.0)
.bind(item_id.0 as i32)
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note))) .bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -379,7 +380,7 @@ async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, it
async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError>
{ {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(mag_item_id.0 as i32)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id}))) .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id})))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -388,7 +389,7 @@ async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, too
async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError>
{ {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(mag_item_id.0 as i32)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id)))) .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id))))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -397,7 +398,7 @@ async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntit
async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError>
{ {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(mag_item_id.0 as i32)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id)))) .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id))))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -406,7 +407,7 @@ async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId,
async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError> async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: &weapon::WeaponModifier) -> Result<(), GatewayError>
{ {
sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);") sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);")
.bind(item_id.0)
.bind(item_id.0 as i32)
.bind(sqlx::types::Json(modifier)) .bind(sqlx::types::Json(modifier))
.execute(conn).await?; .execute(conn).await?;
Ok(()) Ok(())
@ -416,7 +417,7 @@ async fn get_character_inventory(conn: &mut sqlx::PgConnection, char_id: &Charac
{ {
let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit let conn = Arc::new(Mutex::new(conn.begin().await?)); // this is some degen shit
let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1") let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(&mut **conn.lock().await).await?; .fetch_one(&mut **conn.lock().await).await?;
Ok(InventoryEntity::new( Ok(InventoryEntity::new(
@ -441,14 +442,14 @@ async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
let bank = match bank_identifier { let bank = match bank_identifier {
BankIdentifier::Character => { BankIdentifier::Character => {
sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1") sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(&mut **conn.lock().await).await? .fetch_one(&mut **conn.lock().await).await?
}, },
BankIdentifier::Shared(bank_name) => { BankIdentifier::Shared(bank_name) => {
sqlx::query_as::<_, PgInventoryEntity>("select player_character.id as pchar, shared_bank.items as items from shared_bank 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 join player_character on shared_bank.user_account = player_character.user_account
where player_character.id = $1 and shared_bank.name = $2") where player_character.id = $1 and shared_bank.name = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(&bank_name.0) .bind(&bank_name.0)
.fetch_optional(&mut **conn.lock().await) .fetch_optional(&mut **conn.lock().await)
.await? .await?
@ -491,7 +492,7 @@ async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &Charac
.collect::<Vec<_>>(); .collect::<Vec<_>>();
sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2") sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(inventory)) .bind(sqlx::types::Json(inventory))
.execute(conn) .execute(conn)
.await?; .await?;
@ -516,7 +517,7 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
match bank_identifier { match bank_identifier {
BankIdentifier::Character => { BankIdentifier::Character => {
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, '') on conflict (pchar, name) do update set items = $2") sqlx::query("insert into bank (pchar, items, name) values ($1, $2, '') on conflict (pchar, name) do update set items = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(bank)) .bind(sqlx::types::Json(bank))
.execute(conn) .execute(conn)
.await?; .await?;
@ -526,7 +527,7 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
select player_character.user_account, $2, $3 from player_character select player_character.user_account, $2, $3 from player_character
where player_character.id = $1 where player_character.id = $1
on conflict (user_account, name) do update set items = $2;") on conflict (user_account, name) do update set items = $2;")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(bank)) .bind(sqlx::types::Json(bank))
.bind(&bank_name.0) .bind(&bank_name.0)
.execute(conn) .execute(conn)
@ -539,7 +540,7 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
async fn get_character_equips(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> async fn get_character_equips(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError>
{ {
let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1") let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
@ -550,7 +551,7 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character
{ {
sqlx::query(r#"insert into equipped (pchar, weapon, armor, shield, unit0, unit1, unit2, unit3, mag) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) sqlx::query(r#"insert into equipped (pchar, weapon, armor, shield, unit0, unit1, unit2, unit3, mag) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
on conflict (pchar) do update set weapon=$2, armor=$3, shield=$4, unit0=$5, unit1=$6, unit2=$7, unit3=$8, mag=$9"#) on conflict (pchar) do update set weapon=$2, armor=$3, shield=$4, unit0=$5, unit1=$6, unit2=$7, unit3=$8, mag=$9"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(equips.weapon.map(|i| i.0 as i32)) .bind(equips.weapon.map(|i| i.0 as i32))
.bind(equips.armor.map(|i| i.0 as i32)) .bind(equips.armor.map(|i| i.0 as i32))
.bind(equips.shield.map(|i| i.0 as i32)) .bind(equips.shield.map(|i| i.0 as i32))
@ -568,7 +569,7 @@ async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &Character
async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError>
{ {
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2") sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(meseta.0 as i32) .bind(meseta.0 as i32)
.execute(conn) .execute(conn)
.await?; .await?;
@ -580,7 +581,7 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
struct PgMeseta(i32); struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#) let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
Ok(Meseta(meseta.0 as u32)) Ok(Meseta(meseta.0 as u32))
@ -591,7 +592,7 @@ async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
match bank_identifier { match bank_identifier {
BankIdentifier::Character => { BankIdentifier::Character => {
sqlx::query("insert into bank_meseta values ($1, '', $2) on conflict (pchar, bank) do update set meseta = $2") sqlx::query("insert into bank_meseta values ($1, '', $2) on conflict (pchar, bank) do update set meseta = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(meseta.0 as i32) .bind(meseta.0 as i32)
.execute(conn) .execute(conn)
.await?; .await?;
@ -601,7 +602,7 @@ async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
select player_character.user_account, $2, $3 from player_character select player_character.user_account, $2, $3 from player_character
where player_character.id = $1 where player_character.id = $1
on conflict (user_account, name) do update set meseta = $3") on conflict (user_account, name) do update set meseta = $3")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(&bank_name.0) .bind(&bank_name.0)
.bind(meseta.0 as i32) .bind(meseta.0 as i32)
.execute(conn) .execute(conn)
@ -620,7 +621,7 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
let meseta = match bank_identifier { let meseta = match bank_identifier {
BankIdentifier::Character => { BankIdentifier::Character => {
sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1"#) sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn) .fetch_one(conn)
.await? .await?
}, },
@ -628,7 +629,7 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
sqlx::query_as::<_, PgMeseta>(r#"select shared_bank_meseta.meseta from shared_bank_meseta 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 join player_character on shared_bank_meseta.user_account = player_character.user_account
where player_character.id = $1 and shared_bank_meseta.name = $2"#) where player_character.id = $1 and shared_bank_meseta.name = $2"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(&bank_name.0) .bind(&bank_name.0)
.fetch_optional(conn) .fetch_optional(conn)
.await? .await?
@ -641,8 +642,8 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError>
{ {
let trade = sqlx::query_as::<_, PgTradeEntity>(r#"insert into trades (character1, character2) values ($1, $2) returning *;"#) let trade = sqlx::query_as::<_, PgTradeEntity>(r#"insert into trades (character1, character2) values ($1, $2) returning *;"#)
.bind(char_id1.0)
.bind(char_id2.0)
.bind(char_id1.0 as i32)
.bind(char_id2.0 as i32)
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
Ok(trade.into()) Ok(trade.into())
@ -651,8 +652,30 @@ async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityI
async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError>
{ {
sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#) sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#)
.bind(char_id.0)
.bind(playtime)
.bind(char_id.0 as i32)
.bind(playtime as i32)
.execute(conn)
.await?;
Ok(())
}
async fn create_room(conn: &mut sqlx::PgConnection, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
sqlx::query_as::<_, PgRoomEntity>("insert into room (name, section_id, mode, episode, difficulty) values ($1, $2, $3, $4, $5) returning *")
.bind(room.name)
.bind(u8::from(room.section_id) as i8)
.bind(u8::from(room.mode) as i8)
.bind(u8::from(room.episode) as i8)
.bind(u8::from(room.difficulty) as i8)
.fetch_one(conn)
.await
.map(|room| room.into())
.map_err(|err| err.into())
}
async fn add_room_note(conn: &mut sqlx::PgConnection, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
sqlx::query("insert into room_note (room, note) values ($1, $2)")
.bind(room_id.0 as i32)
.bind(sqlx::types::Json(note))
.execute(conn) .execute(conn)
.await?; .await?;
Ok(()) Ok(())
@ -797,6 +820,14 @@ impl EntityGateway for PostgresGateway {
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> { async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
set_character_playtime(&mut *self.pool.acquire().await?, char_id, playtime).await set_character_playtime(&mut *self.pool.acquire().await?, char_id, playtime).await
} }
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
create_room(&mut *self.pool.acquire().await?, room).await
}
async fn add_room_note(&mut self, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
add_room_note(&mut *self.pool.acquire().await?, room_id, note).await
}
} }
@ -923,5 +954,13 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> { async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> {
set_character_playtime(&mut *self.pgtransaction.lock().await, char_id, playtime).await set_character_playtime(&mut *self.pgtransaction.lock().await, char_id, playtime).await
} }
async fn create_room(&mut self, room: NewRoomEntity) -> Result<RoomEntity, GatewayError> {
create_room(&mut *self.pgtransaction.lock().await, room).await
}
async fn add_room_note(&mut self, room_id: RoomEntityId, note: RoomNote) -> Result<(), GatewayError> {
add_room_note(&mut *self.pgtransaction.lock().await, room_id, note).await
}
} }

2
src/entity/item/armor.rs → src/entity/src/item/armor.rs

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

0
src/entity/item/esweapon.rs → src/entity/src/item/esweapon.rs

8
src/entity/item/mag.rs → src/entity/src/item/mag.rs

@ -1,9 +1,9 @@
use thiserror::Error;
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::entity::item::tool::ToolType;
use crate::entity::character::{CharacterClass, SectionID};
use crate::entity::item::ItemEntityId;
use crate::item::tool::ToolType;
use crate::character::{CharacterClass, SectionID};
use crate::item::ItemEntityId;
use std::io::Read; use std::io::Read;
use std::cmp::Ordering::{Less, Greater, Equal}; use std::cmp::Ordering::{Less, Greater, Equal};

53
src/entity/item/mod.rs → src/entity/src/item/mod.rs

@ -9,9 +9,11 @@ pub mod mag;
pub mod esweapon; pub mod esweapon;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::entity::character::CharacterEntityId;
use crate::ship::map::MapArea;
use crate::ship::drops::ItemDropType;
use crate::character::CharacterEntityId;
use crate::room::RoomEntityId;
use maps::area::MapArea;
use maps::monster::MonsterType;
//use crate::ship::drops::ItemDropType;
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ItemEntityId(pub u32); pub struct ItemEntityId(pub u32);
@ -35,8 +37,16 @@ pub enum ItemNote {
}, },
EnemyDrop { EnemyDrop {
character_id: CharacterEntityId, character_id: CharacterEntityId,
//monster_type: MonsterType,
//droprate: f32,
room_id: RoomEntityId,
monster_type: MonsterType,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: CharacterEntityId,
room_id: RoomEntityId,
map_area: MapArea, map_area: MapArea,
x: f32, x: f32,
y: f32, y: f32,
@ -52,15 +62,19 @@ pub enum ItemNote {
y: f32, y: f32,
z: f32, z: f32,
}, },
Consumed, // TODO: character_id
Consumed {
character_id: CharacterEntityId,
},
FedToMag { FedToMag {
//character_id: CharacterEntityId,
character_id: CharacterEntityId,
mag: ItemEntityId, mag: ItemEntityId,
}, },
BoughtAtShop { BoughtAtShop {
character_id: CharacterEntityId, character_id: CharacterEntityId,
}, },
SoldToShop,
SoldToShop {
character_id: CharacterEntityId,
},
Trade { Trade {
trade_id: TradeId, trade_id: TradeId,
character_to: CharacterEntityId, character_to: CharacterEntityId,
@ -74,6 +88,9 @@ pub enum ItemNote {
character_id: CharacterEntityId, character_id: CharacterEntityId,
bank: BankIdentifier, bank: BankIdentifier,
}, },
FloorLimitReached {
map_area: MapArea,
},
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -139,26 +156,6 @@ impl ItemDetail {
} }
} }
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
match item_type {
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
_ => None,
}
}
pub fn as_client_bytes(&self) -> [u8; 16] { pub fn as_client_bytes(&self) -> [u8; 16] {
match self { match self {
ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Weapon(w) => w.as_bytes(),

0
src/entity/item/shield.rs → src/entity/src/item/shield.rs

0
src/entity/item/tech.rs → src/entity/src/item/tech.rs

0
src/entity/item/tool.rs → src/entity/src/item/tool.rs

0
src/entity/item/unit.rs → src/entity/src/item/unit.rs

2
src/entity/item/weapon.rs → src/entity/src/item/weapon.rs

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

1
src/entity/mod.rs → src/entity/src/lib.rs

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

83
src/entity/src/room.rs

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

31
src/entity/src/team.rs

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

25
src/items/Cargo.toml

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

180
src/ship/items/actions.rs → src/items/src/actions.rs

@ -1,6 +1,6 @@
// TODO: replace various u32s and usizes denoting item amounts for ItemAmount(u32) for consistency // TODO: replace various u32s and usizes denoting item amounts for ItemAmount(u32) for consistency
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemNote};
use crate::ClientItemId;
use entity::item::{Meseta, ItemNote};
use async_std::sync::Arc; use async_std::sync::Arc;
use std::future::Future; use std::future::Future;
use futures::future::BoxFuture; use futures::future::BoxFuture;
@ -8,29 +8,33 @@ use std::pin::Pin;
use std::iter::IntoIterator; use std::iter::IntoIterator;
use anyhow::Context; use anyhow::Context;
use libpso::packet::{ship::Message, messages::GameMessage};
use crate::ship::map::MapArea;
use crate::ship::ship::SendShipPacket;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::ship::items::bank::{BankItem, BankItemDetail};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use crate::ship::items::floor::{FloorItem, FloorItemDetail};
use crate::ship::items::apply_item::{apply_item, ApplyItemAction};
use crate::entity::item::{ItemDetail, NewItemEntity, TradeId};
use crate::entity::item::tool::Tool;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::packet::builder;
use crate::ship::location::AreaClient;
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::gateway::{EntityGateway, EntityGatewayTransaction};
use entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier};
use entity::item::tool::Tool;
use entity::room::RoomEntityId;
use maps::area::MapArea;
use crate::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::bank::{BankItem, BankItemDetail};
use crate::inventory::{InventoryItem, InventoryItemDetail};
use crate::floor::{FloorItem, FloorItemDetail};
use crate::apply_item::{apply_item, ApplyItemAction};
use shops::ShopItem;
use drops::{ItemDrop, ItemDropType};
use location::AreaClient;
use maps::monster::MonsterType;
pub enum TriggerCreateItem { pub enum TriggerCreateItem {
Yes, Yes,
No 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<'a, EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
item_id: ClientItemId item_id: ClientItemId
@ -513,7 +517,9 @@ where
Box::pin(async move { Box::pin(async move {
let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move { async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed).await?;
transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed {
character_id: character.id,
}).await?;
Ok(transaction) Ok(transaction)
}}).await?; }}).await?;
@ -548,7 +554,7 @@ where
let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| { let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| {
async move { async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag { transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag {
//character_id: character.id,
character_id: character.id,
mag: mag_entity_id, mag: mag_entity_id,
}).await?; }).await?;
transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?; transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?;
@ -660,7 +666,9 @@ where
let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move { async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop).await?;
transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop {
character_id,
}).await?;
Ok(transaction) Ok(transaction)
}}).await?; }}).await?;
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
@ -904,7 +912,6 @@ where
pub(super) fn convert_item_drop_to_floor_item<'a, EG, TR>( pub(super) fn convert_item_drop_to_floor_item<'a, EG, TR>(
character_id: CharacterEntityId,
item_drop: ItemDrop, item_drop: ItemDrop,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone -> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
@ -946,13 +953,6 @@ where
let entity = transaction.gateway().create_item(NewItemEntity { let entity = transaction.gateway().create_item(NewItemEntity {
item: item_detail.clone(), item: item_detail.clone(),
}).await?; }).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 { FloorItem {
item_id, item_id,
item: FloorItemDetail::Individual(IndividualItemDetail { item: FloorItemDetail::Individual(IndividualItemDetail {
@ -969,13 +969,6 @@ where
let entity = transaction.gateway().create_item(NewItemEntity { let entity = transaction.gateway().create_item(NewItemEntity {
item: ItemDetail::Tool(tool), item: ItemDetail::Tool(tool),
}).await?; }).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 { FloorItem {
item_id, item_id,
item: FloorItemDetail::Stacked(StackedItemDetail{ item: FloorItemDetail::Stacked(StackedItemDetail{
@ -1005,6 +998,88 @@ 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<'a, EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem) ) -> impl Fn((ItemStateProxy, TR), FloorItem)
@ -1072,7 +1147,7 @@ pub(super) fn apply_item_action_packets<'a, EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
area_client: AreaClient, area_client: AreaClient,
) -> impl Fn((ItemStateProxy, TR), ApplyItemAction) ) -> impl Fn((ItemStateProxy, TR), ApplyItemAction)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<SendShipPacket>), anyhow::Error>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<CreateItem>), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a, TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
@ -1089,7 +1164,7 @@ where
let (inventory_item_detail, create_item) = if item_detail.is_stackable() { 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 tool = item_detail.as_tool().ok_or_else(|| ItemStateError::NotATool(ClientItemId(0xFFFFFFFF)))?;
let create_item = builder::message::create_stacked_item(area_client, item_id, &tool, 1).map_err(|_err| ItemStateError::Dummy)?;
let create_item = CreateItem::Stacked(area_client, item_id, tool, 1);
let item_detail = StackedItemDetail { let item_detail = StackedItemDetail {
entity_ids: vec![new_item.id], entity_ids: vec![new_item.id],
tool tool
@ -1101,7 +1176,7 @@ where
entity_id: new_item.id, entity_id: new_item.id,
item: item_detail, item: item_detail,
}; };
let create_item = builder::message::create_individual_item(area_client, item_id, &item_detail).map_err(|_err| ItemStateError::Dummy)?;
let create_item = CreateItem::Individual(area_client, item_id, item_detail.clone());
(InventoryItemDetail::Individual(item_detail), create_item) (InventoryItemDetail::Individual(item_detail), create_item)
}; };
@ -1115,7 +1190,8 @@ where
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await; item_state.set_inventory(inventory).await;
vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
//vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
vec![create_item]
} }
else { else {
Vec::new() Vec::new()
@ -1148,3 +1224,25 @@ where
}) })
} }
} }
pub(super) fn delete_item_from_floor<'a, EG, TR>(
map_area: MapArea
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), ()), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + Clone + 'a,
{
move |(item_state, transaction), floor_item| {
Box::pin(async move {
let transaction = floor_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::FloorLimitReached {
map_area
}).await?;
Ok(transaction)
}}).await?;
Ok(((item_state, transaction), ()))
})
}
}

20
src/ship/items/apply_item.rs → src/items/src/apply_item.rs

@ -4,15 +4,15 @@ use thiserror::Error;
use anyhow::Context; use anyhow::Context;
use rand::SeedableRng; use rand::SeedableRng;
use rand::distributions::{WeightedIndex, Distribution}; use rand::distributions::{WeightedIndex, Distribution};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::{CharacterEntity, TechLevel};
use crate::entity::item::mag::{MagCell, MagCellError};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::tech::TechniqueDisk;
use crate::entity::item::{ItemDetail, ItemEntityId};
use crate::entity::item::weapon::WeaponModifier;
use crate::ship::items::state::ItemStateProxy;
use crate::ship::items::inventory::InventoryItemDetail;
use entity::gateway::{EntityGateway, GatewayError};
use entity::character::{CharacterEntity, TechLevel};
use entity::item::mag::{MagCell, MagCellError};
use entity::item::tool::{Tool, ToolType};
use entity::item::tech::TechniqueDisk;
use entity::item::{ItemDetail, ItemEntityId};
use entity::item::weapon::WeaponModifier;
use crate::state::ItemStateProxy;
use crate::inventory::InventoryItemDetail;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -226,7 +226,7 @@ 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>, anyhow::Error>
{ {
let mag_rate = WeightedIndex::new(&[13, 13, 13, 13, 12, 12, 12, 12]).unwrap();
let mag_rate = WeightedIndex::new([13, 13, 13, 13, 12, 12, 12, 12]).unwrap();
let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) { let mag_type = match mag_rate.sample(&mut rand_chacha::ChaChaRng::from_entropy()) {
0 => ToolType::CellOfMag502, 0 => ToolType::CellOfMag502,
1 => ToolType::CellOfMag213, 1 => ToolType::CellOfMag213,

30
src/ship/items/bank.rs → src/items/src/bank.rs

@ -1,15 +1,15 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use libpso::character::character; use libpso::character::character;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity};
use crate::ClientItemId;
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity};
use std::future::Future; use std::future::Future;
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
use crate::entity::character::CharacterEntityId;
use crate::entity::item::BankIdentifier;
use crate::ship::items::state::ItemStateError;
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use entity::character::CharacterEntityId;
use entity::item::BankIdentifier;
use crate::state::ItemStateError;
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::inventory::{InventoryItem, InventoryItemDetail};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -325,15 +325,7 @@ impl std::cmp::Eq for BankItemDetail {}
impl std::cmp::PartialOrd for BankItemDetail { impl std::cmp::PartialOrd for BankItemDetail {
fn partial_cmp(&self, other: &BankItemDetail) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &BankItemDetail) -> Option<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.partial_cmp(&other_value)
Some(self.cmp(other))
} }
} }
@ -344,8 +336,8 @@ impl std::cmp::Ord for BankItemDetail {
self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]); self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]);
other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]); other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]);
let self_value = u32::from_le_bytes(self_bytes);
let other_value = u32::from_le_bytes(other_bytes);
let self_value = u32::from_be_bytes(self_bytes);
let other_value = u32::from_be_bytes(other_bytes);
self_value.cmp(&other_value) self_value.cmp(&other_value)
} }
@ -362,7 +354,7 @@ impl std::cmp::Eq for BankItem {}
impl std::cmp::PartialOrd for BankItem { impl std::cmp::PartialOrd for BankItem {
fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &BankItem) -> Option<std::cmp::Ordering> {
self.item.partial_cmp(&other.item)
Some(self.cmp(other))
} }
} }

20
src/ship/items/floor.rs → src/items/src/floor.rs

@ -1,14 +1,14 @@
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail};
use crate::ClientItemId;
use entity::item::{Meseta, ItemEntityId, ItemDetail};
use std::future::Future; use std::future::Future;
use crate::ship::map::MapArea;
use crate::entity::character::CharacterEntityId;
use crate::entity::item::mag::Mag;
use maps::area::MapArea;
use entity::character::CharacterEntityId;
use entity::item::mag::Mag;
use crate::ship::items::state::ItemStateError;
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use crate::state::ItemStateError;
use crate::state::{IndividualItemDetail, StackedItemDetail};
use crate::inventory::{InventoryItem, InventoryItemDetail};
pub enum FloorType { pub enum FloorType {
Local, Local,
@ -96,13 +96,13 @@ pub struct FloorState {
impl FloorState { impl FloorState {
pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<FloorItem> { pub fn take_item(&mut self, item_id: &ClientItemId) -> Option<FloorItem> {
let item = self.local.0 let item = self.local.0
.drain_filter(|item| {
.extract_if(|item| {
item.item_id == *item_id item.item_id == *item_id
}) })
.next(); .next();
item.or_else(|| { item.or_else(|| {
self.shared.0 self.shared.0
.drain_filter(|item| {
.extract_if(|item| {
item.item_id == *item_id item.item_id == *item_id
}) })
.next() .next()

20
src/ship/items/inventory.rs → src/items/src/inventory.rs

@ -1,18 +1,18 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use libpso::character::character; use libpso::character::character;
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
use crate::ClientItemId;
use entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity};
use std::future::Future; use std::future::Future;
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
use crate::entity::character::CharacterEntityId;
use crate::entity::item::tool::ToolType;
use crate::entity::item::mag::Mag;
use crate::entity::item::weapon::Weapon;
use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
use crate::ship::items::state::ItemStateError;
use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::ship::items::floor::{FloorItem, FloorItemDetail};
use entity::character::CharacterEntityId;
use entity::item::tool::ToolType;
use entity::item::mag::Mag;
use entity::item::weapon::Weapon;
use shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem};
use crate::state::ItemStateError;
use crate::state::{IndividualItemDetail, StackedItemDetail, AddItemResult};
use crate::floor::{FloorItem, FloorItemDetail};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum InventoryItemDetail { pub enum InventoryItemDetail {

0
src/ship/items/itemstateaction.rs → src/items/src/itemstateaction.rs

3
src/ship/items/mod.rs → src/items/src/lib.rs

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

26
src/ship/items/state.rs → src/items/src/state.rs

@ -4,18 +4,18 @@ use async_std::sync::{Arc, RwLock, Mutex};
use futures::stream::{FuturesOrdered, StreamExt}; use futures::stream::{FuturesOrdered, StreamExt};
use anyhow::Context; use anyhow::Context;
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankIdentifier};
use crate::entity::item::tool::Tool;
use crate::entity::item::weapon::Weapon;
use crate::entity::item::mag::Mag;
use crate::ship::drops::ItemDrop;
use crate::ship::items::ClientItemId;
use crate::ship::items::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState};
use crate::ship::items::floor::{FloorState, FloorItem, LocalFloor, SharedFloor, FloorType};
use crate::ship::items::bank::{Bank, BankState, BankItem, BankItemDetail, BankError};
use crate::ship::location::{AreaClient, RoomId};
use entity::gateway::{EntityGateway, GatewayError};
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankIdentifier};
use entity::item::tool::Tool;
use entity::item::weapon::Weapon;
use entity::item::mag::Mag;
use drops::ItemDrop;
use crate::ClientItemId;
use crate::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState};
use crate::floor::{FloorState, FloorItem, LocalFloor, SharedFloor, FloorType};
use crate::bank::{Bank, BankState, BankItem, BankItemDetail, BankError};
use location::{AreaClient, RoomId};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ItemStateError { pub enum ItemStateError {
@ -50,7 +50,7 @@ pub enum ItemStateError {
#[error("stacked item")] #[error("stacked item")]
StackedItemError(Vec<ItemEntity>), StackedItemError(Vec<ItemEntity>),
#[error("apply item {0}")] #[error("apply item {0}")]
ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError),
ApplyItemError(#[from] crate::apply_item::ApplyItemError),
#[error("item is not a mag {0}")] #[error("item is not a mag {0}")]
NotAMag(ClientItemId), NotAMag(ClientItemId),
#[error("item is not mag food {0}")] #[error("item is not mag food {0}")]

94
src/ship/items/tasks.rs → src/items/src/tasks.rs

@ -1,22 +1,23 @@
use futures::future::BoxFuture; use futures::future::BoxFuture;
use crate::ship::items::ClientItemId;
use crate::entity::item::Meseta;
use crate::ship::ship::SendShipPacket;
use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemState, ItemStateProxy, IndividualItemDetail};
use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction};
use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::floor::FloorItem;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem;
use crate::ship::trade::TradeItem;
use crate::ship::location::AreaClient;
use crate::ship::drops::ItemDrop;
use crate::ship::items::actions;
use crate::ClientItemId;
use entity::item::Meseta;
use maps::area::MapArea;
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::gateway::{EntityGateway, EntityGatewayTransaction};
use entity::item::ItemModifier;
use entity::room::RoomEntityId;
use crate::state::{ItemState, ItemStateProxy, IndividualItemDetail};
use crate::itemstateaction::{ItemStateAction, ItemAction};
use crate::inventory::InventoryItem;
use crate::floor::FloorItem;
use shops::ShopItem;
use crate::trade::TradeItem;
use location::AreaClient;
use drops::ItemDrop;
use maps::monster::MonsterType;
use crate::actions;
pub fn pick_up_item<'a, EG>( pub fn pick_up_item<'a, EG>(
item_state: &'a mut ItemState, item_state: &'a mut ItemState,
@ -276,7 +277,7 @@ pub fn use_item<'a, EG> (
area_client: AreaClient, area_client: AreaClient,
item_id: &'a ClientItemId, item_id: &'a ClientItemId,
amount: u32, amount: u32,
) -> BoxFuture<'a, Result<Vec<SendShipPacket>, anyhow::Error>>
) -> BoxFuture<'a, Result<Vec<actions::CreateItem>, anyhow::Error>>
where where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
@ -370,6 +371,8 @@ where
Ok((transaction, result)) Ok((transaction, result))
}) })
} }
#[allow(clippy::type_complexity)]
pub fn trade_items<'a, EG> ( pub fn trade_items<'a, EG> (
item_state: &'a mut ItemState, item_state: &'a mut ItemState,
entity_gateway: &'a mut EG, entity_gateway: &'a mut EG,
@ -465,6 +468,32 @@ pub fn enemy_drops_item<'a, EG> (
item_state: &'a mut ItemState, item_state: &'a mut ItemState,
entity_gateway: &'a mut EG, entity_gateway: &'a mut EG,
character_id: CharacterEntityId, character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default()
.act(actions::convert_item_drop_to_floor_item(item_drop))
.act(actions::item_note_enemy_drop(character_id, room_id, monster_type))
.act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, floor_item))
})
}
pub fn box_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
item_drop: ItemDrop) item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>> -> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where where
@ -473,7 +502,8 @@ where
entity_gateway.with_transaction(move |transaction| async move { entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone()); let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default() let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default()
.act(actions::convert_item_drop_to_floor_item(character_id, item_drop))
.act(actions::convert_item_drop_to_floor_item(item_drop))
.act(actions::item_note_box_drop(character_id, room_id))
.act(actions::add_item_to_local_floor(character_id)) .act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction)) .commit((item_state_proxy, transaction))
.await?; .await?;
@ -508,3 +538,27 @@ where
Ok((transaction, item)) Ok((transaction, item))
}) })
} }
pub fn floor_item_limit_reached<'a, EG> (
item_state: &'a ItemState,
entity_gateway: &'a mut EG,
character: &'a CharacterEntity,
item_id: &'a ClientItemId,
map_area: MapArea
) -> BoxFuture<'a, Result<(), anyhow::Error>>
where
EG: EntityGateway + 'static,
EG::Transaction<'a>: Clone,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let((item_state_proxy, transaction), result) = ItemStateAction::default()
.act(actions::take_item_from_floor(character.id, *item_id))
.act(actions::delete_item_from_floor(map_area))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, result))
})
}

38
src/items/src/trade.rs

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

17
src/lib.rs

@ -1,17 +0,0 @@
#![allow(clippy::type_complexity)]
#![allow(incomplete_features)]
#![feature(inline_const)]
#![feature(drain_filter)]
#![feature(try_blocks)]
#![feature(once_cell)]
#![feature(test)]
#![feature(error_generic_member_access)]
#![feature(provide_any)]
extern crate test;
pub mod common;
pub mod entity;
pub mod patch;
pub mod login;
pub mod ship;

12
src/location/Cargo.toml

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

2
src/ship/location.rs → src/location/src/lib.rs

@ -2,7 +2,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::time::SystemTime; use std::time::SystemTime;
use thiserror::Error; use thiserror::Error;
use crate::common::serverstate::ClientId;
use networking::serverstate::ClientId;
use async_std::sync::{Arc, RwLock}; use async_std::sync::{Arc, RwLock};
use futures::{stream, StreamExt}; use futures::{stream, StreamExt};

3
src/login/mod.rs

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

92
src/login/models.rs

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

21
src/login_server/Cargo.toml

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

59
src/login/character.rs → src/login_server/src/character.rs

@ -12,30 +12,31 @@ use libpso::packet::login::*;
use libpso::packet::ship::{MenuDetail, SmallLeftDialog}; use libpso::packet::ship::{MenuDetail, SmallLeftDialog};
use libpso::{PacketParseError, PSOPacket}; use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher; use libpso::crypto::bb::PSOBBCipher;
use crate::entity::item;
use libpso::character::character; use libpso::character::character;
use entity::item;
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
use crate::common::leveltable::LEVEL_TABLE;
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::{utf8_to_array, utf8_to_utf16_array}; use libpso::{utf8_to_array, utf8_to_utf16_array};
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
use crate::entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankIdentifier, EquippedEntity, Meseta};
use crate::entity::item::weapon::Weapon;
use crate::entity::item::armor::Armor;
use crate::entity::item::tech::Technique;
use crate::entity::item::tool::Tool;
use crate::entity::item::mag::Mag;
use crate::entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
use entity::gateway::{EntityGateway, GatewayError};
use entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
use entity::item::{NewItemEntity, ItemDetail, ItemNote, InventoryItemEntity, InventoryEntity, BankEntity, BankIdentifier, EquippedEntity, Meseta};
use entity::item::weapon::Weapon;
use entity::item::armor::Armor;
use entity::item::tech::Technique;
use entity::item::tool::Tool;
use entity::item::mag::Mag;
use entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
use crate::login::login::{get_login_status};
use crate::common::interserver::AuthToken;
use crate::login::get_login_status;
use networking::interserver::AuthToken;
use pktbuilder::ship::SHIP_MENU_ID;
pub const CHARACTER_PORT: u16 = 12001; pub const CHARACTER_PORT: u16 = 12001;
pub const SHIP_MENU_ID: u32 = 1;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum CharacterError { pub enum CharacterError {
@ -484,7 +485,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
async fn set_flag(&mut self, id: ClientId, setflag: &SetFlag) -> Result<std::option::IntoIter<SendCharacterPacket>, anyhow::Error> { 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 mut client = self.clients.write().await;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?; let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let mut user = client.user.as_mut().unwrap();
let user = client.user.as_mut().unwrap();
user.flags = setflag.flags; user.flags = setflag.flags;
self.entity_gateway.save_user(user).await.unwrap(); self.entity_gateway.save_user(user).await.unwrap();
Ok(None.into_iter()) Ok(None.into_iter())
@ -515,7 +516,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
async fn character_preview(&mut self, id: ClientId, preview: &CharacterPreview) -> Result<Vec<SendCharacterPacket>, anyhow::Error> { async fn character_preview(&mut self, id: ClientId, preview: &CharacterPreview) -> Result<Vec<SendCharacterPacket>, anyhow::Error> {
let mut client = self.clients.write().await; let mut client = self.clients.write().await;
let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?; let client = client.get_mut(&id).ok_or_else(|| CharacterError::ClientNotFound(id))?;
let mut user = client.user.as_mut().unwrap();
let user = client.user.as_mut().unwrap();
if user.flags == USERFLAG_NEWCHAR { if user.flags == USERFLAG_NEWCHAR {
new_character(&mut self.entity_gateway, user, preview).await? new_character(&mut self.entity_gateway, user, preview).await?
} }
@ -834,9 +835,21 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::entity::account::*;
use entity::account::*;
use libpso::character::{settings, character}; use libpso::character::{settings, character};
use crate::entity::gateway::{InMemoryGateway, GatewayError};
use entity::gateway::{InMemoryGateway, EntityGatewayTransaction, GatewayError};
#[derive(Clone)]
struct CharTestDb;
impl EntityGateway for CharTestDb {
type Transaction<'t> = CharTestDb where Self: 't;
}
impl EntityGatewayTransaction for CharTestDb {
type ParentGateway = CharTestDb;
}
#[async_std::test] #[async_std::test]
async fn test_option_send() { async fn test_option_send() {
@ -846,7 +859,7 @@ mod test {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'a> = () where Self: 'a;
type Transaction<'a> = CharTestDb where Self: 'a;
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> { async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
Ok(UserSettingsEntity { Ok(UserSettingsEntity {
id: UserSettingsId(0), id: UserSettingsId(0),
@ -889,7 +902,7 @@ mod test {
#[derive(Clone)] #[derive(Clone)]
struct TestData; struct TestData;
impl EntityGateway for TestData { impl EntityGateway for TestData {
type Transaction<'a> = () where Self: 'a;
type Transaction<'a> = CharTestDb where Self: 'a;
} }
let mut server = CharacterServerState::new(TestData {}, AuthToken("".into())); let mut server = CharacterServerState::new(TestData {}, AuthToken("".into()));
let send = server.handle(ClientId(1), RecvCharacterPacket::Checksum(Checksum {checksum: 1234, let send = server.handle(ClientId(1), RecvCharacterPacket::Checksum(Checksum {checksum: 1234,

2
src/login_server/src/lib.rs

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

45
src/login/login.rs → src/login_server/src/login.rs

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

14
src/maps/Cargo.toml

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

2
src/ship/map/area.rs → src/maps/src/area.rs

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

37
src/ship/map/enemy.rs → src/maps/src/enemy.rs

@ -1,19 +1,18 @@
// TOOD: `pub(super) for most of these?` // TOOD: `pub(super) for most of these?`
use std::io::{Read}; use std::io::{Read};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::ship::ship::ShipEvent;
use crate::ship::monster::MonsterType;
use crate::ship::room::Episode;
use crate::ship::map::*;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::ship::drops::{load_rare_monster_file};
use crate::Holiday;
use crate::area::{MapArea, MapAreaError};
use crate::room::Episode;
use crate::monster::MonsterType;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct RawMapEnemy { pub struct RawMapEnemy {
@ -69,6 +68,17 @@ impl RawMapEnemy {
} }
pub fn load_rare_monster_file<T: serde::de::DeserializeOwned>(episode: Episode) -> T {
// TODO: where does the rare monster toml file actually live
let mut path = PathBuf::from("data/battle_param/");
path.push(episode.to_string().to_lowercase() + "_rare_monster.toml");
let mut f = File::open(path).unwrap();
let mut s = String::new();
f.read_to_string(&mut s).unwrap();
toml::from_str::<T>(s.as_str()).unwrap()
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("")] #[error("")]
pub enum MapEnemyError { pub enum MapEnemyError {
@ -76,6 +86,7 @@ pub enum MapEnemyError {
MapAreaError(#[from] MapAreaError), MapAreaError(#[from] MapAreaError),
} }
// making this `pub type` doesn't allow `impl`s to be defined? // making this `pub type` doesn't allow `impl`s to be defined?
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RareMonsterAppearTable { pub struct RareMonsterAppearTable {
@ -103,7 +114,7 @@ impl RareMonsterAppearTable {
rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32)
} }
pub fn apply(&self, enemy: MapEnemy, event: ShipEvent) -> MapEnemy {
pub fn apply(&self, enemy: MapEnemy, event: Holiday) -> MapEnemy {
if enemy.can_be_rare() && self.roll_is_rare(&enemy.monster) { if enemy.can_be_rare() && self.roll_is_rare(&enemy.monster) {
enemy.into_rare(event) enemy.into_rare(event)
} }
@ -345,12 +356,12 @@ impl MapEnemy {
guaranteed rare monsters don't count towards the limit guaranteed rare monsters don't count towards the limit
*/ */
#[must_use] #[must_use]
pub fn into_rare(self, event: ShipEvent) -> MapEnemy {
pub fn into_rare(self, event: Holiday) -> MapEnemy {
match (self.monster, self.map_area.to_episode(), event) { match (self.monster, self.map_area.to_episode(), event) {
(MonsterType::RagRappy, Episode::One, _) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}}, (MonsterType::RagRappy, Episode::One, _) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, ShipEvent::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, ShipEvent::Halloween) => {MapEnemy {monster: MonsterType::HalloRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, ShipEvent::Christmas) => {MapEnemy {monster: MonsterType::StRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, Holiday::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, Holiday::Halloween) => {MapEnemy {monster: MonsterType::HalloRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, Holiday::Christmas) => {MapEnemy {monster: MonsterType::StRappy, shiny:true, ..self}},
(MonsterType::RagRappy, Episode::Two, _) => {MapEnemy {monster: MonsterType::LoveRappy, shiny:true, ..self}}, (MonsterType::RagRappy, Episode::Two, _) => {MapEnemy {monster: MonsterType::LoveRappy, shiny:true, ..self}},
(MonsterType::Hildebear, _, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}}, (MonsterType::Hildebear, _, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
(MonsterType::PoisonLily, _, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}}, (MonsterType::PoisonLily, _, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},

59
src/maps/src/lib.rs

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

28
src/ship/map/maps.rs → src/maps/src/maps.rs

@ -6,14 +6,15 @@ use std::fs::File;
use thiserror::Error; use thiserror::Error;
use crate::ship::ship::ShipEvent;
use crate::ship::monster::MonsterType;
use crate::ship::room::{Episode, RoomMode, PlayerMode};
// TODO: don't use *
use crate::ship::map::*;
//use crate::ship::ship::ShipEvent;
use crate::area::MapArea;
use crate::Holiday;
use crate::enemy::{MapEnemy, RawMapEnemy, RareMonsterAppearTable};
use crate::monster::MonsterType;
use crate::variant::{MapVariant, MapVariantMode};
use crate::object::{MapObject, RawMapObject};
use crate::room::{Episode, RoomMode, PlayerMode};
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> { pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
let mut object_data = Vec::new(); let mut object_data = Vec::new();
@ -325,7 +326,7 @@ impl Maps {
enemies: Vec<Option<MapEnemy>>, enemies: Vec<Option<MapEnemy>>,
objects: Vec<Option<MapObject>>, objects: Vec<Option<MapObject>>,
rare_monster_table: &RareMonsterAppearTable, rare_monster_table: &RareMonsterAppearTable,
event: ShipEvent)
event: Holiday)
{ {
self.enemy_data = enemies self.enemy_data = enemies
.into_iter() .into_iter()
@ -358,7 +359,7 @@ impl Maps {
} }
} }
pub fn generate_free_roam_maps(room_mode: RoomMode, event: ShipEvent) -> Maps {
pub fn generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> Maps {
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode()); let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode()); let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode());
Maps { Maps {
@ -375,3 +376,12 @@ pub fn generate_free_roam_maps(room_mode: RoomMode, event: ShipEvent) -> Maps {
map_variants, map_variants,
} }
} }
pub fn null_free_roam_maps(_room_mode: RoomMode, _event: Holiday) -> Maps {
Maps {
enemy_data: Default::default(),
object_data: Default::default(),
map_variants: Default::default(),
}
}

4
src/ship/monster.rs → src/maps/src/monster.rs

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

8
src/ship/map/object.rs → src/maps/src/object.rs

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

150
src/maps/src/room.rs

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

3
src/ship/map/variant.rs → src/maps/src/variant.rs

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

18
src/networking/Cargo.toml

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

0
src/common/cipherkeys.rs → src/networking/src/cipherkeys.rs

4
src/common/interserver.rs → src/networking/src/interserver.rs

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

1
src/common/mod.rs → src/networking/src/lib.rs

@ -1,7 +1,6 @@
pub mod cipherkeys; pub mod cipherkeys;
pub mod serverstate; pub mod serverstate;
pub mod mainloop; pub mod mainloop;
pub mod leveltable;
pub mod interserver; pub mod interserver;
// https://www.reddit.com/r/rust/comments/33xhhu/how_to_create_an_array_of_structs_that_havent/ // https://www.reddit.com/r/rust/comments/33xhhu/how_to_create_an_array_of_structs_that_havent/

6
src/common/mainloop/client.rs → src/networking/src/mainloop/client.rs

@ -9,8 +9,8 @@ use log::{trace, info, warn, error};
use libpso::crypto::{PSOCipher, NullCipher, CipherError}; use libpso::crypto::{PSOCipher, NullCipher, CipherError};
use libpso::PacketParseError; use libpso::PacketParseError;
use crate::common::serverstate::ClientId;
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
use crate::serverstate::ClientId;
use crate::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
#[derive(Debug)] #[derive(Debug)]
@ -255,7 +255,7 @@ where
let (mut socket, addr) = listener.accept().await.unwrap(); let (mut socket, addr) = listener.accept().await.unwrap();
id += 1; id += 1;
let client_id = crate::common::serverstate::ClientId(id);
let client_id = crate::serverstate::ClientId(id);
info!("new client {:?} {:?} {:?}", client_id, socket, addr); info!("new client {:?} {:?} {:?}", client_id, socket, addr);
let (client_tx, client_rx) = async_std::channel::unbounded(); let (client_tx, client_rx) = async_std::channel::unbounded();

20
src/common/mainloop/interserver.rs → src/networking/src/mainloop/interserver.rs

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

0
src/common/mainloop/mod.rs → src/networking/src/mainloop/mod.rs

0
src/common/serverstate.rs → src/networking/src/serverstate.rs

2
src/patch/mod.rs

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

15
src/patch_server/Cargo.toml

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

4
src/patch/patch.rs → src/patch_server/src/lib.rs

@ -11,8 +11,8 @@ use libpso::crypto::pc::PSOPCCipher;
use ron::de::from_str; use ron::de::from_str;
use serde::Deserialize; use serde::Deserialize;
use crate::common::mainloop::{NetworkError};
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
use networking::mainloop::{NetworkError};
use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
#[derive(Debug)] #[derive(Debug)]
pub enum PatchError { pub enum PatchError {

22
src/pktbuilder/Cargo.toml

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

11
src/ship/character.rs → src/pktbuilder/src/character.rs

@ -1,10 +1,9 @@
use libpso::character::character; use libpso::character::character;
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;
use stats::leveltable::CharacterStats;
use entity::character::CharacterEntity;
use items::bank::BankState;
use items::inventory::InventoryState;
use entity::item::Meseta;
#[derive(Default)] #[derive(Default)]

11
src/ship/packet/builder/mod.rs → src/pktbuilder/src/lib.rs

@ -3,14 +3,15 @@ pub mod message;
pub mod room; pub mod room;
pub mod quest; pub mod quest;
pub mod ship; pub mod ship;
pub mod character;
use libpso::character::character::Inventory; use libpso::character::character::Inventory;
use libpso::packet::ship::{PlayerHeader, PlayerInfo}; use libpso::packet::ship::{PlayerHeader, PlayerInfo};
use crate::common::leveltable::LEVEL_TABLE;
use crate::ship::character::CharacterBytesBuilder;
use crate::ship::ship::ClientState;
use crate::ship::location::AreaClient;
use crate::ship::items::inventory::InventoryState;
use stats::leveltable::LEVEL_TABLE;
use crate::character::CharacterBytesBuilder;
use client::ClientState;
use location::AreaClient;
use items::inventory::InventoryState;
pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -> PlayerHeader { pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -> PlayerHeader {
PlayerHeader { PlayerHeader {

15
src/ship/packet/builder/lobby.rs → src/pktbuilder/src/lobby.rs

@ -1,9 +1,10 @@
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{Clients, ShipEvent};
use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError};
use crate::ship::packet::builder::{player_info};
use crate::ship::items::state::ItemState;
use networking::serverstate::ClientId;
use maps::Holiday;
use client::Clients;
use location::{ClientLocation, LobbyId, ClientLocationError};
use crate::player_info;
use items::state::ItemState;
use futures::future::join_all; use futures::future::join_all;
@ -12,7 +13,7 @@ pub async fn join_lobby(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &ItemState, item_state: &ItemState,
event: ShipEvent)
event: Holiday)
-> Result<JoinLobby, anyhow::Error> { -> Result<JoinLobby, anyhow::Error> {
let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?; let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
@ -52,7 +53,7 @@ pub async fn add_to_lobby(id: ClientId,
client_location: &ClientLocation, client_location: &ClientLocation,
clients: &Clients, clients: &Clients,
item_state: &ItemState, item_state: &ItemState,
event: ShipEvent)
event: Holiday)
-> Result<AddToLobby, anyhow::Error> { -> Result<AddToLobby, anyhow::Error> {
let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?; let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?; let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;

106
src/ship/packet/builder/message.rs → src/pktbuilder/src/message.rs

@ -1,21 +1,21 @@
use libpso::packet::messages::*; use libpso::packet::messages::*;
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::entity::item;
use crate::common::leveltable::CharacterStats;
use crate::ship::ship::{ShipError};
use crate::ship::items::ClientItemId;
use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::state::IndividualItemDetail;
use crate::ship::items::bank::BankState;
use crate::ship::items::floor::FloorItem;
use crate::ship::location::AreaClient;
use entity::item;
use stats::leveltable::CharacterStats;
//use crate::ship::ship::{ShipError};
use items::ClientItemId;
use items::inventory::InventoryItem;
use items::state::IndividualItemDetail;
use items::bank::BankState;
use items::floor::FloorItem;
use location::AreaClient;
use std::convert::TryInto; use std::convert::TryInto;
use crate::ship::shops::ShopItem;
use shops::ShopItem;
pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result<ItemDrop, ShipError> {
pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> ItemDrop {
let item_bytes = item_drop.as_client_bytes(); let item_bytes = item_drop.as_client_bytes();
Ok(ItemDrop {
ItemDrop {
client, client,
target, target,
map_area: item_drop.map_area.area_value(), map_area: item_drop.map_area.area_value(),
@ -24,37 +24,37 @@ pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result<ItemDr
x: item_drop.x, x: item_drop.x,
z: item_drop.z, z: item_drop.z,
y: item_drop.y, y: item_drop.y,
item_bytes: item_bytes[0..12].try_into()?,
item_bytes: item_bytes[0..12].try_into().unwrap(),
item_id: item_drop.item_id.0, item_id: item_drop.item_id.0,
item_bytes2: item_bytes[12..16].try_into()?,
item_bytes2: item_bytes[12..16].try_into().unwrap(),
unknown2: 0, unknown2: 0,
})
}
} }
// TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed // TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed
pub fn create_individual_item(area_client: AreaClient, item_id: ClientItemId, item: &IndividualItemDetail) -> Result<CreateItem, ShipError> {
pub fn create_individual_item(area_client: AreaClient, item_id: ClientItemId, item: &IndividualItemDetail) -> CreateItem {
let bytes = item.as_client_bytes(); let bytes = item.as_client_bytes();
Ok(CreateItem {
CreateItem {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
item_data: bytes[0..12].try_into()?,
item_data: bytes[0..12].try_into().unwrap(),
item_id: item_id.0, item_id: item_id.0,
item_data2: bytes[12..16].try_into()?,
item_data2: bytes[12..16].try_into().unwrap(),
unknown: 0, unknown: 0,
})
}
} }
// TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed // TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed
pub fn create_stacked_item(area_client: AreaClient, item_id: ClientItemId, tool: &item::tool::Tool, amount: usize) -> Result<CreateItem, ShipError> {
pub fn create_stacked_item(area_client: AreaClient, item_id: ClientItemId, tool: &item::tool::Tool, amount: usize) -> CreateItem {
let bytes = tool.as_stacked_bytes(amount); let bytes = tool.as_stacked_bytes(amount);
Ok(CreateItem {
CreateItem {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
item_data: bytes[0..12].try_into()?,
item_data: bytes[0..12].try_into().unwrap(),
item_id: item_id.0, item_id: item_id.0,
item_data2: bytes[12..16].try_into()?,
item_data2: bytes[12..16].try_into().unwrap(),
unknown: 0, unknown: 0,
})
}
} }
pub fn create_meseta(area_client: AreaClient, amount: usize) -> CreateItem { pub fn create_meseta(area_client: AreaClient, amount: usize) -> CreateItem {
@ -69,32 +69,32 @@ pub fn create_meseta(area_client: AreaClient, amount: usize) -> CreateItem {
} }
} }
pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &InventoryItem) -> Result<CreateItem, ShipError> {
pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &InventoryItem) -> CreateItem {
let bytes = item.item.as_client_bytes(); let bytes = item.item.as_client_bytes();
Ok(CreateItem {
CreateItem {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
item_data: bytes[0..12].try_into()?,
item_data: bytes[0..12].try_into().unwrap(),
item_id: item.item_id.0, item_id: item.item_id.0,
item_data2: bytes[12..16].try_into()?,
item_data2: bytes[12..16].try_into().unwrap(),
unknown: 0, unknown: 0,
})
}
} }
pub fn create_withdrawn_inventory_item2(area_client: AreaClient, item: &InventoryItem) -> Result<CreateItem, ShipError> {
pub fn create_withdrawn_inventory_item2(area_client: AreaClient, item: &InventoryItem) -> CreateItem {
let bytes = item.item.as_client_bytes(); let bytes = item.item.as_client_bytes();
Ok(CreateItem {
CreateItem {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
item_data: bytes[0..12].try_into()?,
item_data: bytes[0..12].try_into().unwrap(),
item_id: item.item_id.0, item_id: item.item_id.0,
item_data2: bytes[12..16].try_into()?,
item_data2: bytes[12..16].try_into().unwrap(),
unknown: 0, unknown: 0,
})
}
} }
pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Result<RemoveItemFromFloor, ShipError> {
Ok(RemoveItemFromFloor {
pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> RemoveItemFromFloor {
RemoveItemFromFloor {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
client_id: area_client.local_client.id(), client_id: area_client.local_client.id(),
@ -102,12 +102,12 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu
map_area: item.map_area.area_value(), map_area: item.map_area.area_value(),
unknown2: 0, unknown2: 0,
item_id: item.item_id.0, item_id: item.item_id.0,
})
}
} }
pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> DropSplitStack {
let item_bytes = item.as_client_bytes(); let item_bytes = item.as_client_bytes();
Ok(DropSplitStack {
DropSplitStack {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
variety: 0, variety: 0,
@ -115,16 +115,16 @@ pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result<Dro
map_area: item.map_area.area_value(), map_area: item.map_area.area_value(),
x: item.x, x: item.x,
z: item.z, z: item.z,
item_bytes: item_bytes[0..12].try_into()?,
item_bytes: item_bytes[0..12].try_into().unwrap(),
item_id: item.item_id.0, item_id: item.item_id.0,
item_bytes2: item_bytes[12..16].try_into()?,
item_bytes2: item_bytes[12..16].try_into().unwrap(),
unknown2: 0, unknown2: 0,
})
}
} }
pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem) -> DropSplitStack {
let item_bytes = item.as_client_bytes(); let item_bytes = item.as_client_bytes();
Ok(DropSplitStack {
DropSplitStack {
client: area_client.local_client.id(), client: area_client.local_client.id(),
target: 0, target: 0,
variety: 0, variety: 0,
@ -132,11 +132,11 @@ pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem) -> Res
map_area: item.map_area.area_value(), map_area: item.map_area.area_value(),
x: item.x, x: item.x,
z: item.z, z: item.z,
item_bytes: item_bytes[0..12].try_into()?,
item_bytes: item_bytes[0..12].try_into().unwrap(),
item_id: item.item_id.0, item_id: item.item_id.0,
item_bytes2: item_bytes[12..16].try_into()?,
item_bytes2: item_bytes[12..16].try_into().unwrap(),
unknown2: 0, unknown2: 0,
})
}
} }
pub fn character_gained_exp(area_client: AreaClient, exp: u32) -> GiveCharacterExp { pub fn character_gained_exp(area_client: AreaClient, exp: u32) -> GiveCharacterExp {
@ -215,13 +215,13 @@ pub fn shop_list<I: ShopItem>(shop_type: u8, items: &[I]) -> ShopList {
} }
} }
pub fn tek_preview(id: ClientItemId, weapon: &item::weapon::Weapon) -> Result<TekPreview, ShipError> {
pub fn tek_preview(id: ClientItemId, weapon: &item::weapon::Weapon) -> TekPreview {
let bytes = weapon.as_bytes(); let bytes = weapon.as_bytes();
Ok(TekPreview {
TekPreview {
client: 0x79, client: 0x79,
target: 0, target: 0,
item_bytes: bytes[0..12].try_into()?,
item_bytes: bytes[0..12].try_into().unwrap(),
item_id: id.0, item_id: id.0,
item_bytes2: bytes[12..16].try_into()?,
})
item_bytes2: bytes[12..16].try_into().unwrap(),
}
} }

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

Loading…
Cancel
Save