Compare commits

..

No commits in common. "master" and "622c6e598d949783acd448623f17a013e5ac2e4f" have entirely different histories.

158 changed files with 4730 additions and 2652 deletions

View File

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

View File

@ -6,44 +6,18 @@ edition = "2021"
[workspace] [workspace]
members = [ members = [
"src/client", "entity",
"src/drops", "maps",
"src/entity", "common",
"src/items", "networking",
"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] [workspace.dependencies]
entity = { path = "./src/entity" } libpso = { git = "http://git.sharnoth.com/jake/libpso" }
maps = { path = "./src/maps" } entity = { path = "./entity" }
networking = { path = "./src/networking" } maps = { path = "./maps" }
shops = { path = "./src/shops" } common = { path = "./common" }
stats = { path = "./src/stats" } networking = { path = "./networking" }
items = { path = "./src/items" }
pktbuilder = { path = "./src/pktbuilder" }
quests = { path = "./src/quests" }
location = { path = "./src/location" }
client = { path = "./src/client" }
drops = { path = "./src/drops" }
trade = { path = "./src/trade" }
room = { path = "./src/room" }
patch_server = { path = "./src/patch_server" }
login_server = { path = "./src/login_server" }
ship_server = { path = "./src/ship_server" }
libpso = { git = "http://git.sharnoth.com/jake/libpso", rev="90246b6" }
async-std = { version = "1.9.0", features = ["unstable", "attributes"] } 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"
@ -65,6 +39,7 @@ ages-prs = "0.1"
async-trait = "0.1.51" async-trait = "0.1.51"
async-recursion= "1.0.0" async-recursion= "1.0.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] }
refinery = { version = "0.5.0", features = ["postgres"] } refinery = { version = "0.5.0", features = ["postgres"] }
sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] } sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
strum = "0.19.5" strum = "0.19.5"
@ -72,26 +47,35 @@ strum_macros = "0.19"
anyhow = { version = "1.0.68", features = ["backtrace"] } anyhow = { version = "1.0.68", features = ["backtrace"] }
[dependencies] [dependencies]
entity = { workspace = true } libpso = { git = "http://git.sharnoth.com/jake/libpso" }
maps = { workspace = true } entity = { path = "./entity" }
networking = { workspace = true } maps = { path = "./maps" }
patch_server = { workspace = true } common = { path = "./common" }
login_server = { workspace = true } networking = { path = "./networking" }
ship_server = { workspace = true } async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
futures = "0.3.5"
libpso = { workspace = true } rand = "0.7.3"
rand_chacha = "0.2.2"
async-std = { workspace = true } crc = "^1.0.0"
bcrypt = { workspace = true } bcrypt = "0.10"
chrono = { workspace = true } chrono = { workspace = true }
fern = { workspace = true } serde = "*"
futures = { workspace = true } serde_json = "*"
log = { workspace = true } ron = "*"
toml = "*"
[dev-dependencies] log = "*"
drops = { workspace = true } fern = { version = "0.5", features = ["colored"] }
shops = { workspace = true } byteorder = "1"
items = { workspace = true } enum-utils = "0.1.2"
quests = { workspace = true } derive_more = { version = "0.99.3", features = ["display"]}
stats = { workspace = true } thiserror = "1.0.37"
async-trait = { workspace = true } ages-prs = "0.1"
async-trait = "0.1.51"
async-recursion= "1.0.0"
lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] }
refinery = { version = "0.5.0", features = ["postgres"] }
#sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
strum = "0.19.5"
strum_macros = "0.19"
anyhow = { workspace = true }

8
common/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "common"
version = "0.1.0"
edition = "2021"
[dependencies]
derive_more = { workspace = true }

57
entity/Cargo.toml Normal file
View File

@ -0,0 +1,57 @@
[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 }
#libpso = { git = "http://git.sharnoth.com/jake/libpso" }
#async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
#futures = "0.3.5"
#rand = "0.7.3"
#rand_chacha = "0.2.2"
#crc = "^1.0.0"
#bcrypt = "0.10"
#chrono = "0.4.11"
#serde = "*"
#serde_json = "*"
#ron = "*"
#toml = "*"
#log = "*"
#fern = { version = "0.5", features = ["colored"] }
#byteorder = "1"
#enum-utils = "0.1.2"
#derive_more = { version = "0.99.3", features = ["display"]}
#thiserror = "1.0.37"
#ages-prs = "0.1"
#async-trait = "0.1.51"
#async-recursion= "1.0.0"
#lazy_static = "1.4.0"
#barrel = { version = "0.6.5", features = ["pg"] }
#refinery = { version = "0.5.0", features = ["postgres"] }
#sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
#strum = "0.19.5"
#strum_macros = "0.19"
#anyhow = { version = "1.0.68", features = ["backtrace"] }
#[lib]
#name = "entity"
#path = "lib.rs"

View File

@ -223,7 +223,7 @@ impl CharacterInfoboard {
} }
pub fn update_infoboard(&mut self, new_board: &WriteInfoboard) { pub fn update_infoboard(&mut self, new_board: &WriteInfoboard) {
self.board = libpso::util::utf8_to_utf16_array(&new_board.message); self.board = libpso::utf8_to_utf16_array!(new_board.message, 172);
} }
} }

View File

@ -253,7 +253,7 @@ impl From<PgCharacter> for CharacterEntity {
raw_data: vec_to_array(other.config) raw_data: vec_to_array(other.config)
}, },
info_board: CharacterInfoboard { info_board: CharacterInfoboard {
board: libpso::util::utf8_to_utf16_array(other.infoboard), board: libpso::utf8_to_utf16_array!(other.infoboard, 172),
}, },
guildcard: CharacterGuildCard { guildcard: CharacterGuildCard {
description: other.guildcard, description: other.guildcard,

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
common = { workspace = true }
byteorder = { workspace = true } byteorder = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }

View File

@ -9,11 +9,17 @@ use thiserror::Error;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::Holiday;
use crate::area::{MapArea, MapAreaError}; use crate::area::{MapArea, MapAreaError};
use crate::room::Episode; use crate::room::Episode;
use crate::monster::MonsterType; use crate::monster::MonsterType;
#[derive(Clone, Copy)]
pub enum RareEnemyEvent {
Easter,
Halloween,
Christmas,
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct RawMapEnemy { pub struct RawMapEnemy {
id: u32, id: u32,
@ -75,7 +81,7 @@ pub fn load_rare_monster_file<T: serde::de::DeserializeOwned>(episode: Episode)
let mut f = File::open(path).unwrap(); let mut f = File::open(path).unwrap();
let mut s = String::new(); let mut s = String::new();
f.read_to_string(&mut s).unwrap(); f.read_to_string(&mut s);
toml::from_str::<T>(s.as_str()).unwrap() toml::from_str::<T>(s.as_str()).unwrap()
} }
@ -114,7 +120,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: Holiday) -> MapEnemy { pub fn apply(&self, enemy: MapEnemy, event: Option<RareEnemyEvent>) -> 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)
} }
@ -356,12 +362,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: Holiday) -> MapEnemy { pub fn into_rare(self, event: Option<RareEnemyEvent>) -> 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, Holiday::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}}, (MonsterType::RagRappy, Episode::Two, Some(RareEnemyEvent::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, Some(RareEnemyEvent::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, Some(RareEnemyEvent::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}},

7
maps/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
pub mod area;
pub mod enemy;
pub mod object;
pub mod variant;
pub mod maps;
pub mod monster;
pub mod room;

View File

@ -9,8 +9,7 @@ use thiserror::Error;
//use crate::ship::ship::ShipEvent; //use crate::ship::ship::ShipEvent;
use crate::area::MapArea; use crate::area::MapArea;
use crate::Holiday; use crate::enemy::{MapEnemy, RawMapEnemy, RareEnemyEvent, RareMonsterAppearTable};
use crate::enemy::{MapEnemy, RawMapEnemy, RareMonsterAppearTable};
use crate::monster::MonsterType; use crate::monster::MonsterType;
use crate::variant::{MapVariant, MapVariantMode}; use crate::variant::{MapVariant, MapVariantMode};
use crate::object::{MapObject, RawMapObject}; use crate::object::{MapObject, RawMapObject};
@ -326,7 +325,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: Holiday) event: Option<RareEnemyEvent>)
{ {
self.enemy_data = enemies self.enemy_data = enemies
.into_iter() .into_iter()
@ -359,7 +358,7 @@ impl Maps {
} }
} }
pub fn generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> Maps { pub fn generate_free_roam_maps(room_mode: RoomMode, event: Option<RareEnemyEvent>) -> 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 {
@ -376,12 +375,3 @@ pub fn generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> 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
networking/Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[package]
name = "networking"
version = "0.1.0"
edition = "2021"

0
networking/src/lib.rs Normal file
View File

View File

@ -1,8 +1,8 @@
use log::{info}; use log::{info};
use entity::gateway::postgres::PostgresGateway; use entity::gateway::postgres::PostgresGateway;
use login_server::login::LoginServerState; use elseware::login::login::LoginServerState;
use login_server::character::CharacterServerState; use elseware::login::character::CharacterServerState;
use networking::interserver::AuthToken; use elseware::common::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 {
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await; elseware::common::mainloop::run_server(login_state, elseware::login::login::LOGIN_PORT).await;
}); });
let char_state = CharacterServerState::new(entity_gateway, AuthToken(shipgate_token)); let 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 {
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await; elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
}); });
let inter_character_loop = async_std::task::spawn(async move { let inter_character_loop = async_std::task::spawn(async move {
networking::mainloop::run_interserver_listen(char_state, login_server::login::COMMUNICATION_PORT).await; elseware::common::mainloop::run_interserver_listen(char_state, elseware::login::login::COMMUNICATION_PORT).await;
}); });
info!("[auth/character] starting server"); info!("[auth/character] starting server");

View File

@ -1,14 +1,14 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use log::{info}; use log::{info};
use networking::interserver::AuthToken; use elseware::common::interserver::AuthToken;
use login_server::login::LoginServerState; use elseware::login::login::LoginServerState;
use login_server::character::CharacterServerState; use elseware::login::character::CharacterServerState;
use patch_server::{PatchServerState, generate_patch_tree, load_config, load_motd}; use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd};
use ship_server::ShipServerStateBuilder; use elseware::ship::ship::{ShipServerStateBuilder, ShipEvent};
use maps::Holiday; #[allow(unused_imports)]
use entity::gateway::{EntityGateway, InMemoryGateway}; use entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
use entity::account::{NewUserAccountEntity, NewUserSettingsEntity}; use entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
use entity::character::NewCharacterEntity; use entity::character::NewCharacterEntity;
use entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity}; use entity::item::{NewItemEntity, ItemDetail, InventoryItemEntity};
@ -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 {
networking::mainloop::run_server(patch_state, patch_config.port).await; elseware::common::mainloop::run_server(patch_state, patch_config.port).await;
}); });
info!("[auth] starting server"); 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 {
networking::mainloop::run_server(login_state, login_server::login::LOGIN_PORT).await; elseware::common::mainloop::run_server(login_state, elseware::login::login::LOGIN_PORT).await;
}); });
info!("[character] starting server"); 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 {
networking::mainloop::run_server(sub_char_state, login_server::character::CHARACTER_PORT).await; elseware::common::mainloop::run_server(sub_char_state, elseware::login::character::CHARACTER_PORT).await;
}); });
let sub_char_state = char_state.clone(); let 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 {
networking::mainloop::run_interserver_listen(sub_char_state, login_server::login::COMMUNICATION_PORT).await; elseware::common::mainloop::run_interserver_listen(sub_char_state, elseware::login::login::COMMUNICATION_PORT).await;
}); });
info!("[ship] starting servers"); 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(ship_server::SHIP_PORT) .port(elseware::ship::ship::SHIP_PORT)
.event(Holiday::Halloween) .event(ShipEvent::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 {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT).await; elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
}); });
let sub_ship_state = ship_state.clone(); let 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 {
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await; elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
}); });
let ship_state = ShipServerStateBuilder::default() 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(ship_server::SHIP_PORT+2000) .port(elseware::ship::ship::SHIP_PORT+2000)
.event(Holiday::Christmas) .event(ShipEvent::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 {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+2000).await; elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+2000).await;
}); });
let sub_ship_state = ship_state.clone(); let 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 {
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await; elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
}); });
let ship_state = ShipServerStateBuilder::default() 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(ship_server::SHIP_PORT+3000) .port(elseware::ship::ship::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 {
networking::mainloop::run_server(sub_ship_state, ship_server::SHIP_PORT+3000).await; elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT+3000).await;
}); });
let sub_ship_state = ship_state.clone(); let 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 {
networking::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), login_server::login::COMMUNICATION_PORT).await; elseware::common::mainloop::run_interserver_connect(sub_ship_state, std::net::Ipv4Addr::new(127, 0, 0, 1), elseware::login::login::COMMUNICATION_PORT).await;
}); });
futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop, futures::future::join_all(vec![patch_loop, login_loop, character_loop, inter_character_loop,

View File

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

View File

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

View File

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

View File

@ -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::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect}; use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect};
#[derive(Debug)] #[derive(Debug)]
@ -72,7 +72,7 @@ impl<C: PSOCipher> PacketReceiver<C> {
let mut dec_buf = { let mut dec_buf = {
//let mut cipher = self.cipher.lock().await; //let mut cipher = self.cipher.lock().await;
let block_chunk_len = self.recv_buffer.len() / self.cipher.block_size() * self.cipher.block_size(); let block_chunk_len = self.recv_buffer.len() / self.cipher.block_size() * self.cipher.block_size();
let buf = self.recv_buffer.drain(..block_chunk_len).collect::<Vec<_>>(); let buf = self.recv_buffer.drain(..block_chunk_len).collect();
self.cipher.decrypt(&buf)? self.cipher.decrypt(&buf)?
}; };
self.incoming_data.append(&mut dec_buf); self.incoming_data.append(&mut dec_buf);
@ -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::serverstate::ClientId(id); let client_id = crate::common::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();

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

16
src/lib.rs Normal file
View File

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

View File

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

View File

@ -15,11 +15,11 @@ use libpso::crypto::bb::PSOBBCipher;
use libpso::character::character; use libpso::character::character;
use entity::item; use entity::item;
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use networking::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship}; use crate::common::interserver::{ServerId, InterserverActor, LoginMessage, ShipMessage, Ship};
use stats::leveltable::LEVEL_TABLE; use crate::common::leveltable::LEVEL_TABLE;
use libpso::util::{utf8_to_array, utf8_to_utf16_array}; use libpso::{utf8_to_array, utf8_to_utf16_array};
use entity::gateway::{EntityGateway, GatewayError}; use entity::gateway::{EntityGateway, GatewayError};
use entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM}; use entity::account::{UserAccountId, UserAccountEntity, NewUserSettingsEntity, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM};
@ -31,12 +31,11 @@ use entity::item::tool::Tool;
use entity::item::mag::Mag; use entity::item::mag::Mag;
use entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel}; use entity::character::{CharacterEntity, NewCharacterEntity, CharacterClass, TechLevel};
use crate::login::get_login_status; use crate::login::login::{get_login_status};
use networking::interserver::AuthToken; use crate::common::interserver::AuthToken;
use pktbuilder::ship::SHIP_MENU_ID;
pub const CHARACTER_PORT: u16 = 12001; pub const CHARACTER_PORT: u16 = 12001;
pub const SHIP_MENU_ID: u32 = 1;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum CharacterError { pub enum CharacterError {
@ -150,7 +149,7 @@ fn generate_param_data(path: &str) -> (ParamDataHeader, Vec<u8>) {
size: len as u32, size: len as u32,
checksum: crc.sum32(), checksum: crc.sum32(),
offset: buffer.len() as u32, offset: buffer.len() as u32,
filename: utf8_to_array(param.file_name().unwrap().to_str().unwrap()), filename: utf8_to_array!(param.file_name().unwrap().to_str().unwrap(), 0x40),
}); });
buffer.append(&mut filebuf); buffer.append(&mut filebuf);
@ -177,7 +176,7 @@ impl ClientState {
user: None, user: None,
characters: None, characters: None,
guildcard_data_buffer: None, guildcard_data_buffer: None,
session: Session::default(), session: Session::new(),
} }
} }
} }
@ -342,15 +341,15 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
if let Some(connected_client) = self.connected_clients.read().await.get(&user.id) { if let Some(connected_client) = self.connected_clients.read().await.get(&user.id) {
if let Some(expires) = connected_client.expires { if let Some(expires) = connected_client.expires {
if expires > chrono::Utc::now() { if expires > chrono::Utc::now() {
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]); return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
} }
} }
else { else {
return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::default()))]); return Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(AccountStatus::AlreadyOnline, Session::new()))]);
} }
} }
let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::default()); let mut response = LoginResponse::by_status(AccountStatus::Ok, Session::new());
response.guildcard = user.guildcard; response.guildcard = user.guildcard;
response.team_id = user.team_id.map_or(0, |ti| ti); response.team_id = user.team_id.map_or(0, |ti| ti);
@ -367,7 +366,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
Ok(vec![SendCharacterPacket::LoginResponse(response)]) Ok(vec![SendCharacterPacket::LoginResponse(response)])
}, },
Err(err) => { Err(err) => {
Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::default()))]) Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, Session::new()))])
} }
} }
} }
@ -379,7 +378,7 @@ impl<EG: EntityGateway + Clone> CharacterServerState<EG> {
menu: SHIP_MENU_ID, menu: SHIP_MENU_ID,
item: i.0 as u32, item: i.0 as u32,
flags: 0, flags: 0,
name: utf8_to_utf16_array(&s.name) name: utf8_to_utf16_array!(s.name, 0x11)
} }
}).collect())) }).collect()))
]) ])
@ -824,7 +823,7 @@ impl<'a> SelectScreenCharacterBuilder<'a> {
hair_b: character.appearance.hair_b, hair_b: character.appearance.hair_b,
prop_x: character.appearance.prop_x, prop_x: character.appearance.prop_x,
prop_y: character.appearance.prop_y, prop_y: character.appearance.prop_y,
name: utf8_to_utf16_array(&character.name), name: utf8_to_utf16_array!(character.name, 16),
play_time: character.playtime, play_time: character.playtime,
..character::SelectScreenCharacter::default() ..character::SelectScreenCharacter::default()
} }

View File

@ -11,8 +11,8 @@ use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher; use libpso::crypto::bb::PSOBBCipher;
use libpso::util::array_to_utf8; use libpso::util::array_to_utf8;
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use entity::gateway::EntityGateway; use entity::gateway::EntityGateway;
use entity::account::{UserAccountEntity}; use entity::account::{UserAccountEntity};
@ -83,13 +83,21 @@ pub async fn get_login_status(entity_gateway: &mut impl EntityGateway, pkt: &Log
pub fn check_if_already_online(user: UserAccountEntity) -> Result<UserAccountEntity, AccountStatus> { 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>, // TODO: this should be arc/mutex'd? clients: HashMap<ClientId, String>,
} }
impl<EG: EntityGateway + Clone> LoginServerState<EG> { impl<EG: EntityGateway + Clone> LoginServerState<EG> {
@ -111,7 +119,7 @@ impl<EG: EntityGateway + Clone> LoginServerState<EG> {
let response = SendLoginPacket::LoginResponse(LoginResponse::by_status(AccountStatus::Ok, pkt.session)); let response = SendLoginPacket::LoginResponse(LoginResponse::by_status(AccountStatus::Ok, pkt.session));
let ip = u32::from_ne_bytes(self.character_server_ip.octets()); let ip = u32::from_ne_bytes(self.character_server_ip.octets());
Ok(vec![response, Ok(vec![response,
SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::character::CHARACTER_PORT))]) SendLoginPacket::RedirectClient(RedirectClient::new(ip, crate::login::character::CHARACTER_PORT))])
}, },
Err(err) => { Err(err) => {
Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))]) Ok(vec![SendLoginPacket::LoginResponse(LoginResponse::by_status(err, pkt.session))])

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -11,8 +11,8 @@ use libpso::crypto::pc::PSOPCCipher;
use ron::de::from_str; use ron::de::from_str;
use serde::Deserialize; use serde::Deserialize;
use networking::mainloop::{NetworkError}; use crate::common::mainloop::{NetworkError};
use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId}; use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
#[derive(Debug)] #[derive(Debug)]
pub enum PatchError { pub enum PatchError {
@ -341,7 +341,7 @@ impl Iterator for SendFileIterator {
if len == 0 { if len == 0 {
self.current_file = None; self.current_file = None;
self.chunk_num = 0; self.chunk_num = 0;
Some(SendPatchPacket::EndFileSend(EndFileSend::default())) Some(SendPatchPacket::EndFileSend(EndFileSend::new()))
} }
else { else {
let mut crc = crc32::Digest::new(crc32::IEEE); let mut crc = crc32::Digest::new(crc32::IEEE);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
use libpso::character::character; use libpso::character::character;
use stats::leveltable::CharacterStats; use crate::common::leveltable::CharacterStats;
use entity::character::CharacterEntity; use entity::character::CharacterEntity;
use items::bank::BankState; //use crate::ship::items::{CharacterInventory, CharacterBank};
use items::inventory::InventoryState; use crate::ship::items::bank::BankState;
use crate::ship::items::inventory::InventoryState;
use entity::item::Meseta; use entity::item::Meseta;
@ -53,7 +54,7 @@ impl<'a> CharacterBytesBuilder<'a> {
let level = self.level.unwrap(); let level = self.level.unwrap();
let meseta = self.meseta.unwrap(); let meseta = self.meseta.unwrap();
character::Character { character::Character {
name: libpso::util::utf8_to_utf16_array(&character.name), name: libpso::utf8_to_utf16_array!(character.name, 16),
hp: stats.hp, hp: stats.hp,
atp: stats.atp + character.materials.power as u16 * 2, atp: stats.atp + character.materials.power as u16 * 2,
mst: stats.mst + character.materials.mind as u16 * 2, mst: stats.mst + character.materials.mind as u16 * 2,

View File

@ -1,11 +1,11 @@
use libpso::packet::ship::PlayerChat; use libpso::packet::ship::PlayerChat;
use entity::gateway::EntityGateway; use entity::gateway::EntityGateway;
use networking::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::{ShipServerState, SendShipPacket}; use crate::ship::ship::{ShipServerState, SendShipPacket};
use client::Clients; use crate::ship::client::Clients;
use items::state::ItemState; use crate::ship::items::state::ItemState;
use entity::item::{BankName, BankIdentifier}; use entity::item::{BankName, BankIdentifier};
use pktbuilder::message::bank_item_list; use crate::ship::packet::builder::message::bank_item_list;
async fn default_bank<'a, EG>(id: ClientId, async fn default_bank<'a, EG>(id: ClientId,
entity_gateway: &mut EG, entity_gateway: &mut EG,

View File

@ -6,20 +6,15 @@ use futures::future::BoxFuture;
use libpso::packet::ship::*; use libpso::packet::ship::*;
use libpso::packet::login::Session; use libpso::packet::login::Session;
use networking::serverstate::ClientId; use crate::common::serverstate::ClientId;
use entity::account::{UserAccountEntity, UserSettingsEntity}; use entity::account::{UserAccountEntity, UserSettingsEntity};
use entity::character::CharacterEntity; use entity::character::CharacterEntity;
use entity::item; use entity::item;
use crate::ship::ship::ShipError;
use crate::ship::items;
use maps::area::MapArea; use maps::area::MapArea;
use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem}; use crate::ship::shops::{WeaponShopItem, ToolShopItem, ArmorShopItem};
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("not found {0}")]
NotFound(ClientId),
}
#[derive(Clone, Default)] #[derive(Clone, Default)]
@ -51,7 +46,7 @@ impl Clients {
.await; .await;
let client = clients let client = clients
.get(&client_id) .get(&client_id)
.ok_or(ClientError::NotFound(client_id))? .ok_or_else(|| ShipError::ClientNotFound(client_id))?
.read() .read()
.await; .await;
@ -74,14 +69,17 @@ 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(ClientError::NotFound(*client_id))? .ok_or_else(|| ShipError::ClientNotFound(*client_id))?
.read() .read()
.await; .await;
client_states[cindex].write(c); client_states[cindex].write(c);
} }
let client_states = unsafe { let client_states = unsafe {
std::mem::transmute_copy(&client_states) // TODO: this should just be a normal transmute but due to compiler limitations it
// does not yet work with const generics
// https://github.com/rust-lang/rust/issues/61956
std::mem::transmute_copy::<_, [RwLockReadGuard<ClientState>; N]>(&client_states)
}; };
Ok(func(client_states).await) Ok(func(client_states).await)
@ -97,7 +95,7 @@ impl Clients {
.await; .await;
let mut client = clients let mut client = clients
.get(&client_id) .get(&client_id)
.ok_or(ClientError::NotFound(client_id))? .ok_or_else(|| ShipError::ClientNotFound(client_id))?
.write() .write()
.await; .await;

View File

@ -5,14 +5,15 @@ use serde::{Serialize, Deserialize};
use entity::character::SectionID; use entity::character::SectionID;
use maps::room::{Difficulty, Episode}; use maps::room::{Difficulty, Episode};
use maps::area::MapArea; use maps::area::MapArea;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use maps::object::{MapObject, MapObjectType, FixedBoxDropType}; use maps::object::{MapObject, MapObjectType, FixedBoxDropType};
use crate::rare_drop_table::{RareDropTable, RareDropItem}; use crate::ship::drops::rare_drop_table::{RareDropTable, RareDropItem};
use crate::generic_weapon::GenericWeaponTable; use crate::ship::drops::generic_weapon::GenericWeaponTable;
use crate::generic_armor::GenericArmorTable; use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable; use crate::ship::drops::generic_shield::GenericShieldTable;
use crate::generic_unit::GenericUnitTable; use crate::ship::drops::generic_unit::GenericUnitTable;
use crate::tool_table::ToolTable; use crate::ship::drops::tool_table::ToolTable;
use entity::item::ItemDetail;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct BoxDropRate { struct BoxDropRate {
@ -175,7 +176,7 @@ impl BoxDropTable {
fn random_box_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> { fn random_box_drop<R: Rng>(&self, map_area: &MapArea, rng: &mut R) -> Option<ItemDropType> {
self.rare_drop(map_area, rng).or_else(|| { self.rare_drop(map_area, rng).or_else(|| {
let rate = self.box_rates.rates_by_area(map_area); let rate = self.box_rates.rates_by_area(map_area);
let type_weights = WeightedIndex::new([rate.weapon_rate, rate.armor_rate, rate.shield_rate, rate.unit_rate, let type_weights = WeightedIndex::new(&[rate.weapon_rate, rate.armor_rate, rate.shield_rate, rate.unit_rate,
rate.tool_rate, rate.meseta_rate, rate.nothing_rate]).unwrap(); rate.tool_rate, rate.meseta_rate, rate.nothing_rate]).unwrap();
let btype = type_weights.sample(rng); let btype = type_weights.sample(rng);
match btype { match btype {

View File

View File

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

View File

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

View File

@ -7,8 +7,8 @@ use entity::character::SectionID;
use entity::item::unit::{UnitType, Unit, UnitModifier}; use entity::item::unit::{UnitType, Unit, UnitModifier};
use maps::room::{Difficulty, Episode}; use maps::room::{Difficulty, Episode};
use maps::area::MapArea; use maps::area::MapArea;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use stats::items::{unit_stats, UnitStats}; use crate::ship::item_stats::{unit_stats, UnitStats};

View File

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

View File

@ -5,6 +5,7 @@
// 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;
@ -25,13 +26,13 @@ use maps::monster::MonsterType;
use maps::room::{Difficulty, Episode}; use maps::room::{Difficulty, Episode};
use maps::area::MapArea; use maps::area::MapArea;
use entity::character::SectionID; use entity::character::SectionID;
use crate::generic_weapon::GenericWeaponTable; use crate::ship::drops::generic_weapon::GenericWeaponTable;
use crate::generic_armor::GenericArmorTable; use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable; use crate::ship::drops::generic_shield::GenericShieldTable;
use crate::generic_unit::GenericUnitTable; use crate::ship::drops::generic_unit::GenericUnitTable;
use crate::tool_table::ToolTable; use crate::ship::drops::tool_table::ToolTable;
use crate::rare_drop_table::RareDropTable; use crate::ship::drops::rare_drop_table::RareDropTable;
use crate::box_drop_table::BoxDropTable; use crate::ship::drops::box_drop_table::BoxDropTable;
use maps::object::MapObject; use maps::object::MapObject;
use entity::item::{ItemType, weapon, armor, shield, unit, mag, tool, tech, esweapon}; use entity::item::{ItemType, weapon, armor, shield, unit, mag, tool, tech, esweapon};
@ -121,12 +122,7 @@ pub struct ItemDrop {
} }
pub trait DropTable { pub struct 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,
@ -138,12 +134,11 @@ pub struct StandardDropTable {
rng: rand_chacha::ChaCha20Rng, rng: rand_chacha::ChaCha20Rng,
} }
impl StandardDropTable { impl DropTable {
#[allow(clippy::new_ret_no_self)] pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
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");
Box::new(StandardDropTable { DropTable {
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),
@ -153,7 +148,7 @@ impl StandardDropTable {
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 {
@ -183,10 +178,8 @@ impl StandardDropTable {
MonsterDropType::None => None, MonsterDropType::None => None,
} }
} }
}
impl DropTable for StandardDropTable { pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
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);
@ -214,7 +207,7 @@ impl DropTable for StandardDropTable {
} }
} }
fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> { pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
self.box_table.get_drop(map_area, object, &mut self.rng) self.box_table.get_drop(map_area, object, &mut self.rng)
} }
} }
@ -261,8 +254,8 @@ impl DropTableBuilder {
self self
} }
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> { pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
Box::new(StandardDropTable { DropTable {
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()
@ -275,10 +268,11 @@ 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::*;

View File

@ -11,10 +11,10 @@ use entity::character::SectionID;
use maps::monster::MonsterType; use maps::monster::MonsterType;
use maps::room::{Difficulty, Episode}; use maps::room::{Difficulty, Episode};
use maps::area::MapArea; use maps::area::MapArea;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use crate::generic_weapon::AttributeTable; use crate::ship::drops::generic_weapon::AttributeTable;
use crate::generic_armor::GenericArmorTable; use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable; use crate::ship::drops::generic_shield::GenericShieldTable;
type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>; type ItemParseFn = Box<dyn Fn(&String) -> Option<RareDropItem>>;

View File

@ -7,7 +7,7 @@ use entity::item::tech::{Technique, TechniqueDisk};
use maps::room::{Difficulty, Episode}; use maps::room::{Difficulty, Episode};
use maps::area::MapArea; use maps::area::MapArea;
use entity::character::SectionID; use entity::character::SectionID;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};

View File

@ -7,8 +7,8 @@ use entity::item::tool::{Tool, ToolType};
use maps::room::{Difficulty, Episode}; use maps::room::{Difficulty, Episode};
use maps::area::MapArea; use maps::area::MapArea;
use entity::character::SectionID; use entity::character::SectionID;
use crate::{ItemDropType, load_data_file}; use crate::ship::drops::{ItemDropType, load_data_file};
use crate::tech_table::TechniqueTable; use crate::ship::drops::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)]

View File

@ -1,5 +1,5 @@
// 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::ClientItemId; use crate::ship::items::ClientItemId;
use entity::item::{Meseta, ItemNote}; use entity::item::{Meseta, ItemNote};
use async_std::sync::Arc; use async_std::sync::Arc;
use std::future::Future; use std::future::Future;
@ -8,20 +8,23 @@ 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 entity::character::{CharacterEntity, CharacterEntityId}; use entity::character::{CharacterEntity, CharacterEntityId};
use entity::gateway::{EntityGateway, EntityGatewayTransaction}; use entity::gateway::{EntityGateway, EntityGatewayTransaction};
use entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier}; use entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier};
use entity::item::tool::Tool; use entity::item::tool::Tool;
use entity::room::RoomEntityId; use entity::room::RoomEntityId;
use maps::area::MapArea; use maps::area::MapArea;
use crate::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail}; use crate::ship::ship::SendShipPacket;
use crate::bank::{BankItem, BankItemDetail}; use crate::ship::items::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::inventory::{InventoryItem, InventoryItemDetail}; use crate::ship::items::bank::{BankItem, BankItemDetail};
use crate::floor::{FloorItem, FloorItemDetail}; use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use crate::apply_item::{apply_item, ApplyItemAction}; use crate::ship::items::floor::{FloorItem, FloorItemDetail};
use shops::ShopItem; use crate::ship::items::apply_item::{apply_item, ApplyItemAction};
use drops::{ItemDrop, ItemDropType}; use crate::ship::shops::ShopItem;
use location::AreaClient; use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::packet::builder;
use crate::ship::location::AreaClient;
use maps::monster::MonsterType; use maps::monster::MonsterType;
pub enum TriggerCreateItem { pub enum TriggerCreateItem {
@ -29,12 +32,6 @@ pub enum TriggerCreateItem {
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
@ -1147,7 +1144,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<CreateItem>), anyhow::Error>> -> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<SendShipPacket>), anyhow::Error>>
where where
EG: EntityGateway, EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a, TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
@ -1164,7 +1161,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 = CreateItem::Stacked(area_client, item_id, tool, 1); let create_item = builder::message::create_stacked_item(area_client, item_id, &tool, 1).map_err(|_err| ItemStateError::Dummy)?;
let item_detail = StackedItemDetail { let item_detail = StackedItemDetail {
entity_ids: vec![new_item.id], entity_ids: vec![new_item.id],
tool tool
@ -1176,7 +1173,7 @@ where
entity_id: new_item.id, entity_id: new_item.id,
item: item_detail, item: item_detail,
}; };
let create_item = CreateItem::Individual(area_client, item_id, item_detail.clone()); let create_item = builder::message::create_individual_item(area_client, item_id, &item_detail).map_err(|_err| ItemStateError::Dummy)?;
(InventoryItemDetail::Individual(item_detail), create_item) (InventoryItemDetail::Individual(item_detail), create_item)
}; };
@ -1190,8 +1187,7 @@ 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()

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