Compare commits

..

No commits in common. "b42056419ab1b411c57cbcb617d886bc124e2bcb" and "1f7dd1eafe9933be8023821ccb961b7e06b4a9e0" have entirely different histories.

145 changed files with 2107 additions and 2484 deletions

View File

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

View File

@ -4,40 +4,8 @@ version = "0.1.0"
authors = ["Jake Probst <jake.probst@gmail.com>"]
edition = "2021"
[workspace]
members = [
"client",
"drops",
"entity",
"items",
"location",
"maps",
"networking",
"pktbuilder",
"quests",
"room",
"shops",
"stats",
"trade",
]
[workspace.dependencies]
entity = { path = "./entity" }
maps = { path = "./maps" }
networking = { path = "./networking" }
shops = { path = "./shops" }
stats = { path = "./stats" }
items = { path = "./items" }
pktbuilder = { path = "./pktbuilder" }
quests = { path = "./quests" }
location = { path = "./location" }
client = { path = "./client" }
drops = { path = "./drops" }
trade = { path = "./trade" }
room = { path = "./room" }
[dependencies]
libpso = { git = "http://git.sharnoth.com/jake/libpso" }
async-std = { version = "1.9.0", features = ["unstable", "attributes"] }
futures = "0.3.5"
rand = "0.7.3"
@ -65,42 +33,3 @@ sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgre
strum = "0.19.5"
strum_macros = "0.19"
anyhow = { version = "1.0.68", features = ["backtrace"] }
[dependencies]
entity = { workspace = true }
maps = { workspace = true }
networking = { workspace = true }
shops = { workspace = true }
stats = { workspace = true }
items = { workspace = true }
pktbuilder = { workspace = true }
quests = { workspace = true }
location = { workspace = true }
client = { workspace = true }
drops = { workspace = true }
trade = { workspace = true }
room = { workspace = true }
libpso = { workspace = true }
ages-prs = { workspace = true }
anyhow = { workspace = true }
async-recursion = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
bcrypt = { workspace = true }
byteorder = { workspace = true }
chrono = { workspace = true }
crc = { workspace = true }
derive_more = { workspace = true }
enum-utils = { workspace = true }
fern = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
ron = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
toml = { workspace = true }
thiserror = { workspace = true }

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

@ -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,3 +0,0 @@
use refinery::include_migration_mods;
include_migration_mods!("src/gateway/postgres/migrations");

View File

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

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

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

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

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

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,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,17 +0,0 @@
[package]
name = "shops"
version = "0.1.0"
edition = "2021"
[dependencies]
maps = { workspace = true }
stats = { workspace = true }
entity = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
toml = { workspace = true }
serde = { workspace = true }

View File

@ -1,95 +0,0 @@
mod weapon;
mod tool;
mod armor;
use async_std::sync::{Arc, Mutex};
use futures::future::OptionFuture;
use std::collections::HashMap;
use entity::item::ItemDetail;
use maps::room::Difficulty;
use entity::character::SectionID;
pub use weapon::{WeaponShop, WeaponShopItem};
pub use tool::{ToolShop, ToolShopItem};
pub use armor::{ArmorShop, ArmorShopItem};
pub trait ShopItem {
fn price(&self) -> usize;
fn as_bytes(&self) -> [u8; 12];
fn as_item(&self) -> ItemDetail;
}
#[async_trait::async_trait]
pub trait ItemShops {
async fn generate_weapon_list(&self, difficulty: Difficulty, section_id: SectionID, char_level: usize) -> Option<Vec<WeaponShopItem>>;
async fn generate_tool_list(&self, char_level: usize) -> Vec<ToolShopItem>;
async fn generate_armor_list(&self, char_level: usize) -> Vec<ArmorShopItem>;
}
#[derive(Clone)]
pub struct StandardItemShops {
weapon_shop: HashMap<(Difficulty, SectionID), Arc<Mutex<WeaponShop<rand_chacha::ChaCha20Rng>>>>,
tool_shop: Arc<Mutex<ToolShop<rand_chacha::ChaCha20Rng>>>,
armor_shop: Arc<Mutex<ArmorShop<rand_chacha::ChaCha20Rng>>>,
}
impl Default for StandardItemShops {
fn default() -> StandardItemShops {
let difficulty = [Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate];
let section_id = [SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill];
let mut weapon_shop = HashMap::new();
for d in difficulty.iter() {
for id in section_id.iter() {
weapon_shop.insert((*d, *id), Arc::new(Mutex::new(WeaponShop::new(*d, *id))));
}
}
StandardItemShops {
weapon_shop,
tool_shop: Arc::new(Mutex::new(ToolShop::default())),
armor_shop: Arc::new(Mutex::new(ArmorShop::default())),
}
}
}
#[async_trait::async_trait]
impl ItemShops for StandardItemShops {
async fn generate_weapon_list(&self, difficulty: Difficulty, section_id: SectionID, char_level: usize) -> Option<Vec<WeaponShopItem>> {
OptionFuture::from(
self.weapon_shop
.get(&(difficulty, section_id))
.map(|shop| async {
shop
.lock()
.await
.generate_weapon_list(char_level)
})).await
}
async fn generate_tool_list(&self, char_level: usize) -> Vec<ToolShopItem> {
self.tool_shop
.lock()
.await
.generate_tool_list(char_level)
}
async fn generate_armor_list(&self, char_level: usize) -> Vec<ArmorShopItem> {
self.armor_shop
.lock()
.await
.generate_armor_list(char_level)
}
}
pub enum ShopType {
Weapon,
Tool,
Armor
}

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use log::info;
use entity::gateway::postgres::PostgresGateway;
use log::{info};
use elseware::entity::gateway::postgres::PostgresGateway;
use elseware::ship::ship::ShipServerStateBuilder;
use networking::interserver::AuthToken;
use elseware::common::interserver::AuthToken;
fn main() {
let colors = fern::colors::ColoredLevelConfig::new()
@ -49,10 +49,10 @@ fn main() {
let sub_ship_state = ship_state.clone();
let ship_loop = async_std::task::spawn(async move {
networking::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
elseware::common::mainloop::run_server(sub_ship_state, elseware::ship::ship::SHIP_PORT).await;
});
let inter_ship_loop = async_std::task::spawn(async move {
networking::mainloop::run_interserver_connect(ship_state, shipgate_ip, elseware::login::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");

View File

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

View File

@ -1,6 +1,6 @@
use std::fs::File;
use serde_json::Value;
use entity::character::CharacterClass;
use crate::entity::character::CharacterClass;
use std::sync::LazyLock;
pub static LEVEL_TABLE: LazyLock<CharacterLevelTable> = LazyLock::new(CharacterLevelTable::default);

View File

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

View File

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

View File

@ -1,6 +1,7 @@
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/

View File

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

View File

@ -1,10 +1,10 @@
use thiserror::Error;
use futures::future::{Future, BoxFuture};
use crate::account::*;
use crate::character::*;
use crate::item::*;
use crate::room::*;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::entity::room::*;
// TODO: better granularity?

View File

@ -2,11 +2,11 @@ use std::collections::BTreeMap;
use std::convert::TryInto;
use futures::future::{Future, BoxFuture};
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::entity::room::*;
use async_std::sync::{Arc, Mutex};

View File

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

View File

@ -4,13 +4,13 @@ use std::convert::Into;
use serde::{Serialize, Deserialize};
use libpso::character::settings;
use libpso::util::vec_to_array;
use crate::account::*;
use crate::character::*;
use crate::item::*;
use crate::room::*;
use maps::area::MapArea;
use maps::room::{Episode, Difficulty};
use maps::monster::MonsterType;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::entity::room::*;
use crate::ship::map::MapArea;
use crate::ship::room::{Episode, Difficulty};
use crate::ship::monster::MonsterType;
#[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount {

View File

@ -1,16 +1,16 @@
// this lint is currently bugged and suggests incorrect code https://github.com/rust-lang/rust-clippy/issues/9123
#![allow(clippy::explicit_auto_deref)]
use std::convert::{From, Into};
use std::convert::{From, TryFrom, Into};
use futures::future::{Future, BoxFuture};
use futures::stream::{StreamExt, FuturesOrdered};
use async_std::sync::{Arc, Mutex};
use libpso::character::guildcard;
use crate::account::*;
use crate::character::*;
use crate::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::item::*;
use crate::room::*;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::entity::room::*;
use super::models::*;
use sqlx::postgres::PgPoolOptions;
@ -19,7 +19,7 @@ use sqlx::Connection;
mod embedded {
use refinery::embed_migrations;
embed_migrations!("src/gateway/postgres/migrations");
embed_migrations!("src/entity/gateway/postgres/migrations");
}

View File

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

View File

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

View File

@ -9,11 +9,11 @@ pub mod mag;
pub mod esweapon;
use serde::{Serialize, Deserialize};
use crate::character::CharacterEntityId;
use crate::room::RoomEntityId;
use maps::area::MapArea;
use maps::monster::MonsterType;
//use crate::ship::drops::ItemDropType;
use crate::entity::character::CharacterEntityId;
use crate::entity::room::RoomEntityId;
use crate::ship::map::MapArea;
use crate::ship::monster::MonsterType;
use crate::ship::drops::ItemDropType;
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ItemEntityId(pub u32);
@ -156,6 +156,26 @@ impl ItemDetail {
}
}
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
match item_type {
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
_ => None,
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
ItemDetail::Weapon(w) => w.as_bytes(),

View File

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

View File

@ -1,8 +1,8 @@
use serde::{Serialize, Deserialize};
use crate::character::{CharacterEntityId, SectionID};
use maps::room::{Episode, Difficulty};
use crate::entity::character::{CharacterEntityId, SectionID};
use crate::ship::room::{Episode, Difficulty};
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]

View File

@ -1,15 +1,17 @@
#![allow(clippy::type_complexity)]
#![allow(incomplete_features)]
#![feature(inline_const)]
#![feature(extract_if)]
#![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 common;
pub mod entity;
pub mod patch;
pub mod login;
pub mod ship;

View File

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

View File

@ -11,11 +11,11 @@ use libpso::{PacketParseError, PSOPacket};
use libpso::crypto::bb::PSOBBCipher;
use libpso::util::array_to_utf8;
use networking::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use networking::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use entity::gateway::EntityGateway;
use entity::account::{UserAccountEntity};
use crate::entity::gateway::EntityGateway;
use crate::entity::account::{UserAccountEntity};
pub const LOGIN_PORT: u16 = 12000;
pub const COMMUNICATION_PORT: u16 = 12123;
@ -178,8 +178,8 @@ impl<EG: EntityGateway + Clone> ServerState for LoginServerState<EG> {
#[cfg(test)]
mod test {
use super::*;
use entity::account::{UserAccountId};
use entity::gateway::{EntityGatewayTransaction, GatewayError};
use crate::entity::account::{UserAccountId};
use crate::entity::gateway::{EntityGatewayTransaction, GatewayError};
const LOGIN_PACKET: RecvLoginPacket = RecvLoginPacket::Login(Login {
tag: 65536,
@ -205,15 +205,12 @@ mod test {
}
});
#[derive(Clone)]
struct LoginTestDb;
impl EntityGateway for LoginTestDb {
type Transaction<'t> = LoginTestDb where Self: 't;
impl EntityGateway for () {
type Transaction<'t> = () where Self: 't;
}
impl EntityGatewayTransaction for LoginTestDb {
type ParentGateway = LoginTestDb;
impl EntityGatewayTransaction for () {
type ParentGateway = ();
}
#[async_std::test]
@ -224,7 +221,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
type Transaction<'t> = () where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {
@ -283,7 +280,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
type Transaction<'t> = () where Self: 't;
async fn get_user_by_name(&mut self, _name: String) -> Result<UserAccountEntity, GatewayError> {
Err(GatewayError::Error)
}
@ -318,7 +315,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
type Transaction<'t> = () where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {
@ -368,7 +365,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
type Transaction<'t> = LoginTestDb where Self: 't;
type Transaction<'t> = () where Self: 't;
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {

View File

@ -1,432 +1,2 @@
use std::collections::{HashMap, HashSet};
use std::fs;
use std::io;
use std::io::{Read};
use std::path::{Path, PathBuf};
use rand::Rng;
use crc::{crc32, Hasher32};
use libpso::{PacketParseError, PSOPacket};
use libpso::packet::patch::*;
use libpso::crypto::pc::PSOPCCipher;
use ron::de::from_str;
use serde::Deserialize;
use networking::mainloop::{NetworkError};
use networking::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
#[derive(Debug)]
pub enum PatchError {
NetworkError(NetworkError),
IOError(std::io::Error),
}
impl From<NetworkError> for PatchError {
fn from(err: NetworkError) -> PatchError {
PatchError::NetworkError(err)
}
}
impl From<std::io::Error> for PatchError {
fn from(err: std::io::Error) -> PatchError {
PatchError::IOError(err)
}
}
#[derive(Debug, Clone)]
pub struct PatchFile {
path: PathBuf,
checksum: u32,
size: u32,
}
pub enum PatchTreeIterItem {
Directory(PathBuf),
File(PathBuf, u32),
UpDirectory,
}
#[derive(Debug, Clone)]
pub enum PatchFileTree {
Directory(PathBuf, Vec<PatchFileTree>),
File(PathBuf, u32), // file_id
}
impl PatchFileTree {
fn iter_dir(tree: &PatchFileTree) -> Vec<PatchTreeIterItem> {
let mut v = Vec::new();
match tree {
PatchFileTree::Directory(dir, files) => {
v.push(PatchTreeIterItem::Directory(dir.clone()));
for file in files {
v.append(&mut PatchFileTree::iter_dir(file));
}
v.push(PatchTreeIterItem::UpDirectory);
},
PatchFileTree::File(path, id) => {
v.push(PatchTreeIterItem::File(path.clone(), *id));
}
}
v
}
pub fn flatten(&self) -> Vec<PatchTreeIterItem> {
PatchFileTree::iter_dir(self)
}
}
#[derive(Debug)]
pub enum RecvPatchPacket {
PatchWelcomeReply(PatchWelcomeReply),
LoginReply(LoginReply),
FileInfoReply(FileInfoReply),
FileInfoListEnd(FileInfoListEnd),
}
impl RecvServerPacket for RecvPatchPacket {
fn from_bytes(data: &[u8]) -> Result<RecvPatchPacket, PacketParseError> {
match data[2] {
0x02 => Ok(RecvPatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)),
0x04 => Ok(RecvPatchPacket::LoginReply(LoginReply::from_bytes(data)?)),
0x0F => Ok(RecvPatchPacket::FileInfoReply(FileInfoReply::from_bytes(data)?)),
0x10 => Ok(RecvPatchPacket::FileInfoListEnd(FileInfoListEnd::from_bytes(data)?)),
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
}
}
}
#[derive(Debug)]
pub enum SendPatchPacket {
ChangeDirectory(ChangeDirectory),
EndFileSend(EndFileSend),
FileInfo(FileInfo),
FileSend(Box<FileSend>),
FilesToPatchMetadata(FilesToPatchMetadata),
FinalizePatching(FinalizePatching),
Message(Message),
PatchEndList(PatchEndList),
PatchStartList(PatchStartList),
PatchWelcome(PatchWelcome),
RequestLogin(RequestLogin),
StartFileSend(StartFileSend),
UpOneDirectory(UpOneDirectory),
}
impl SendServerPacket for SendPatchPacket {
fn as_bytes(&self) -> Vec<u8> {
match self {
SendPatchPacket::ChangeDirectory(pkt) => pkt.as_bytes(),
SendPatchPacket::EndFileSend(pkt) => pkt.as_bytes(),
SendPatchPacket::FileInfo(pkt) => pkt.as_bytes(),
SendPatchPacket::FileSend(pkt) => pkt.as_bytes(),
SendPatchPacket::FilesToPatchMetadata(pkt) => pkt.as_bytes(),
SendPatchPacket::FinalizePatching(pkt) => pkt.as_bytes(),
SendPatchPacket::Message(pkt) => pkt.as_bytes(),
SendPatchPacket::PatchEndList(pkt) => pkt.as_bytes(),
SendPatchPacket::PatchStartList(pkt) => pkt.as_bytes(),
SendPatchPacket::PatchWelcome(pkt) => pkt.as_bytes(),
SendPatchPacket::RequestLogin(pkt) => pkt.as_bytes(),
SendPatchPacket::StartFileSend(pkt) => pkt.as_bytes(),
SendPatchPacket::UpOneDirectory(pkt) => pkt.as_bytes(),
}
}
}
#[derive(Clone)]
pub struct PatchServerState {
patch_file_tree: PatchFileTree,
patch_file_lookup: HashMap<u32, PatchFile>,
patch_file_info: Vec<FileInfoReply>,
patch_motd: String,
}
impl PatchServerState {
pub fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap<u32, PatchFile>, patch_motd: String) -> PatchServerState {
PatchServerState {
patch_file_tree,
patch_file_lookup,
patch_file_info: Vec::new(),
patch_motd,
}
}
}
#[async_trait::async_trait]
impl ServerState for PatchServerState {
type SendPacket = SendPatchPacket;
type RecvPacket = RecvPatchPacket;
type Cipher = PSOPCCipher;
type PacketError = PatchError;
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, PatchError> {
let mut rng = rand::thread_rng();
let key_in: u32 = rng.gen();
let key_out: u32 = rng.gen();
Ok(vec![OnConnect::Packet(SendPatchPacket::PatchWelcome(PatchWelcome::new(key_out, key_in))),
OnConnect::Cipher(PSOPCCipher::new(key_in), PSOPCCipher::new(key_out))
])
}
async fn handle(&mut self, id: ClientId, pkt: RecvPatchPacket) -> Result<Vec<(ClientId, SendPatchPacket)>, PatchError> {
Ok(match pkt {
RecvPatchPacket::PatchWelcomeReply(_pkt) => {
vec![SendPatchPacket::RequestLogin(RequestLogin {})]
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
},
RecvPatchPacket::LoginReply(_pkt) => {
let mut pkts = vec![SendPatchPacket::Message(Message::new(self.patch_motd.clone()))];
pkts.append(&mut get_file_list_packets(&self.patch_file_tree));
pkts.push(SendPatchPacket::PatchEndList(PatchEndList {}));
pkts
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
},
RecvPatchPacket::FileInfoReply(pkt) => {
self.patch_file_info.push(pkt);
Vec::new()
},
RecvPatchPacket::FileInfoListEnd(_pkt) => {
let need_update = self.patch_file_info.iter()
.filter(|file_info| does_file_need_updating(file_info, &self.patch_file_lookup))
.collect::<Vec<_>>();
let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size);
let total_files = need_update.len() as u32;
vec![SendPatchPacket::FilesToPatchMetadata(FilesToPatchMetadata::new(total_size, total_files)),
SendPatchPacket::PatchStartList(PatchStartList {})]
.into_iter()
.chain(SendFileIterator::new(self))
.map(move |pkt| (id, pkt))
.collect()
}
})
}
async fn on_disconnect(&mut self, _id: ClientId) -> Result<Vec<(ClientId, SendPatchPacket)>, PatchError> {
Ok(Vec::new())
}
}
fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap<u32, PatchFile>) -> PatchFileTree {
let paths = fs::read_dir(basedir).expect("could not read directory");
let mut files = Vec::new();
let mut dirs = Vec::new();
for p in paths {
let path = p.expect("not a real path").path();
let patch_path = path.strip_prefix(basedir).unwrap();
if path.is_dir() {
dirs.push(load_patch_dir(path.to_str().unwrap(), patch_path.to_str().unwrap(), file_ids));
}
else {
files.push(PatchFileTree::File(patch_path.to_path_buf(), file_ids.len() as u32));
let (checksum, size) = get_checksum_and_size(&path).unwrap();
file_ids.insert(file_ids.len() as u32, PatchFile {
path,
checksum,
size,
});
}
}
files.append(&mut dirs);
PatchFileTree::Directory(PathBuf::from(patchbase), files)
}
pub fn generate_patch_tree(basedir: &str) -> (PatchFileTree, HashMap<u32, PatchFile>) {
let mut file_ids = HashMap::new();
let patch_tree = load_patch_dir(basedir, "", &mut file_ids);
(patch_tree, file_ids)
}
fn get_file_list_packets(patch_file_tree: &PatchFileTree) -> Vec<SendPatchPacket> {
let mut pkts = Vec::new();
for item in patch_file_tree.flatten() {
match item {
PatchTreeIterItem::Directory(path) => {
pkts.push(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap())));
},
PatchTreeIterItem::File(path, id) => {
pkts.push(SendPatchPacket::FileInfo(FileInfo::new(path.to_str().unwrap(), id)));
},
PatchTreeIterItem::UpDirectory => {
pkts.push(SendPatchPacket::UpOneDirectory(UpOneDirectory {}));
}
}
}
pkts
}
fn get_checksum_and_size(path: &Path) -> Result<(u32, u32), PatchError> {
let file = fs::File::open(path)?;
let size = file.metadata().unwrap().len();
let mut crc = crc32::Digest::new(crc32::IEEE);
let mut buf = [0u8; 1024 * 32];
let mut reader = io::BufReader::new(file);
while let Ok(len) = reader.read(&mut buf) {
if len == 0 {
break;
}
crc.write(&buf[0..len]);
}
Ok((crc.sum32(), size as u32))
}
fn does_file_need_updating(file_info: &FileInfoReply, patch_file_lookup: &HashMap<u32, PatchFile>) -> bool {
let patch_file = patch_file_lookup.get(&file_info.id).unwrap();
patch_file.checksum != file_info.checksum || patch_file.size != file_info.size
}
struct SendFileIterator {
done: bool,
file_iter: Box<dyn Iterator<Item = PatchTreeIterItem> + Send>,
patch_file_lookup: HashMap<u32, PatchFile>,
current_file: Option<io::BufReader<fs::File>>,
chunk_num: u32,
}
impl SendFileIterator {
fn new(state: &PatchServerState) -> SendFileIterator {
let file_ids_to_update = state.patch_file_info.iter()
.filter(|file_info| does_file_need_updating(file_info, &state.patch_file_lookup))
.map(|k| k.id)
.collect::<HashSet<_>>();
SendFileIterator {
done: false,
patch_file_lookup: state.patch_file_lookup.clone(),
file_iter: Box::new(state.patch_file_tree.flatten().into_iter().filter(move |file| {
match file {
PatchTreeIterItem::File(_path, id) => {
file_ids_to_update.contains(id)
},
_ => true,
}
})),
current_file: None,
chunk_num: 0,
}
}
}
impl Iterator for SendFileIterator {
type Item = SendPatchPacket;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
match self.current_file {
Some(ref mut file) => {
let mut buf = [0u8; PATCH_FILE_CHUNK_SIZE as usize];
let len = file.read(&mut buf).unwrap();
if len == 0 {
self.current_file = None;
self.chunk_num = 0;
Some(SendPatchPacket::EndFileSend(EndFileSend::new()))
}
else {
let mut crc = crc32::Digest::new(crc32::IEEE);
crc.write(&buf[0..len]);
let pkt = SendPatchPacket::FileSend(Box::new(FileSend {
chunk_num: self.chunk_num,
checksum: crc.sum32(),
chunk_size: len as u32,
buffer: buf,
}));
self.chunk_num += 1;
Some(pkt)
}
},
None => {
match self.file_iter.next() {
Some(next_file) => {
match next_file {
PatchTreeIterItem::Directory(path) => {
Some(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap())))
},
PatchTreeIterItem::File(path, id) => {
let patch_file = self.patch_file_lookup.get(&id).unwrap();
let file = fs::File::open(&patch_file.path).unwrap();
let size = file.metadata().unwrap().len();
self.current_file = Some(io::BufReader::new(file));
Some(SendPatchPacket::StartFileSend(StartFileSend::new(path.to_str().unwrap(), size as u32, id)))
},
PatchTreeIterItem::UpDirectory => {
Some(SendPatchPacket::UpOneDirectory(UpOneDirectory {}))
},
}
},
None => {
self.current_file = None;
self.done = true;
Some(SendPatchPacket::FinalizePatching(FinalizePatching {}))
}
}
}
}
}
}
#[derive(Debug, Deserialize)]
pub struct PatchConfig {
pub path: String,
pub ip: String, // TODO: this does nothing
pub port: u16,
}
pub fn load_config() -> PatchConfig {
let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) {
Err(err) => panic!("Failed to open patch.ron config file. \n{err}"),
Ok(ini_file) => ini_file,
};
let mut s = String::new();
if let Err(err) = (&ini_file).read_to_string(&mut s) {
panic!("Failed to read patch.ron config file. \n{err}");
}
let config: PatchConfig = match from_str(s.as_str()) {
Ok(config) => config,
Err(err) => panic!("Failed to load values from patch.ron \n{err}"),
};
config
}
pub fn load_config_env() -> PatchConfig {
let patch_path = std::env::var("PATCHFILE_DIR").unwrap();
let patch_port = std::env::var("PATCH_PORT").unwrap().parse().unwrap();
PatchConfig {
path: patch_path,
ip: "127.0.0.1".into(),
port: patch_port,
}
}
pub fn load_motd() -> String {
if let Ok(m) = fs::read_to_string("patch.motd") {
m
}
else {
"Welcome to Elseware!".to_string()
}
}
#[allow(clippy::module_inception)]
pub mod patch;

432
src/patch/patch.rs Normal file
View File

@ -0,0 +1,432 @@
use std::collections::{HashMap, HashSet};
use std::fs;
use std::io;
use std::io::{Read};
use std::path::{Path, PathBuf};
use rand::Rng;
use crc::{crc32, Hasher32};
use libpso::{PacketParseError, PSOPacket};
use libpso::packet::patch::*;
use libpso::crypto::pc::PSOPCCipher;
use ron::de::from_str;
use serde::Deserialize;
use crate::common::mainloop::{NetworkError};
use crate::common::serverstate::{RecvServerPacket, SendServerPacket, ServerState, OnConnect, ClientId};
#[derive(Debug)]
pub enum PatchError {
NetworkError(NetworkError),
IOError(std::io::Error),
}
impl From<NetworkError> for PatchError {
fn from(err: NetworkError) -> PatchError {
PatchError::NetworkError(err)
}
}
impl From<std::io::Error> for PatchError {
fn from(err: std::io::Error) -> PatchError {
PatchError::IOError(err)
}
}
#[derive(Debug, Clone)]
pub struct PatchFile {
path: PathBuf,
checksum: u32,
size: u32,
}
pub enum PatchTreeIterItem {
Directory(PathBuf),
File(PathBuf, u32),
UpDirectory,
}
#[derive(Debug, Clone)]
pub enum PatchFileTree {
Directory(PathBuf, Vec<PatchFileTree>),
File(PathBuf, u32), // file_id
}
impl PatchFileTree {
fn iter_dir(tree: &PatchFileTree) -> Vec<PatchTreeIterItem> {
let mut v = Vec::new();
match tree {
PatchFileTree::Directory(dir, files) => {
v.push(PatchTreeIterItem::Directory(dir.clone()));
for file in files {
v.append(&mut PatchFileTree::iter_dir(file));
}
v.push(PatchTreeIterItem::UpDirectory);
},
PatchFileTree::File(path, id) => {
v.push(PatchTreeIterItem::File(path.clone(), *id));
}
}
v
}
pub fn flatten(&self) -> Vec<PatchTreeIterItem> {
PatchFileTree::iter_dir(self)
}
}
#[derive(Debug)]
pub enum RecvPatchPacket {
PatchWelcomeReply(PatchWelcomeReply),
LoginReply(LoginReply),
FileInfoReply(FileInfoReply),
FileInfoListEnd(FileInfoListEnd),
}
impl RecvServerPacket for RecvPatchPacket {
fn from_bytes(data: &[u8]) -> Result<RecvPatchPacket, PacketParseError> {
match data[2] {
0x02 => Ok(RecvPatchPacket::PatchWelcomeReply(PatchWelcomeReply::from_bytes(data)?)),
0x04 => Ok(RecvPatchPacket::LoginReply(LoginReply::from_bytes(data)?)),
0x0F => Ok(RecvPatchPacket::FileInfoReply(FileInfoReply::from_bytes(data)?)),
0x10 => Ok(RecvPatchPacket::FileInfoListEnd(FileInfoListEnd::from_bytes(data)?)),
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
}
}
}
#[derive(Debug)]
pub enum SendPatchPacket {
ChangeDirectory(ChangeDirectory),
EndFileSend(EndFileSend),
FileInfo(FileInfo),
FileSend(Box<FileSend>),
FilesToPatchMetadata(FilesToPatchMetadata),
FinalizePatching(FinalizePatching),
Message(Message),
PatchEndList(PatchEndList),
PatchStartList(PatchStartList),
PatchWelcome(PatchWelcome),
RequestLogin(RequestLogin),
StartFileSend(StartFileSend),
UpOneDirectory(UpOneDirectory),
}
impl SendServerPacket for SendPatchPacket {
fn as_bytes(&self) -> Vec<u8> {
match self {
SendPatchPacket::ChangeDirectory(pkt) => pkt.as_bytes(),
SendPatchPacket::EndFileSend(pkt) => pkt.as_bytes(),
SendPatchPacket::FileInfo(pkt) => pkt.as_bytes(),
SendPatchPacket::FileSend(pkt) => pkt.as_bytes(),
SendPatchPacket::FilesToPatchMetadata(pkt) => pkt.as_bytes(),
SendPatchPacket::FinalizePatching(pkt) => pkt.as_bytes(),
SendPatchPacket::Message(pkt) => pkt.as_bytes(),
SendPatchPacket::PatchEndList(pkt) => pkt.as_bytes(),
SendPatchPacket::PatchStartList(pkt) => pkt.as_bytes(),
SendPatchPacket::PatchWelcome(pkt) => pkt.as_bytes(),
SendPatchPacket::RequestLogin(pkt) => pkt.as_bytes(),
SendPatchPacket::StartFileSend(pkt) => pkt.as_bytes(),
SendPatchPacket::UpOneDirectory(pkt) => pkt.as_bytes(),
}
}
}
#[derive(Clone)]
pub struct PatchServerState {
patch_file_tree: PatchFileTree,
patch_file_lookup: HashMap<u32, PatchFile>,
patch_file_info: Vec<FileInfoReply>,
patch_motd: String,
}
impl PatchServerState {
pub fn new(patch_file_tree: PatchFileTree, patch_file_lookup: HashMap<u32, PatchFile>, patch_motd: String) -> PatchServerState {
PatchServerState {
patch_file_tree,
patch_file_lookup,
patch_file_info: Vec::new(),
patch_motd,
}
}
}
#[async_trait::async_trait]
impl ServerState for PatchServerState {
type SendPacket = SendPatchPacket;
type RecvPacket = RecvPatchPacket;
type Cipher = PSOPCCipher;
type PacketError = PatchError;
async fn on_connect(&mut self, _id: ClientId) -> Result<Vec<OnConnect<Self::SendPacket, Self::Cipher>>, PatchError> {
let mut rng = rand::thread_rng();
let key_in: u32 = rng.gen();
let key_out: u32 = rng.gen();
Ok(vec![OnConnect::Packet(SendPatchPacket::PatchWelcome(PatchWelcome::new(key_out, key_in))),
OnConnect::Cipher(PSOPCCipher::new(key_in), PSOPCCipher::new(key_out))
])
}
async fn handle(&mut self, id: ClientId, pkt: RecvPatchPacket) -> Result<Vec<(ClientId, SendPatchPacket)>, PatchError> {
Ok(match pkt {
RecvPatchPacket::PatchWelcomeReply(_pkt) => {
vec![SendPatchPacket::RequestLogin(RequestLogin {})]
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
},
RecvPatchPacket::LoginReply(_pkt) => {
let mut pkts = vec![SendPatchPacket::Message(Message::new(self.patch_motd.clone()))];
pkts.append(&mut get_file_list_packets(&self.patch_file_tree));
pkts.push(SendPatchPacket::PatchEndList(PatchEndList {}));
pkts
.into_iter()
.map(move |pkt| (id, pkt))
.collect()
},
RecvPatchPacket::FileInfoReply(pkt) => {
self.patch_file_info.push(pkt);
Vec::new()
},
RecvPatchPacket::FileInfoListEnd(_pkt) => {
let need_update = self.patch_file_info.iter()
.filter(|file_info| does_file_need_updating(file_info, &self.patch_file_lookup))
.collect::<Vec<_>>();
let total_size = need_update.iter().fold(0, |a, file_info| a + file_info.size);
let total_files = need_update.len() as u32;
vec![SendPatchPacket::FilesToPatchMetadata(FilesToPatchMetadata::new(total_size, total_files)),
SendPatchPacket::PatchStartList(PatchStartList {})]
.into_iter()
.chain(SendFileIterator::new(self))
.map(move |pkt| (id, pkt))
.collect()
}
})
}
async fn on_disconnect(&mut self, _id: ClientId) -> Result<Vec<(ClientId, SendPatchPacket)>, PatchError> {
Ok(Vec::new())
}
}
fn load_patch_dir(basedir: &str, patchbase: &str, file_ids: &mut HashMap<u32, PatchFile>) -> PatchFileTree {
let paths = fs::read_dir(basedir).expect("could not read directory");
let mut files = Vec::new();
let mut dirs = Vec::new();
for p in paths {
let path = p.expect("not a real path").path();
let patch_path = path.strip_prefix(basedir).unwrap();
if path.is_dir() {
dirs.push(load_patch_dir(path.to_str().unwrap(), patch_path.to_str().unwrap(), file_ids));
}
else {
files.push(PatchFileTree::File(patch_path.to_path_buf(), file_ids.len() as u32));
let (checksum, size) = get_checksum_and_size(&path).unwrap();
file_ids.insert(file_ids.len() as u32, PatchFile {
path,
checksum,
size,
});
}
}
files.append(&mut dirs);
PatchFileTree::Directory(PathBuf::from(patchbase), files)
}
pub fn generate_patch_tree(basedir: &str) -> (PatchFileTree, HashMap<u32, PatchFile>) {
let mut file_ids = HashMap::new();
let patch_tree = load_patch_dir(basedir, "", &mut file_ids);
(patch_tree, file_ids)
}
fn get_file_list_packets(patch_file_tree: &PatchFileTree) -> Vec<SendPatchPacket> {
let mut pkts = Vec::new();
for item in patch_file_tree.flatten() {
match item {
PatchTreeIterItem::Directory(path) => {
pkts.push(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap())));
},
PatchTreeIterItem::File(path, id) => {
pkts.push(SendPatchPacket::FileInfo(FileInfo::new(path.to_str().unwrap(), id)));
},
PatchTreeIterItem::UpDirectory => {
pkts.push(SendPatchPacket::UpOneDirectory(UpOneDirectory {}));
}
}
}
pkts
}
fn get_checksum_and_size(path: &Path) -> Result<(u32, u32), PatchError> {
let file = fs::File::open(path)?;
let size = file.metadata().unwrap().len();
let mut crc = crc32::Digest::new(crc32::IEEE);
let mut buf = [0u8; 1024 * 32];
let mut reader = io::BufReader::new(file);
while let Ok(len) = reader.read(&mut buf) {
if len == 0 {
break;
}
crc.write(&buf[0..len]);
}
Ok((crc.sum32(), size as u32))
}
fn does_file_need_updating(file_info: &FileInfoReply, patch_file_lookup: &HashMap<u32, PatchFile>) -> bool {
let patch_file = patch_file_lookup.get(&file_info.id).unwrap();
patch_file.checksum != file_info.checksum || patch_file.size != file_info.size
}
struct SendFileIterator {
done: bool,
file_iter: Box<dyn Iterator<Item = PatchTreeIterItem> + Send>,
patch_file_lookup: HashMap<u32, PatchFile>,
current_file: Option<io::BufReader<fs::File>>,
chunk_num: u32,
}
impl SendFileIterator {
fn new(state: &PatchServerState) -> SendFileIterator {
let file_ids_to_update = state.patch_file_info.iter()
.filter(|file_info| does_file_need_updating(file_info, &state.patch_file_lookup))
.map(|k| k.id)
.collect::<HashSet<_>>();
SendFileIterator {
done: false,
patch_file_lookup: state.patch_file_lookup.clone(),
file_iter: Box::new(state.patch_file_tree.flatten().into_iter().filter(move |file| {
match file {
PatchTreeIterItem::File(_path, id) => {
file_ids_to_update.contains(id)
},
_ => true,
}
})),
current_file: None,
chunk_num: 0,
}
}
}
impl Iterator for SendFileIterator {
type Item = SendPatchPacket;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
match self.current_file {
Some(ref mut file) => {
let mut buf = [0u8; PATCH_FILE_CHUNK_SIZE as usize];
let len = file.read(&mut buf).unwrap();
if len == 0 {
self.current_file = None;
self.chunk_num = 0;
Some(SendPatchPacket::EndFileSend(EndFileSend::new()))
}
else {
let mut crc = crc32::Digest::new(crc32::IEEE);
crc.write(&buf[0..len]);
let pkt = SendPatchPacket::FileSend(Box::new(FileSend {
chunk_num: self.chunk_num,
checksum: crc.sum32(),
chunk_size: len as u32,
buffer: buf,
}));
self.chunk_num += 1;
Some(pkt)
}
},
None => {
match self.file_iter.next() {
Some(next_file) => {
match next_file {
PatchTreeIterItem::Directory(path) => {
Some(SendPatchPacket::ChangeDirectory(ChangeDirectory::new(path.to_str().unwrap())))
},
PatchTreeIterItem::File(path, id) => {
let patch_file = self.patch_file_lookup.get(&id).unwrap();
let file = fs::File::open(&patch_file.path).unwrap();
let size = file.metadata().unwrap().len();
self.current_file = Some(io::BufReader::new(file));
Some(SendPatchPacket::StartFileSend(StartFileSend::new(path.to_str().unwrap(), size as u32, id)))
},
PatchTreeIterItem::UpDirectory => {
Some(SendPatchPacket::UpOneDirectory(UpOneDirectory {}))
},
}
},
None => {
self.current_file = None;
self.done = true;
Some(SendPatchPacket::FinalizePatching(FinalizePatching {}))
}
}
}
}
}
}
#[derive(Debug, Deserialize)]
pub struct PatchConfig {
pub path: String,
pub ip: String, // TODO: this does nothing
pub port: u16,
}
pub fn load_config() -> PatchConfig {
let ini_file = match fs::File::open(std::path::Path::new("patch.ron")) {
Err(err) => panic!("Failed to open patch.ron config file. \n{err}"),
Ok(ini_file) => ini_file,
};
let mut s = String::new();
if let Err(err) = (&ini_file).read_to_string(&mut s) {
panic!("Failed to read patch.ron config file. \n{err}");
}
let config: PatchConfig = match from_str(s.as_str()) {
Ok(config) => config,
Err(err) => panic!("Failed to load values from patch.ron \n{err}"),
};
config
}
pub fn load_config_env() -> PatchConfig {
let patch_path = std::env::var("PATCHFILE_DIR").unwrap();
let patch_port = std::env::var("PATCH_PORT").unwrap().parse().unwrap();
PatchConfig {
path: patch_path,
ip: "127.0.0.1".into(),
port: patch_port,
}
}
pub fn load_motd() -> String {
if let Ok(m) = fs::read_to_string("patch.motd") {
m
}
else {
"Welcome to Elseware!".to_string()
}
}

View File

@ -1,9 +1,10 @@
use libpso::character::character;
use stats::leveltable::CharacterStats;
use entity::character::CharacterEntity;
use items::bank::BankState;
use items::inventory::InventoryState;
use entity::item::Meseta;
use crate::common::leveltable::CharacterStats;
use crate::entity::character::CharacterEntity;
//use crate::ship::items::{CharacterInventory, CharacterBank};
use crate::ship::items::bank::BankState;
use crate::ship::items::inventory::InventoryState;
use crate::entity::item::Meseta;
#[derive(Default)]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
// to their drops
mod drop_table;
pub mod rare_drop_table;
mod generic_weapon;
mod generic_armor;
@ -21,19 +22,19 @@ use std::io::Read;
use serde::{Serialize, Deserialize};
use rand::{Rng, SeedableRng};
use maps::monster::MonsterType;
use maps::room::{Difficulty, Episode};
use maps::area::MapArea;
use entity::character::SectionID;
use crate::generic_weapon::GenericWeaponTable;
use crate::generic_armor::GenericArmorTable;
use crate::generic_shield::GenericShieldTable;
use crate::generic_unit::GenericUnitTable;
use crate::tool_table::ToolTable;
use crate::rare_drop_table::RareDropTable;
use crate::box_drop_table::BoxDropTable;
use maps::object::MapObject;
use entity::item::{ItemType, weapon, armor, shield, unit, mag, tool, tech, esweapon};
use crate::ship::monster::MonsterType;
use crate::ship::room::{Difficulty, Episode};
use crate::ship::map::MapArea;
use crate::entity::character::SectionID;
use crate::ship::drops::generic_weapon::GenericWeaponTable;
use crate::ship::drops::generic_armor::GenericArmorTable;
use crate::ship::drops::generic_shield::GenericShieldTable;
use crate::ship::drops::generic_unit::GenericUnitTable;
use crate::ship::drops::tool_table::ToolTable;
use crate::ship::drops::rare_drop_table::RareDropTable;
use crate::ship::drops::box_drop_table::BoxDropTable;
use crate::ship::map::MapObject;
use crate::entity::item::{weapon, armor, shield, unit, mag, tool, tech};
fn data_file_path(episode: Episode, difficulty: Difficulty, section_id: SectionID, filename: &str) -> PathBuf {
@ -54,6 +55,18 @@ pub fn load_data_file<T: serde::de::DeserializeOwned>(episode: Episode, difficul
toml::from_str::<T>(s.as_str()).unwrap()
}
// this is just copypaste
pub fn load_rare_monster_file<T: serde::de::DeserializeOwned>(episode: Episode) -> T {
// TODO: where does the rare monster toml file actually live
let mut path = PathBuf::from("data/battle_param/");
path.push(episode.to_string().to_lowercase() + "_rare_monster.toml");
let mut f = File::open(path).unwrap();
let mut s = String::new();
f.read_to_string(&mut s);
toml::from_str::<T>(s.as_str()).unwrap()
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub enum MonsterDropType {
#[serde(rename = "weapon")]
@ -89,28 +102,6 @@ pub enum ItemDropType {
Meseta(u32),
}
impl ItemDropType {
pub fn parse_item_from_bytes(data: [u8; 16]) -> Option<ItemDropType> {
let item_type = weapon::WeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::Weapon)
.or_else(|_| armor::ArmorType::parse_type([data[0],data[1],data[2]]).map(ItemType::Armor))
.or_else(|_| shield::ShieldType::parse_type([data[0],data[1],data[2]]).map(ItemType::Shield))
.or_else(|_| unit::UnitType::parse_type([data[0],data[1],data[2]]).map(ItemType::Unit))
.or_else(|_| mag::MagType::parse_type([data[0],data[1],data[2]]).map(ItemType::Mag))
.or_else(|_| tool::ToolType::parse_type([data[0],data[1],data[2]]).map(ItemType::Tool))
.or_else(|_| esweapon::ESWeaponType::parse_type([data[0],data[1],data[2]]).map(ItemType::ESWeapon)).ok()?;
match item_type {
ItemType::Weapon(_w) => Some(ItemDropType::Weapon(weapon::Weapon::from_bytes(data).ok()?)),
ItemType::Armor(_a) => Some(ItemDropType::Armor(armor::Armor::from_bytes(data).ok()?)),
ItemType::Shield(_s) => Some(ItemDropType::Shield(shield::Shield::from_bytes(data).ok()?)),
ItemType::Unit(_u) => Some(ItemDropType::Unit(unit::Unit::from_bytes(data).ok()?)),
ItemType::Mag(_m) => Some(ItemDropType::Mag(mag::Mag::from_bytes(data).ok()?)),
ItemType::Tool(_t) => Some(ItemDropType::Tool(tool::Tool::from_bytes(data).ok()?)),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ItemDrop {
pub map_area: MapArea,
@ -121,12 +112,7 @@ pub struct ItemDrop {
}
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 {
pub struct DropTable {
monster_stats: HashMap<MonsterType, MonsterDropStats>,
rare_table: RareDropTable,
weapon_table: GenericWeaponTable,
@ -138,12 +124,11 @@ pub struct StandardDropTable {
rng: rand_chacha::ChaCha20Rng,
}
impl StandardDropTable {
#[allow(clippy::new_ret_no_self)]
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
impl DropTable {
pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
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(),
rare_table: RareDropTable::new(episode, difficulty, section_id),
weapon_table: GenericWeaponTable::new(episode, difficulty, section_id),
@ -153,7 +138,7 @@ impl StandardDropTable {
tool_table: ToolTable::new(episode, difficulty, section_id),
box_table: BoxDropTable::new(episode, difficulty, section_id),
rng: rand_chacha::ChaCha20Rng::from_entropy(),
})
}
}
pub fn builder() -> DropTableBuilder {
@ -183,10 +168,8 @@ impl StandardDropTable {
MonsterDropType::None => None,
}
}
}
impl DropTable for StandardDropTable {
fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
pub fn get_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
let monster_stat = *self.monster_stats.get(monster)?;
let drop_anything = self.rng.gen_range(0, 100);
@ -214,7 +197,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)
}
}
@ -261,8 +244,8 @@ impl DropTableBuilder {
self
}
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
Box::new(StandardDropTable {
pub fn build(self, episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable {
DropTable {
monster_stats: self.monster_stats.unwrap_or_else(|| {
let monster_stats: HashMap<String, MonsterDropStats> = load_data_file(episode, difficulty, section_id, "monster_dar.toml");
monster_stats.into_iter().map(|(m, s)| (m.parse().unwrap(), s)).collect()
@ -275,10 +258,11 @@ impl DropTableBuilder {
tool_table: self.tool_table.unwrap_or_else(|| ToolTable::new(episode, difficulty, section_id)),
box_table: self.box_table.unwrap_or_else(|| BoxDropTable::new(episode, difficulty, section_id)),
rng: self.rng.unwrap_or_else(rand_chacha::ChaCha20Rng::from_entropy),
})
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

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

View File

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

View File

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

View File

@ -4,13 +4,13 @@ use serde::{Serialize, Deserialize};
use std::fs::File;
use std::io::Read;
use entity::item::weapon::WeaponType;
use entity::item::armor::ArmorType;
use entity::item::shield::ShieldType;
use entity::item::unit::UnitType;
use entity::item::mag::MagType;
use entity::item::tool::ToolType;
use entity::item::tech::Technique;
use crate::entity::item::weapon::WeaponType;
use crate::entity::item::armor::ArmorType;
use crate::entity::item::shield::ShieldType;
use crate::entity::item::unit::UnitType;
use crate::entity::item::mag::MagType;
use crate::entity::item::tool::ToolType;
use crate::entity::item::tech::Technique;
lazy_static::lazy_static! {

View File

@ -1,6 +1,6 @@
// TODO: replace various u32s and usizes denoting item amounts for ItemAmount(u32) for consistency
use crate::ClientItemId;
use entity::item::{Meseta, ItemNote};
use crate::ship::items::ClientItemId;
use crate::entity::item::{Meseta, ItemNote};
use async_std::sync::Arc;
use std::future::Future;
use futures::future::BoxFuture;
@ -8,33 +8,30 @@ use std::pin::Pin;
use std::iter::IntoIterator;
use anyhow::Context;
use entity::character::{CharacterEntity, CharacterEntityId};
use entity::gateway::{EntityGateway, EntityGatewayTransaction};
use entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier};
use entity::item::tool::Tool;
use entity::room::RoomEntityId;
use maps::area::MapArea;
use crate::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::bank::{BankItem, BankItemDetail};
use crate::inventory::{InventoryItem, InventoryItemDetail};
use crate::floor::{FloorItem, FloorItemDetail};
use crate::apply_item::{apply_item, ApplyItemAction};
use shops::ShopItem;
use drops::{ItemDrop, ItemDropType};
use location::AreaClient;
use maps::monster::MonsterType;
use libpso::packet::{ship::Message, messages::GameMessage};
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier};
use crate::entity::item::tool::Tool;
use crate::entity::room::RoomEntityId;
use crate::ship::map::MapArea;
use crate::ship::ship::SendShipPacket;
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::ship::shops::ShopItem;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::packet::builder;
use crate::ship::location::AreaClient;
use crate::ship::monster::MonsterType;
pub enum TriggerCreateItem {
Yes,
No
}
#[derive(Clone)]
pub enum CreateItem {
Individual(AreaClient, ClientItemId, IndividualItemDetail),
Stacked(AreaClient, ClientItemId, Tool, usize),
}
pub(super) fn take_item_from_floor<'a, EG, TR>(
character_id: CharacterEntityId,
item_id: ClientItemId
@ -1147,7 +1144,7 @@ pub(super) fn apply_item_action_packets<'a, EG, TR>(
character_id: CharacterEntityId,
area_client: AreaClient,
) -> impl Fn((ItemStateProxy, TR), ApplyItemAction)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<CreateItem>), anyhow::Error>>
-> BoxFuture<'a, Result<((ItemStateProxy, TR), Vec<SendShipPacket>), anyhow::Error>>
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
@ -1164,7 +1161,7 @@ where
let (inventory_item_detail, create_item) = if item_detail.is_stackable() {
let tool = item_detail.as_tool().ok_or_else(|| ItemStateError::NotATool(ClientItemId(0xFFFFFFFF)))?;
let create_item = CreateItem::Stacked(area_client, item_id, tool, 1);
let create_item = builder::message::create_stacked_item(area_client, item_id, &tool, 1).map_err(|_err| ItemStateError::Dummy)?;
let item_detail = StackedItemDetail {
entity_ids: vec![new_item.id],
tool
@ -1176,7 +1173,7 @@ where
entity_id: new_item.id,
item: item_detail,
};
let create_item = CreateItem::Individual(area_client, item_id, item_detail.clone());
let create_item = builder::message::create_individual_item(area_client, item_id, &item_detail).map_err(|_err| ItemStateError::Dummy)?;
(InventoryItemDetail::Individual(item_detail), create_item)
};
@ -1190,8 +1187,7 @@ where
transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?;
item_state.set_inventory(inventory).await;
//vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
vec![create_item]
vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item)))]
}
else {
Vec::new()

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::ClientItemId;
use crate::ship::items::ClientItemId;
use std::collections::HashMap;
use std::cmp::Ordering;
use std::cell::RefCell;
@ -9,18 +9,18 @@ use crate::entity::item::{ItemDetail, ItemNote, BankName};
use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::weapon;
use maps::area::MapArea;
use crate::ship::map::MapArea;
use crate::ship::ship::ItemDropLocation;
use crate::ship::trade::TradeItem;
use drops::{ItemDrop, ItemDropType};
use location::{AreaClient, RoomId};
use shops::ShopItem;
use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::location::{AreaClient, RoomId};
use crate::ship::shops::ShopItem;
use crate::ship::packet::handler::trade::{TradeError, OTHER_MESETA_ITEM_ID};
use crate::bank::*;
use crate::floor::*;
use crate::inventory::*;
use crate::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError};
use crate::ship::items::bank::*;
use crate::ship::items::floor::*;
use crate::ship::items::inventory::*;
use crate::ship::items::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError};
#[derive(PartialEq, Eq)]
pub enum FloorType {

View File

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

View File

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

View File

@ -1,23 +1,24 @@
use futures::future::BoxFuture;
use crate::ClientItemId;
use entity::item::Meseta;
use crate::ship::items::ClientItemId;
use crate::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::ship::ship::SendShipPacket;
use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::entity::item::ItemModifier;
use crate::entity::room::RoomEntityId;
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::ship::shops::ShopItem;
use crate::ship::trade::TradeItem;
use crate::ship::location::AreaClient;
use crate::ship::drops::ItemDrop;
use crate::ship::monster::MonsterType;
use crate::actions;
use crate::ship::items::actions;
pub fn pick_up_item<'a, EG>(
item_state: &'a mut ItemState,
@ -277,7 +278,7 @@ pub fn use_item<'a, EG> (
area_client: AreaClient,
item_id: &'a ClientItemId,
amount: u32,
) -> BoxFuture<'a, Result<Vec<actions::CreateItem>, anyhow::Error>>
) -> BoxFuture<'a, Result<Vec<SendShipPacket>, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
@ -371,8 +372,6 @@ where
Ok((transaction, result))
})
}
#[allow(clippy::type_complexity)]
pub fn trade_items<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,

View File

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

View File

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

View File

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

View File

@ -6,15 +6,14 @@ use std::fs::File;
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>> {
let mut object_data = Vec::new();
@ -326,7 +325,7 @@ impl Maps {
enemies: Vec<Option<MapEnemy>>,
objects: Vec<Option<MapObject>>,
rare_monster_table: &RareMonsterAppearTable,
event: Holiday)
event: ShipEvent)
{
self.enemy_data = enemies
.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: ShipEvent) -> Maps {
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode());
Maps {
@ -376,12 +375,3 @@ pub fn generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> Maps {
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(),
}
}

12
src/ship/map/mod.rs Normal file
View File

@ -0,0 +1,12 @@
pub mod area;
pub mod enemy;
pub mod object;
pub mod variant;
pub mod maps;
// TODO: don't just forward everything to the module scope
pub use area::*;
pub use enemy::*;
pub use object::*;
pub use variant::*;
pub use maps::*;

View File

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

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