Merge pull request 'postgres' (#229) from postgres into master
This commit is contained in:
commit
a796c4f07b
@ -15,7 +15,7 @@ mio-extras = "2.0.5"
|
||||
crc = "^1.0.0"
|
||||
bcrypt = "0.4"
|
||||
threadpool = "1.0"
|
||||
chrono = "*"
|
||||
chrono = "0.4.11"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
ron = "*"
|
||||
@ -29,4 +29,8 @@ thiserror = "1.0.15"
|
||||
ages-prs = "0.1"
|
||||
async-trait = "0.1.31"
|
||||
lazy_static = "1.4.0"
|
||||
barrel = { version = "0.6.5", features = ["pg"] }
|
||||
postgres = "0.17.5"
|
||||
refinery = { version = "0.3.0", features = ["postgres"] }
|
||||
sqlx = { version = "0.4.0-beta.1", features = ["postgres", "json", "chrono"] }
|
||||
|
||||
|
@ -3,6 +3,7 @@ feed_table = 0
|
||||
|
||||
[Varuna]
|
||||
feed_table = 1
|
||||
photon_blast = "Farlla"
|
||||
|
||||
[Mitra]
|
||||
feed_table = 3
|
||||
@ -82,6 +83,7 @@ photon_blast = "Pilla"
|
||||
|
||||
[Ribhava]
|
||||
feed_table = 5
|
||||
photon_blast = "Farlla"
|
||||
|
||||
[Soma]
|
||||
feed_table = 5
|
||||
@ -120,6 +122,7 @@ photon_blast = "MyllaYoulla"
|
||||
|
||||
[Ravana]
|
||||
feed_table = 6
|
||||
photon_blast = "Farlla"
|
||||
|
||||
[Marica]
|
||||
feed_table = 6
|
||||
|
@ -8,7 +8,7 @@ use elseware::login::character::CharacterServerState;
|
||||
use elseware::ship::ship::ShipServerState;
|
||||
use elseware::ship::ship::ShipServerStateBuilder;
|
||||
use elseware::entity::account::{NewUserAccountEntity, NewUserSettingsEntity};
|
||||
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
|
||||
use elseware::entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
|
||||
use elseware::entity::character::NewCharacterEntity;
|
||||
use elseware::entity::item::{NewItemEntity, ItemDetail, ItemLocation};
|
||||
|
||||
@ -48,17 +48,19 @@ fn setup_logger() {
|
||||
fn main() {
|
||||
setup_logger();
|
||||
async_std::task::block_on(async move {
|
||||
//let mut entity_gateway = PostgresGateway::new("localhost", "elsewhere", "elsewhere", "");
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
for i in 0..5 {
|
||||
let fake_user = NewUserAccountEntity {
|
||||
email: format!("fake{}@email.com", i),
|
||||
username: if i == 0 { "hi".to_string() } else { format!("hi{}", i+1) },
|
||||
password: bcrypt::hash("qwer", 5).unwrap(),
|
||||
guildcard: i + 1,
|
||||
team_id: None,
|
||||
banned: false,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
banned_until: None,
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
flags: 0,
|
||||
};
|
||||
let fake_user = entity_gateway.create_user(fake_user).await.unwrap();
|
||||
|
@ -119,7 +119,7 @@ async fn send_pkt<S: SendServerPacket + Send + std::fmt::Debug>(socket: Arc<asyn
|
||||
-> Result<(), NetworkError>
|
||||
{
|
||||
let buf = pkt.as_bytes();
|
||||
println!("sndbuf: {:?}", buf);
|
||||
//println!("sndbuf: {:?}", buf);
|
||||
let cbuf = cipher.lock().await.encrypt(&buf)?;
|
||||
let mut ssock = &*socket;
|
||||
ssock.write_all(&cbuf).await?;
|
||||
|
@ -147,8 +147,8 @@ where
|
||||
async_std::task::spawn(async move {
|
||||
loop {
|
||||
info!("login send loop");
|
||||
let msg = output_loop_receiver.recv().await.unwrap();
|
||||
|
||||
match output_loop_receiver.recv().await {
|
||||
Ok(msg) => {
|
||||
let payload = serde_json::to_string(&msg);
|
||||
if let Ok(payload) = payload {
|
||||
let len_bytes = u32::to_le_bytes(payload.len() as u32);
|
||||
@ -162,6 +162,11 @@ where
|
||||
Err(err) => warn!("send failed: {:?}", err),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("error in send_loop: {:?}, {:?}", server_id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -17,13 +17,14 @@ pub struct GuildCardDataId(pub u32);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NewUserAccountEntity {
|
||||
pub email: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub guildcard: u32,
|
||||
pub team_id: Option<u32>,
|
||||
pub banned: bool,
|
||||
pub muted_until: SystemTime,
|
||||
pub created_at: SystemTime,
|
||||
pub banned_until: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub muted_until: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
@ -34,9 +35,9 @@ pub struct UserAccountEntity {
|
||||
pub password: String,
|
||||
pub guildcard: u32,
|
||||
pub team_id: Option<u32>,
|
||||
pub banned: bool,
|
||||
pub muted_until: SystemTime,
|
||||
pub created_at: SystemTime,
|
||||
pub banned_until: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub muted_until: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub flags: u32, // TODO: is this used for anything other than character creation?
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use libpso::character::character::{DEFAULT_PALETTE_CONFIG, DEFAULT_TECH_MENU};
|
||||
use crate::entity::item::tech::Technique;
|
||||
use crate::entity::account::UserAccountId;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize)]
|
||||
pub enum CharacterClass {
|
||||
HUmar,
|
||||
HUnewearl,
|
||||
@ -64,7 +64,7 @@ impl Into<u8> for CharacterClass {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, enum_utils::FromStr, derive_more::Display, Serialize, Deserialize)]
|
||||
pub enum SectionID {
|
||||
Viridia,
|
||||
Greenill,
|
||||
@ -134,7 +134,7 @@ pub struct TechLevel(pub u8);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CharacterTechniques {
|
||||
techs: HashMap<Technique, TechLevel>
|
||||
pub techs: HashMap<Technique, TechLevel>
|
||||
}
|
||||
|
||||
impl CharacterTechniques {
|
||||
@ -163,7 +163,7 @@ impl CharacterTechniques {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CharacterConfig {
|
||||
raw_data: [u8; 0xE8],
|
||||
pub raw_data: [u8; 0xE8],
|
||||
}
|
||||
|
||||
impl CharacterConfig {
|
||||
@ -184,7 +184,7 @@ impl CharacterConfig {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CharacterInfoboard {
|
||||
board: [u16; 172],
|
||||
pub board: [u16; 172],
|
||||
}
|
||||
|
||||
impl CharacterInfoboard {
|
||||
|
@ -2,6 +2,7 @@ use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::item::*;
|
||||
|
||||
// TODO: all these Options should be Results
|
||||
#[async_trait::async_trait]
|
||||
pub trait EntityGateway: Send + Sync + Clone {
|
||||
async fn create_user(&mut self, _user: NewUserAccountEntity) -> Option<UserAccountEntity> {
|
||||
@ -28,7 +29,7 @@ pub trait EntityGateway: Send + Sync + Clone {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn save_user_settings(&mut self, _settings: &UserSettingsEntity) {
|
||||
async fn save_user_settings(&mut self, _settings: &UserSettingsEntity) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@ -36,6 +37,7 @@ pub trait EntityGateway: Send + Sync + Clone {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// TODO: just make this a vec sorted by slot order?
|
||||
async fn get_characters_by_user(&self, _user: &UserAccountEntity) -> [Option<CharacterEntity>; 4] {
|
||||
unimplemented!();
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ impl EntityGateway for InMemoryGateway {
|
||||
password: user.password,
|
||||
guildcard: user.guildcard,
|
||||
team_id: user.team_id,
|
||||
banned: user.banned,
|
||||
banned_until: user.banned_until,
|
||||
muted_until: user.muted_until,
|
||||
created_at: user.created_at,
|
||||
flags: user.flags,
|
||||
|
@ -1,5 +1,7 @@
|
||||
pub mod entitygateway;
|
||||
pub mod inmemory;
|
||||
pub mod postgres;
|
||||
|
||||
pub use entitygateway::EntityGateway;
|
||||
pub use inmemory::InMemoryGateway;
|
||||
pub use self::postgres::PostgresGateway;
|
||||
|
121
src/entity/gateway/postgres/migrations/V0001__initial.sql
Normal file
121
src/entity/gateway/postgres/migrations/V0001__initial.sql
Normal file
@ -0,0 +1,121 @@
|
||||
create table user_accounts (
|
||||
id serial primary key not null,
|
||||
email varchar(128) not null unique,
|
||||
username varchar(128) not null unique,
|
||||
password varchar(128) not null,
|
||||
banned timestamptz,
|
||||
muted timestamptz,
|
||||
created_at timestamptz default current_timestamp not null,
|
||||
flags integer default 0 not null,
|
||||
activated boolean default false not null,
|
||||
game_session integer,
|
||||
interserver_session integer
|
||||
);
|
||||
|
||||
create table user_settings (
|
||||
id serial primary key not null,
|
||||
user_account integer references user_accounts (id) not null,
|
||||
blocked_users bytea not null,
|
||||
key_config bytea not null,
|
||||
joystick_config bytea not null,
|
||||
option_flags integer not null,
|
||||
shortcuts bytea not null,
|
||||
symbol_chats bytea not null,
|
||||
team_name bytea not null
|
||||
);
|
||||
|
||||
/* TODO: guild card data */
|
||||
|
||||
create table player_character (
|
||||
id serial primary key not null,
|
||||
user_account integer references user_accounts (id) not null,
|
||||
slot smallint not null,
|
||||
name varchar(12) not null,
|
||||
exp integer not null,
|
||||
class varchar(12) null,
|
||||
section_id varchar(12) not null,
|
||||
|
||||
costume smallint not null,
|
||||
skin smallint not null,
|
||||
face smallint not null,
|
||||
head smallint not null,
|
||||
hair smallint not null,
|
||||
hair_r smallint not null,
|
||||
hair_g smallint not null,
|
||||
hair_b smallint not null,
|
||||
prop_x real not null,
|
||||
prop_y real not null,
|
||||
|
||||
techs bytea not null,
|
||||
|
||||
config bytea not null,
|
||||
infoboard varchar(172) not null,
|
||||
guildcard varchar(172) not null,
|
||||
option_flags integer not null,
|
||||
|
||||
power smallint not null,
|
||||
mind smallint not null,
|
||||
def smallint not null,
|
||||
evade smallint not null,
|
||||
luck smallint not null,
|
||||
hp smallint not null,
|
||||
tp smallint not null,
|
||||
|
||||
tech_menu bytea not null,
|
||||
meseta integer not null,
|
||||
bank_meseta integer not null
|
||||
|
||||
);
|
||||
|
||||
|
||||
create table item (
|
||||
id serial primary key not null,
|
||||
item jsonb not null
|
||||
);
|
||||
|
||||
create table item_location (
|
||||
item integer references item (id) not null,
|
||||
location jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
||||
|
||||
create table inventory_slots (
|
||||
pchar integer references player_character not null,
|
||||
items integer[30] /* references item (id) */
|
||||
);
|
||||
|
||||
create table weapon_modifier (
|
||||
weapon integer references item (id) not null,
|
||||
modifier jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
||||
|
||||
create table armor_modifier (
|
||||
armor integer references item (id) not null,
|
||||
modifier jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
||||
|
||||
create table shield_modifier (
|
||||
shield integer references item (id) not null,
|
||||
modifier jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
||||
|
||||
create table unit_modifier (
|
||||
unit integer references item (id) not null,
|
||||
modifier jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
||||
|
||||
create table esweapon_modifier (
|
||||
esweapon integer references item (id) not null,
|
||||
modifier jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
||||
|
||||
create table mag_modifier (
|
||||
mag integer references item (id) not null,
|
||||
modifier jsonb not null,
|
||||
created_at timestamptz default current_timestamp not null
|
||||
);
|
3
src/entity/gateway/postgres/migrations/mod.rs
Normal file
3
src/entity/gateway/postgres/migrations/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use refinery::include_migration_mods;
|
||||
|
||||
include_migration_mods!("src/entity/gateway/postgres/migrations");
|
5
src/entity/gateway/postgres/mod.rs
Normal file
5
src/entity/gateway/postgres/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod postgres;
|
||||
pub mod migrations;
|
||||
pub mod models;
|
||||
|
||||
pub use self::postgres::PostgresGateway;
|
691
src/entity/gateway/postgres/models.rs
Normal file
691
src/entity/gateway/postgres/models.rs
Normal file
@ -0,0 +1,691 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Into;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use futures::TryStreamExt;
|
||||
use libpso::character::{settings, guildcard};
|
||||
use libpso::util::vec_to_array;
|
||||
use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::gateway::EntityGateway;
|
||||
use crate::entity::item::*;
|
||||
use crate::ship::map::MapArea;
|
||||
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::Row;
|
||||
use sqlx::Execute;
|
||||
use postgres::{Client, NoTls};
|
||||
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgUserAccount {
|
||||
id: i32,
|
||||
username: String,
|
||||
password: String,
|
||||
banned: Option<chrono::DateTime<chrono::Utc>>,
|
||||
muted: Option<chrono::DateTime<chrono::Utc>>,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
flags: i32,
|
||||
}
|
||||
|
||||
impl Into<UserAccountEntity> for PgUserAccount {
|
||||
fn into(self) -> UserAccountEntity {
|
||||
UserAccountEntity {
|
||||
id: UserAccountId(self.id as u32),
|
||||
username: self.username,
|
||||
password: self.password,
|
||||
banned_until: self.banned,
|
||||
muted_until: self.muted,
|
||||
created_at: self.created_at,
|
||||
flags: self.flags as u32,
|
||||
// TOOD
|
||||
guildcard: self.id as u32 + 1,
|
||||
team_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgUserSettings {
|
||||
id: i32,
|
||||
user_account: i32,
|
||||
blocked_users: Vec<u8>, //[u32; 0x1E],
|
||||
key_config: Vec<u8>, //[u8; 0x16C],
|
||||
joystick_config: Vec<u8>, //[u8; 0x38],
|
||||
option_flags: i32,
|
||||
shortcuts: Vec<u8>, //[u8; 0xA40],
|
||||
symbol_chats: Vec<u8>, //[u8; 0x4E0],
|
||||
team_name: Vec<u8>, //[u16; 0x10],
|
||||
}
|
||||
|
||||
impl Into<UserSettingsEntity> for PgUserSettings {
|
||||
fn into(self) -> UserSettingsEntity {
|
||||
UserSettingsEntity {
|
||||
id: UserSettingsId(self.id as u32),
|
||||
user_id: UserAccountId(self.user_account as u32),
|
||||
settings: settings::UserSettings {
|
||||
blocked_users: vec_to_array(self.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
|
||||
key_config: vec_to_array(self.key_config),
|
||||
joystick_config: vec_to_array(self.joystick_config),
|
||||
option_flags: self.option_flags as u32,
|
||||
shortcuts: vec_to_array(self.shortcuts),
|
||||
symbol_chats: vec_to_array(self.symbol_chats),
|
||||
team_name: vec_to_array(self.team_name.chunks(2).map(|b| u16::from_le_bytes([b[0], b[1]])).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Debug)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
pub enum PgCharacterClass {
|
||||
HUmar,
|
||||
HUnewearl,
|
||||
HUcast,
|
||||
HUcaseal,
|
||||
RAmar,
|
||||
RAmarl,
|
||||
RAcast,
|
||||
RAcaseal,
|
||||
FOmar,
|
||||
FOmarl,
|
||||
FOnewm,
|
||||
FOnewearl,
|
||||
}
|
||||
|
||||
impl Into<CharacterClass> for PgCharacterClass {
|
||||
fn into(self) -> CharacterClass {
|
||||
match self {
|
||||
PgCharacterClass::HUmar => CharacterClass::HUmar,
|
||||
PgCharacterClass::HUnewearl => CharacterClass::HUnewearl,
|
||||
PgCharacterClass::HUcast => CharacterClass::HUcast,
|
||||
PgCharacterClass::HUcaseal => CharacterClass::HUcaseal,
|
||||
PgCharacterClass::RAmar => CharacterClass::RAmar,
|
||||
PgCharacterClass::RAmarl => CharacterClass::RAmarl,
|
||||
PgCharacterClass::RAcast => CharacterClass::RAcast,
|
||||
PgCharacterClass::RAcaseal => CharacterClass::RAcaseal,
|
||||
PgCharacterClass::FOmar => CharacterClass::FOmar,
|
||||
PgCharacterClass::FOmarl => CharacterClass::FOmarl,
|
||||
PgCharacterClass::FOnewm => CharacterClass::FOnewm,
|
||||
PgCharacterClass::FOnewearl => CharacterClass::FOnewearl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CharacterClass> for PgCharacterClass {
|
||||
fn from(other: CharacterClass) -> PgCharacterClass {
|
||||
match other {
|
||||
CharacterClass::HUmar => PgCharacterClass::HUmar,
|
||||
CharacterClass::HUnewearl => PgCharacterClass::HUnewearl,
|
||||
CharacterClass::HUcast => PgCharacterClass::HUcast,
|
||||
CharacterClass::HUcaseal => PgCharacterClass::HUcaseal,
|
||||
CharacterClass::RAmar => PgCharacterClass::RAmar,
|
||||
CharacterClass::RAmarl => PgCharacterClass::RAmarl,
|
||||
CharacterClass::RAcast => PgCharacterClass::RAcast,
|
||||
CharacterClass::RAcaseal => PgCharacterClass::RAcaseal,
|
||||
CharacterClass::FOmar => PgCharacterClass::FOmar,
|
||||
CharacterClass::FOmarl => PgCharacterClass::FOmarl,
|
||||
CharacterClass::FOnewm => PgCharacterClass::FOnewm,
|
||||
CharacterClass::FOnewearl => PgCharacterClass::FOnewearl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Debug)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
pub enum PgSectionId {
|
||||
Viridia,
|
||||
Greenill,
|
||||
Skyly,
|
||||
Bluefull,
|
||||
Purplenum,
|
||||
Pinkal,
|
||||
Redria,
|
||||
Oran,
|
||||
Yellowboze,
|
||||
Whitill,
|
||||
}
|
||||
|
||||
impl Into<SectionID> for PgSectionId {
|
||||
fn into(self) -> SectionID {
|
||||
match self {
|
||||
PgSectionId::Viridia => SectionID::Viridia,
|
||||
PgSectionId::Greenill => SectionID::Greenill,
|
||||
PgSectionId::Skyly => SectionID::Skyly,
|
||||
PgSectionId::Bluefull => SectionID::Bluefull,
|
||||
PgSectionId::Purplenum => SectionID::Purplenum,
|
||||
PgSectionId::Pinkal => SectionID::Pinkal,
|
||||
PgSectionId::Redria => SectionID::Redria,
|
||||
PgSectionId::Oran => SectionID::Oran,
|
||||
PgSectionId::Yellowboze => SectionID::Yellowboze,
|
||||
PgSectionId::Whitill => SectionID::Whitill,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SectionID> for PgSectionId {
|
||||
fn from(other: SectionID) -> PgSectionId {
|
||||
match other {
|
||||
SectionID::Viridia => PgSectionId::Viridia,
|
||||
SectionID::Greenill => PgSectionId::Greenill,
|
||||
SectionID::Skyly => PgSectionId::Skyly,
|
||||
SectionID::Bluefull => PgSectionId::Bluefull,
|
||||
SectionID::Purplenum => PgSectionId::Purplenum,
|
||||
SectionID::Pinkal => PgSectionId::Pinkal,
|
||||
SectionID::Redria => PgSectionId::Redria,
|
||||
SectionID::Oran => PgSectionId::Oran,
|
||||
SectionID::Yellowboze => PgSectionId::Yellowboze,
|
||||
SectionID::Whitill => PgSectionId::Whitill,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgCharacter {
|
||||
pub id: i32,
|
||||
user_account: i32,
|
||||
pub slot: i16,
|
||||
name: String,
|
||||
exp: i32,
|
||||
class: String,
|
||||
section_id: String,
|
||||
|
||||
costume: i16,
|
||||
skin: i16,
|
||||
face: i16,
|
||||
head: i16,
|
||||
hair: i16,
|
||||
hair_r: i16,
|
||||
hair_g: i16,
|
||||
hair_b: i16,
|
||||
prop_x: f32,
|
||||
prop_y: f32,
|
||||
|
||||
techs: Vec<u8>,
|
||||
|
||||
config: Vec<u8>,
|
||||
infoboard: String,
|
||||
guildcard: String,
|
||||
option_flags: i32,
|
||||
|
||||
power: i16,
|
||||
mind: i16,
|
||||
def: i16,
|
||||
evade: i16,
|
||||
luck: i16,
|
||||
hp: i16,
|
||||
tp: i16,
|
||||
|
||||
tech_menu: Vec<u8>,
|
||||
meseta: i32,
|
||||
bank_meseta: i32,
|
||||
}
|
||||
|
||||
impl Into<CharacterEntity> for PgCharacter {
|
||||
fn into(self) -> CharacterEntity {
|
||||
CharacterEntity {
|
||||
id: CharacterEntityId(self.id as u32),
|
||||
user_id: UserAccountId(self.user_account as u32),
|
||||
slot: self.slot as u32,
|
||||
name: self.name,
|
||||
exp: self.exp as u32,
|
||||
char_class: self.class.parse().unwrap(),
|
||||
section_id: self.section_id.parse().unwrap(),
|
||||
appearance: CharacterAppearance {
|
||||
costume: self.costume as u16,
|
||||
skin: self.skin as u16,
|
||||
face: self.face as u16,
|
||||
head: self.head as u16,
|
||||
hair: self.hair as u16,
|
||||
hair_r: self.hair_r as u16,
|
||||
hair_g: self.hair_g as u16,
|
||||
hair_b: self.hair_b as u16,
|
||||
prop_x: self.prop_x,
|
||||
prop_y: self.prop_y,
|
||||
},
|
||||
techs: CharacterTechniques {
|
||||
techs: self.techs.iter().enumerate().take(19).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
|
||||
},
|
||||
config: CharacterConfig {
|
||||
raw_data: vec_to_array(self.config)
|
||||
},
|
||||
info_board: CharacterInfoboard {
|
||||
board: libpso::utf8_to_utf16_array!(self.infoboard, 172),
|
||||
},
|
||||
guildcard: CharacterGuildCard {
|
||||
description: self.guildcard,
|
||||
},
|
||||
option_flags: self.option_flags as u32,
|
||||
materials: CharacterMaterials {
|
||||
power: self.power as u32,
|
||||
mind: self.mind as u32,
|
||||
def: self.def as u32,
|
||||
evade: self.evade as u32,
|
||||
luck: self.luck as u32,
|
||||
hp: self.hp as u32,
|
||||
tp: self.tp as u32,
|
||||
},
|
||||
tech_menu: CharacterTechMenu {
|
||||
tech_menu: vec_to_array(self.tech_menu)
|
||||
},
|
||||
meseta: self.meseta as u32,
|
||||
bank_meseta: self.bank_meseta as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgGuildCard {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgWeapon {
|
||||
weapon: weapon::WeaponType,
|
||||
special: Option<weapon::WeaponSpecial>,
|
||||
grind: u8,
|
||||
attrs: HashMap<weapon::Attribute, i8>,
|
||||
tekked: bool,
|
||||
}
|
||||
|
||||
impl From<weapon::Weapon> for PgWeapon {
|
||||
fn from(other: weapon::Weapon) -> PgWeapon {
|
||||
PgWeapon {
|
||||
weapon: other.weapon,
|
||||
special: other.special,
|
||||
grind: other.grind,
|
||||
attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(),
|
||||
tekked: other.tekked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<weapon::Weapon> for PgWeapon {
|
||||
fn into(self) -> weapon::Weapon {
|
||||
let mut attrs: [Option<weapon::WeaponAttribute>; 3] = [None; 3];
|
||||
for (attr, (atype, value)) in attrs.iter_mut().zip(self.attrs.iter()) {
|
||||
*attr = Some(weapon::WeaponAttribute {
|
||||
attr: *atype,
|
||||
value: *value
|
||||
});
|
||||
}
|
||||
|
||||
weapon::Weapon {
|
||||
weapon: self.weapon,
|
||||
special: self.special,
|
||||
grind: self.grind,
|
||||
attrs: attrs,
|
||||
tekked: self.tekked,
|
||||
modifiers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgArmor {
|
||||
armor: armor::ArmorType,
|
||||
dfp: u8,
|
||||
evp: u8,
|
||||
slots: u8,
|
||||
}
|
||||
|
||||
impl From<armor::Armor> for PgArmor {
|
||||
fn from(other: armor::Armor) -> PgArmor {
|
||||
PgArmor {
|
||||
armor: other.armor,
|
||||
dfp: other.dfp,
|
||||
evp: other.evp,
|
||||
slots: other.slots,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<armor::Armor> for PgArmor {
|
||||
fn into(self) -> armor::Armor {
|
||||
armor::Armor {
|
||||
armor: self.armor,
|
||||
dfp: self.dfp,
|
||||
evp: self.evp,
|
||||
slots: self.slots,
|
||||
modifiers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgShield {
|
||||
shield: shield::ShieldType,
|
||||
dfp: u8,
|
||||
evp: u8,
|
||||
}
|
||||
|
||||
impl From<shield::Shield> for PgShield {
|
||||
fn from(other: shield::Shield) -> PgShield {
|
||||
PgShield {
|
||||
shield: other.shield,
|
||||
dfp: other.dfp,
|
||||
evp: other.evp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<shield::Shield> for PgShield {
|
||||
fn into(self) -> shield::Shield {
|
||||
shield::Shield {
|
||||
shield: self.shield,
|
||||
dfp: self.dfp,
|
||||
evp: self.evp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgUnit {
|
||||
unit: unit::UnitType,
|
||||
modifier: Option<unit::UnitModifier>,
|
||||
}
|
||||
|
||||
impl From<unit::Unit> for PgUnit {
|
||||
fn from(other: unit::Unit) -> PgUnit {
|
||||
PgUnit {
|
||||
unit: other.unit,
|
||||
modifier: other.modifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<unit::Unit> for PgUnit {
|
||||
fn into(self) -> unit::Unit {
|
||||
unit::Unit {
|
||||
unit: self.unit,
|
||||
modifier: self.modifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgTool {
|
||||
pub tool: tool::ToolType,
|
||||
}
|
||||
|
||||
impl From<tool::Tool> for PgTool {
|
||||
fn from(other: tool::Tool) -> PgTool {
|
||||
PgTool {
|
||||
tool: other.tool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<tool::Tool> for PgTool {
|
||||
fn into(self) -> tool::Tool {
|
||||
tool::Tool {
|
||||
tool: self.tool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgTechDisk {
|
||||
tech: tech::Technique,
|
||||
level: u32,
|
||||
}
|
||||
|
||||
impl From<tech::TechniqueDisk> for PgTechDisk {
|
||||
fn from(other: tech::TechniqueDisk) -> PgTechDisk {
|
||||
PgTechDisk {
|
||||
tech: other.tech,
|
||||
level: other.level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<tech::TechniqueDisk> for PgTechDisk {
|
||||
fn into(self) -> tech::TechniqueDisk {
|
||||
tech::TechniqueDisk {
|
||||
tech: self.tech,
|
||||
level: self.level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgMag {
|
||||
mag: mag::MagType,
|
||||
synchro: u8,
|
||||
color: u8,
|
||||
}
|
||||
|
||||
impl From<mag::Mag> for PgMag {
|
||||
fn from(other: mag::Mag) -> PgMag {
|
||||
PgMag {
|
||||
mag: other.mag,
|
||||
synchro: other.synchro,
|
||||
color: other.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<mag::Mag> for PgMag {
|
||||
fn into(self) -> mag::Mag {
|
||||
/*mag::Mag {
|
||||
mag: self.mag,
|
||||
synchro: self.synchro,
|
||||
color: self.color,
|
||||
def: 500,
|
||||
pow: 0,
|
||||
dex: 0,
|
||||
mnd: 0,
|
||||
iq: 0,
|
||||
photon_blast: [None; 3],
|
||||
class: CharacterClass::HUmar,
|
||||
id: SectionID::Viridia,
|
||||
}*/
|
||||
let mut mag = mag::Mag::baby_mag(self.color as u16);
|
||||
mag.mag = self.mag;
|
||||
mag.synchro = self.synchro;
|
||||
mag
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PgESWeapon {
|
||||
esweapon: esweapon::ESWeaponType,
|
||||
special: Option<esweapon::ESWeaponSpecial>,
|
||||
name: String,
|
||||
grind: u8,
|
||||
}
|
||||
|
||||
impl From<esweapon::ESWeapon> for PgESWeapon {
|
||||
fn from(other: esweapon::ESWeapon) -> PgESWeapon {
|
||||
PgESWeapon {
|
||||
esweapon: other.esweapon,
|
||||
special: other.special,
|
||||
name: other.name,
|
||||
grind: other.grind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<esweapon::ESWeapon> for PgESWeapon {
|
||||
fn into(self) -> esweapon::ESWeapon {
|
||||
esweapon::ESWeapon {
|
||||
esweapon: self.esweapon,
|
||||
special: self.special,
|
||||
name: self.name,
|
||||
grind: self.grind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum PgItemDetail {
|
||||
Weapon(PgWeapon),
|
||||
Armor(PgArmor),
|
||||
Shield(PgShield),
|
||||
Unit(PgUnit),
|
||||
Tool(PgTool),
|
||||
TechDisk(PgTechDisk),
|
||||
Mag(PgMag),
|
||||
ESWeapon(PgESWeapon),
|
||||
}
|
||||
|
||||
impl From<ItemDetail> for PgItemDetail {
|
||||
fn from(other: ItemDetail) -> PgItemDetail {
|
||||
match other {
|
||||
ItemDetail::Weapon(weapon) => PgItemDetail::Weapon(weapon.into()),
|
||||
ItemDetail::Armor(armor) => PgItemDetail::Armor(armor.into()),
|
||||
ItemDetail::Shield(shield) => PgItemDetail::Shield(shield.into()),
|
||||
ItemDetail::Unit(unit) => PgItemDetail::Unit(unit.into()),
|
||||
ItemDetail::Tool(tool) => PgItemDetail::Tool(tool.into()),
|
||||
ItemDetail::TechniqueDisk(tech_disk) => PgItemDetail::TechDisk(tech_disk.into()),
|
||||
ItemDetail::Mag(mag) => PgItemDetail::Mag(mag.into()),
|
||||
ItemDetail::ESWeapon(esweapon) => PgItemDetail::ESWeapon(esweapon.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ItemDetail> for PgItemDetail {
|
||||
fn into(self) -> ItemDetail {
|
||||
match self {
|
||||
PgItemDetail::Weapon(weapon) => ItemDetail::Weapon(weapon.into()),
|
||||
PgItemDetail::Armor(armor) => ItemDetail::Armor(armor.into()),
|
||||
PgItemDetail::Shield(shield) => ItemDetail::Shield(shield.into()),
|
||||
PgItemDetail::Unit(unit) => ItemDetail::Unit(unit.into()),
|
||||
PgItemDetail::Tool(tool) => ItemDetail::Tool(tool.into()),
|
||||
PgItemDetail::TechDisk(tech_disk) => ItemDetail::TechniqueDisk(tech_disk.into()),
|
||||
PgItemDetail::Mag(mag) => ItemDetail::Mag(mag.into()),
|
||||
PgItemDetail::ESWeapon(esweapon) => ItemDetail::ESWeapon(esweapon.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgItem {
|
||||
pub id: i32,
|
||||
pub item: sqlx::types::Json<PgItemDetail>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum PgItemLocationDetail {
|
||||
Inventory {
|
||||
character_id: u32,
|
||||
#[serde(skip_serializing)]
|
||||
slot: usize,
|
||||
equipped: bool,
|
||||
},
|
||||
Bank {
|
||||
character_id: u32,
|
||||
name: String,
|
||||
},
|
||||
LocalFloor {
|
||||
character_id: u32,
|
||||
map_area: MapArea,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
},
|
||||
SharedFloor {
|
||||
map_area: MapArea,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
},
|
||||
Consumed,
|
||||
FedToMag {
|
||||
mag: u32,
|
||||
},
|
||||
Shop,
|
||||
}
|
||||
|
||||
impl From<ItemLocation> for PgItemLocationDetail {
|
||||
fn from(other: ItemLocation) -> PgItemLocationDetail {
|
||||
match other {
|
||||
ItemLocation::Inventory{character_id, slot, equipped} => PgItemLocationDetail::Inventory{character_id: character_id.0, slot, equipped},
|
||||
ItemLocation::Bank{character_id, name} => PgItemLocationDetail::Bank{character_id: character_id.0, name: name.0},
|
||||
ItemLocation::LocalFloor{character_id, map_area, x,y,z} => PgItemLocationDetail::LocalFloor{character_id: character_id.0, map_area, x,y,z},
|
||||
ItemLocation::SharedFloor{map_area, x,y,z} => PgItemLocationDetail::SharedFloor{map_area, x,y,z},
|
||||
ItemLocation::Consumed => PgItemLocationDetail::Consumed,
|
||||
ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0},
|
||||
ItemLocation::Shop => PgItemLocationDetail::Shop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ItemLocation> for PgItemLocationDetail {
|
||||
fn into(self) -> ItemLocation {
|
||||
match self {
|
||||
PgItemLocationDetail::Inventory{character_id, slot, equipped} => ItemLocation::Inventory{character_id: CharacterEntityId(character_id), slot, equipped},
|
||||
PgItemLocationDetail::Bank{character_id, name} => ItemLocation::Bank{character_id: CharacterEntityId(character_id), name: BankName(name)},
|
||||
PgItemLocationDetail::LocalFloor{character_id, map_area, x,y,z} => ItemLocation::LocalFloor{character_id: CharacterEntityId(character_id), map_area, x,y,z},
|
||||
PgItemLocationDetail::SharedFloor{map_area, x,y,z} => ItemLocation::SharedFloor{map_area, x,y,z},
|
||||
PgItemLocationDetail::Consumed => ItemLocation::Consumed,
|
||||
PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)},
|
||||
PgItemLocationDetail::Shop => ItemLocation::Shop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgItemLocation {
|
||||
//pub id: i32,
|
||||
pub location: sqlx::types::Json<PgItemLocationDetail>,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum PgMagModifierDetail {
|
||||
FeedMag(i32),
|
||||
BankMag,
|
||||
MagCell(i32),
|
||||
OwnerChange(CharacterClass, SectionID)
|
||||
}
|
||||
|
||||
impl From<mag::MagModifier> for PgMagModifierDetail {
|
||||
fn from(other: mag::MagModifier) -> PgMagModifierDetail {
|
||||
match other {
|
||||
mag::MagModifier::FeedMag{food} => PgMagModifierDetail::FeedMag(food.0 as i32),
|
||||
mag::MagModifier::BankMag => PgMagModifierDetail::BankMag,
|
||||
mag::MagModifier::MagCell(cell) => PgMagModifierDetail::MagCell(cell.0 as i32),
|
||||
mag::MagModifier::OwnerChange(class, section_id) => PgMagModifierDetail::OwnerChange(class, section_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<mag::MagModifier> for PgMagModifierDetail {
|
||||
fn into(self) -> mag::MagModifier {
|
||||
match self {
|
||||
PgMagModifierDetail::FeedMag(food) => mag::MagModifier::FeedMag{food: ItemEntityId(food as u32)},
|
||||
PgMagModifierDetail::BankMag => mag::MagModifier::BankMag,
|
||||
PgMagModifierDetail::MagCell(cell) => mag::MagModifier::MagCell(ItemEntityId(cell as u32)),
|
||||
PgMagModifierDetail::OwnerChange(class, section_id) => mag::MagModifier::OwnerChange(class, section_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgMagModifier {
|
||||
mag: i32,
|
||||
pub modifier: sqlx::types::Json<PgMagModifierDetail>,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgItemWithLocation {
|
||||
pub id: i32,
|
||||
pub item: sqlx::types::Json<PgItemDetail>,
|
||||
pub location: sqlx::types::Json<PgItemLocationDetail>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct PgMagModifierWithParameters {
|
||||
pub mag: i32,
|
||||
pub modifier: sqlx::types::Json<PgMagModifierDetail>,
|
||||
pub feed: Option<sqlx::types::Json<PgTool>>,
|
||||
pub cell: Option<sqlx::types::Json<PgTool>>,
|
||||
}
|
404
src/entity/gateway/postgres/postgres.rs
Normal file
404
src/entity/gateway/postgres/postgres.rs
Normal file
@ -0,0 +1,404 @@
|
||||
use std::convert::{From, TryFrom, Into, TryInto};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use futures::future::join_all;
|
||||
use futures::TryStreamExt;
|
||||
//use futures::StreamExt;
|
||||
use async_std::stream::StreamExt;
|
||||
//use futures::StreamExt;
|
||||
use libpso::character::{settings, guildcard};
|
||||
use libpso::util::vec_to_array;
|
||||
use crate::entity::account::*;
|
||||
use crate::entity::character::*;
|
||||
use crate::entity::gateway::EntityGateway;
|
||||
use crate::entity::item::*;
|
||||
use super::models::*;
|
||||
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::Row;
|
||||
use sqlx::Execute;
|
||||
use postgres::{Client, NoTls};
|
||||
|
||||
mod embedded {
|
||||
use refinery::embed_migrations;
|
||||
embed_migrations!("src/entity/gateway/postgres/migrations");
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PostgresGateway {
|
||||
pool: sqlx::Pool<sqlx::Postgres>,
|
||||
}
|
||||
|
||||
impl PostgresGateway {
|
||||
pub fn new(host: &str, dbname: &str, username: &str, password: &str) -> PostgresGateway {
|
||||
// the postgres dep can be removed once refinery supports sqlx
|
||||
let mut conn = Client::connect(&format!("host='{}' dbname='{}' user='{}' password='{}'", host, dbname, username, password), NoTls).unwrap();
|
||||
embedded::migrations::runner().run(&mut conn).unwrap();
|
||||
|
||||
let pool = async_std::task::block_on(async move {
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap();
|
||||
|
||||
pool
|
||||
});
|
||||
|
||||
PostgresGateway {
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_item_modifications(&self, item: ItemEntity) -> ItemEntity {
|
||||
let ItemEntity {id, item, location} = item;
|
||||
|
||||
let item = match item {
|
||||
ItemDetail::Mag(mut mag) => {
|
||||
let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell
|
||||
from mag_modifier
|
||||
left join item on item.id = cast (modifier ->> 'FeedMag' as integer)
|
||||
left join item as item2 on item2.id = cast (modifier ->> 'MagCell' as integer)
|
||||
where mag = $1 order by created_at"#;
|
||||
let mag_modifiers = sqlx::query_as::<_, PgMagModifierWithParameters>(q)
|
||||
.bind(id.0 as i32)
|
||||
.fetch(&self.pool);
|
||||
|
||||
mag_modifiers.for_each(|modifier| {
|
||||
let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap();
|
||||
let modifier: mag::MagModifier = modifier.0.into();
|
||||
match modifier {
|
||||
mag::MagModifier::FeedMag{..} => {
|
||||
mag.feed(feed.unwrap().tool)
|
||||
},
|
||||
mag::MagModifier::BankMag => {
|
||||
mag.bank()
|
||||
},
|
||||
mag::MagModifier::MagCell(_) => {
|
||||
mag.apply_mag_cell(mag::MagCell::try_from(Into::<tool::Tool>::into(cell.unwrap().0).tool).unwrap())
|
||||
},
|
||||
mag::MagModifier::OwnerChange(class, section_id) => {
|
||||
mag.change_owner(class, section_id)
|
||||
},
|
||||
}
|
||||
}).await;
|
||||
|
||||
ItemDetail::Mag(mag)
|
||||
},
|
||||
item @ _ => item
|
||||
};
|
||||
|
||||
ItemEntity {
|
||||
id: id,
|
||||
item: item,
|
||||
location: location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EntityGateway for PostgresGateway {
|
||||
async fn create_user(&mut self, user: NewUserAccountEntity) -> Option<UserAccountEntity> {
|
||||
let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password) values ($1, $2, $3) returning *;")
|
||||
.bind(user.email)
|
||||
.bind(user.username)
|
||||
.bind(user.password)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
Some(new_user.into())
|
||||
}
|
||||
|
||||
async fn get_user_by_id(&self, id: UserAccountId) -> Option<UserAccountEntity> {
|
||||
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1")
|
||||
.bind(id.0)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
Some(user.into())
|
||||
}
|
||||
|
||||
async fn get_user_by_name(&self, username: String) -> Option<UserAccountEntity> {
|
||||
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where username = $1")
|
||||
.bind(username)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
Some(user.into())
|
||||
}
|
||||
|
||||
async fn save_user(&mut self, user: &UserAccountEntity) {
|
||||
sqlx::query("UPDATE user_accounts set name=$1, password=$2, banned=$3, muted=$4, flags=$5 where id=$6")
|
||||
.bind(&user.username)
|
||||
.bind(&user.password)
|
||||
.bind(&user.banned_until)
|
||||
.bind(&user.muted_until)
|
||||
.bind(&user.flags)
|
||||
.bind(&user.id.0)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
}
|
||||
|
||||
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Option<UserSettingsEntity> {
|
||||
let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;")
|
||||
.bind(settings.user_id.0)
|
||||
.bind(settings.settings.blocked_users.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(settings.settings.key_config.to_vec())
|
||||
.bind(settings.settings.joystick_config.to_vec())
|
||||
.bind(settings.settings.option_flags as i32)
|
||||
.bind(settings.settings.shortcuts.to_vec())
|
||||
.bind(settings.settings.symbol_chats.to_vec())
|
||||
.bind(settings.settings.team_name.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
Some(new_settings.into())
|
||||
}
|
||||
|
||||
async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Option<UserSettingsEntity> {
|
||||
let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where id = $1")
|
||||
.bind(user.id.0)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
Some(settings.into())
|
||||
}
|
||||
|
||||
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) {
|
||||
sqlx::query("update user_settings set blocked_users=$1, key_config=$2, joystick_config=$3, option_flags=$4, shortcuts=$5, symbol_chats=$6, team_name=$7 where id=$8")
|
||||
.bind(settings.settings.blocked_users.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(&settings.settings.key_config.to_vec())
|
||||
.bind(&settings.settings.joystick_config.to_vec())
|
||||
.bind(&settings.settings.option_flags)
|
||||
.bind(&settings.settings.shortcuts.to_vec())
|
||||
.bind(&settings.settings.symbol_chats.to_vec())
|
||||
.bind(settings.settings.team_name.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
|
||||
.bind(&settings.id.0)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
}
|
||||
|
||||
async fn create_character(&mut self, char: NewCharacterEntity) -> Option<CharacterEntity> {
|
||||
let q = r#"insert into player_character
|
||||
(user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
|
||||
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, meseta, bank_meseta, option_flags)
|
||||
values
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)
|
||||
returning *;"#;
|
||||
let character = sqlx::query_as::<_, PgCharacter>(q)
|
||||
//sqlx::query(q)
|
||||
.bind(char.user_id.0)
|
||||
.bind(char.slot as i16)
|
||||
.bind(char.name)
|
||||
.bind(char.exp as i32)
|
||||
.bind(char.char_class.to_string())
|
||||
.bind(char.section_id.to_string())
|
||||
.bind(char.appearance.costume as i16)
|
||||
.bind(char.appearance.skin as i16)
|
||||
.bind(char.appearance.face as i16)
|
||||
.bind(char.appearance.head as i16)
|
||||
.bind(char.appearance.hair as i16)
|
||||
.bind(char.appearance.hair_r as i16)
|
||||
.bind(char.appearance.hair_g as i16)
|
||||
.bind(char.appearance.hair_b as i16)
|
||||
.bind(char.appearance.prop_x)
|
||||
.bind(char.appearance.prop_y)
|
||||
.bind(&char.techs.as_bytes().to_vec())
|
||||
.bind(&char.config.as_bytes().to_vec())
|
||||
.bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0)))
|
||||
.bind(char.guildcard.description)
|
||||
.bind(char.materials.power as i16)
|
||||
.bind(char.materials.mind as i16)
|
||||
.bind(char.materials.def as i16)
|
||||
.bind(char.materials.evade as i16)
|
||||
.bind(char.materials.luck as i16)
|
||||
.bind(char.materials.hp as i16)
|
||||
.bind(char.materials.tp as i16)
|
||||
.bind(char.tech_menu.tech_menu.to_vec())
|
||||
.bind(char.meseta as i32)
|
||||
.bind(char.bank_meseta as i32)
|
||||
.bind(char.option_flags as i32)
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
|
||||
sqlx::query("insert into inventory_slots (pchar) values ($1)")
|
||||
.bind(character.id)
|
||||
.execute(&self.pool).await.unwrap();
|
||||
Some(character.into())
|
||||
}
|
||||
|
||||
async fn get_characters_by_user(&self, user: &UserAccountEntity) -> [Option<CharacterEntity>; 4] {
|
||||
let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot")
|
||||
.bind(user.id.0)
|
||||
.fetch(&self.pool);
|
||||
let mut result = [None; 4];
|
||||
while let Some(character) = stream.try_next().await.unwrap() {
|
||||
let index = character.slot as usize;
|
||||
result[index] = Some(character.into())
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn save_character(&mut self, char: &CharacterEntity) {
|
||||
let q = r#"update player_character set
|
||||
user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12,
|
||||
hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23,
|
||||
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, meseta=$29, bank_meseta=$30
|
||||
where id=$31;"#;
|
||||
sqlx::query(q)
|
||||
.bind(char.user_id.0)
|
||||
.bind(char.slot as i16)
|
||||
.bind(&char.name)
|
||||
.bind(char.exp as i32)
|
||||
.bind(char.char_class.to_string())
|
||||
.bind(char.section_id.to_string())
|
||||
.bind(char.appearance.costume as i16)
|
||||
.bind(char.appearance.skin as i16)
|
||||
.bind(char.appearance.face as i16)
|
||||
.bind(char.appearance.head as i16)
|
||||
.bind(char.appearance.hair as i16)
|
||||
.bind(char.appearance.hair_r as i16)
|
||||
.bind(char.appearance.hair_g as i16)
|
||||
.bind(char.appearance.hair_b as i16)
|
||||
.bind(char.appearance.prop_x)
|
||||
.bind(char.appearance.prop_y)
|
||||
.bind(&char.techs.as_bytes().to_vec())
|
||||
.bind(&char.config.as_bytes().to_vec())
|
||||
.bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0)))
|
||||
.bind(&char.guildcard.description)
|
||||
.bind(char.materials.power as i16)
|
||||
.bind(char.materials.mind as i16)
|
||||
.bind(char.materials.def as i16)
|
||||
.bind(char.materials.evade as i16)
|
||||
.bind(char.materials.luck as i16)
|
||||
.bind(char.materials.hp as i16)
|
||||
.bind(char.materials.tp as i16)
|
||||
.bind(char.tech_menu.tech_menu.to_vec())
|
||||
.bind(char.meseta as i32)
|
||||
.bind(char.bank_meseta as i32)
|
||||
.bind(char.id.0 as i32)
|
||||
.execute(&self.pool).await.unwrap();
|
||||
}
|
||||
|
||||
async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> GuildCardDataEntity {
|
||||
GuildCardDataEntity {
|
||||
id: GuildCardDataId(0),
|
||||
user_id: user.id,
|
||||
guildcard: guildcard::GuildCardData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_item(&mut self, item: NewItemEntity) -> Option<ItemEntity> {
|
||||
let new_item = sqlx::query_as::<_, PgItem>("insert into item (item) values ($1) returning *;")
|
||||
.bind(sqlx::types::Json(PgItemDetail::from(item.item)))
|
||||
.fetch_one(&self.pool).await.unwrap();
|
||||
let location = if let ItemLocation::Inventory{character_id, slot, ..} = &item.location {
|
||||
sqlx::query("insert into item_location (item, location) values ($1, $2)")
|
||||
.bind(new_item.id)
|
||||
.bind(sqlx::types::Json(PgItemLocationDetail::from(item.location.clone())))
|
||||
.execute(&self.pool).await.unwrap();
|
||||
sqlx::query("update inventory_slots set items[$2] = $1 where pchar = $3")
|
||||
.bind(new_item.id)
|
||||
.bind(*slot as i32)
|
||||
.bind(character_id.0 as i32)
|
||||
.execute(&self.pool).await.unwrap();
|
||||
sqlx::query_as::<_, PgItemLocation>(r#"select
|
||||
item_location.item,
|
||||
jsonb_set(item_location.location, '{Inventory,slot}', (array_position(inventory_slots.items, item.id))::text::jsonb) as location,
|
||||
item_location.created_at
|
||||
from item_location
|
||||
join item on item.id = item_location.item
|
||||
join inventory_slots on inventory_slots.pchar = cast (item_location.location -> 'Inventory' ->> 'character_id' as integer)
|
||||
where item.id = $1
|
||||
order by item_location.created_at
|
||||
limit 1"#)
|
||||
.bind(new_item.id)
|
||||
.fetch_one(&self.pool).await.unwrap()
|
||||
}
|
||||
else {
|
||||
sqlx::query_as::<_, PgItemLocation>("insert into item_location (item, location) values ($1, $2) returning *")
|
||||
.bind(new_item.id)
|
||||
.bind(sqlx::types::Json(PgItemLocationDetail::from(item.location)))
|
||||
.fetch_one(&self.pool).await.unwrap()
|
||||
};
|
||||
Some(ItemEntity {
|
||||
id: ItemEntityId(new_item.id as u32),
|
||||
item: new_item.item.0.into(),
|
||||
location: location.location.0.into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn change_item_location(&mut self, item_id: &ItemEntityId, item_location: ItemLocation) {
|
||||
if let ItemLocation::Inventory{character_id, slot, ..} = &item_location {
|
||||
sqlx::query("update inventory_slots set items[array_position(items, $1)] = null where pchar = $2 and items[array_position(items, $1)] is not null")
|
||||
.bind(item_id.0 as i32)
|
||||
.bind(character_id.0 as i32)
|
||||
.execute(&self.pool).await.unwrap();
|
||||
sqlx::query("update inventory_slots set items[$2] = $1 where pchar = $3")
|
||||
.bind(item_id.0 as i32)
|
||||
.bind(*slot as i32)
|
||||
.bind(character_id.0 as i32)
|
||||
.execute(&self.pool).await.unwrap();
|
||||
sqlx::query(r#"insert into item_location (item, location)
|
||||
select $1, $2
|
||||
where (select jsonb_object_keys(location) from item_location where item=$1
|
||||
order by created_at desc limit 1) != 'Inventory'"#)
|
||||
.bind(item_id.0)
|
||||
.bind(sqlx::types::Json(PgItemLocationDetail::from(item_location)))
|
||||
.execute(&self.pool).await.unwrap();
|
||||
}
|
||||
else {
|
||||
sqlx::query("insert into item_location (item, location) values ($1, $2)")
|
||||
.bind(item_id.0)
|
||||
.bind(sqlx::types::Json(PgItemLocationDetail::from(item_location)))
|
||||
.execute(&self.pool).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) {
|
||||
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
|
||||
.bind(mag_item_id.0)
|
||||
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id})))
|
||||
.execute(&self.pool).await.unwrap();
|
||||
}
|
||||
|
||||
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) {
|
||||
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
|
||||
.bind(mag_item_id.0)
|
||||
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id))))
|
||||
.execute(&self.pool).await.unwrap();
|
||||
}
|
||||
|
||||
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) {
|
||||
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
|
||||
.bind(mag_item_id.0)
|
||||
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id))))
|
||||
.execute(&self.pool).await.unwrap();
|
||||
}
|
||||
|
||||
async fn get_items_by_character(&self, char: &CharacterEntity) -> Vec<ItemEntity> {
|
||||
let q = r#"select * from (
|
||||
select distinct on (item_location.item)
|
||||
item.id,
|
||||
case
|
||||
when item_location.location -> 'Inventory' is not null then
|
||||
jsonb_set(item_location.location, '{Inventory,slot}', (array_position(inventory_slots.items, item.id))::text::jsonb)
|
||||
else
|
||||
item_location.location
|
||||
end,
|
||||
item.item
|
||||
from item_location
|
||||
join item on item.id = item_location.item
|
||||
join inventory_slots on inventory_slots.pchar = $1
|
||||
order by item_location.item, item_location.created_at desc
|
||||
) as i
|
||||
where cast (location -> 'Inventory' ->> 'character_id' as integer) = $1
|
||||
or cast (location -> 'Bank' ->> 'character_id' as integer) = $1
|
||||
"#;
|
||||
let items = sqlx::query_as::<_, PgItemWithLocation>(q)
|
||||
.bind(char.id.0)
|
||||
.fetch(&self.pool);
|
||||
join_all(items
|
||||
.filter_map(|item: Result<PgItemWithLocation, _>| {
|
||||
let item = item.ok()?;
|
||||
Some(ItemEntity {
|
||||
id: ItemEntityId(item.id as u32),
|
||||
item: item.item.0.into(),
|
||||
location: item.location.0.into()
|
||||
})
|
||||
})
|
||||
.map(|item: ItemEntity| {
|
||||
self.apply_item_modifications(item)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
).await
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
// TODO: actually use this
|
||||
#[derive(Debug)]
|
||||
pub enum ItemParseError {
|
||||
@ -8,7 +9,7 @@ pub enum ItemParseError {
|
||||
InvalidESWeaponName,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ESWeaponType {
|
||||
Saber = 0,
|
||||
Sword,
|
||||
@ -120,7 +121,7 @@ impl ESWeaponType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ESWeaponSpecial {
|
||||
Jellen = 1,
|
||||
Zalure,
|
||||
|
@ -48,6 +48,31 @@ impl Technique {
|
||||
Technique::Megid => 18,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: u8) -> Technique {
|
||||
match value {
|
||||
0 => Technique::Foie,
|
||||
1 => Technique::Gifoie,
|
||||
2 => Technique::Rafoie,
|
||||
3 => Technique::Barta,
|
||||
4 => Technique::Gibarta,
|
||||
5 => Technique::Rabarta,
|
||||
6 => Technique::Zonde,
|
||||
7 => Technique::Gizonde,
|
||||
8 => Technique::Razonde,
|
||||
9 => Technique::Grants,
|
||||
10 => Technique::Deband,
|
||||
11 => Technique::Jellen,
|
||||
12 => Technique::Zalure,
|
||||
13 => Technique::Shifta,
|
||||
14 => Technique::Ryuker,
|
||||
15 => Technique::Resta,
|
||||
16 => Technique::Anti,
|
||||
17 => Technique::Reverser,
|
||||
18 => Technique::Megid,
|
||||
_ => panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
|
@ -323,7 +323,7 @@ impl UnitType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum UnitModifier {
|
||||
PlusPlus,
|
||||
Plus,
|
||||
|
@ -10,7 +10,7 @@ pub enum ItemParseError {
|
||||
InvalidWeaponAttribute,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum Attribute {
|
||||
Native = 1,
|
||||
ABeast,
|
||||
@ -45,7 +45,7 @@ impl WeaponAttribute {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum WeaponSpecial {
|
||||
Draw = 1,
|
||||
Drain,
|
||||
|
@ -677,10 +677,10 @@ mod test {
|
||||
username: "testuser".to_owned(),
|
||||
password: bcrypt::hash("mypassword", 5).unwrap(),
|
||||
guildcard: 0,
|
||||
banned_until: None,
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
team_id: None,
|
||||
banned: false,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
flags: 0,
|
||||
});
|
||||
server.clients.insert(ClientId(5), clientstate);
|
||||
@ -721,9 +721,9 @@ mod test {
|
||||
password: bcrypt::hash("qwer", 5).unwrap(),
|
||||
guildcard: 3,
|
||||
team_id: None,
|
||||
banned: false,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
banned_until: None,
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
flags: 0,
|
||||
});
|
||||
|
||||
|
@ -63,7 +63,7 @@ pub async fn get_login_status(entity_gateway: &impl EntityGateway, pkt: &Login)
|
||||
let user = entity_gateway.get_user_by_name(username).await.ok_or(AccountStatus::InvalidUser)?;
|
||||
let verified = bcrypt::verify(password, user.password.as_str()).map_err(|_err| AccountStatus::Error)?;
|
||||
match verified {
|
||||
true => if user.banned {
|
||||
true => if user.banned_until.map(|banned| banned > chrono::Utc::now()).unwrap_or(false) {
|
||||
Err(AccountStatus::Banned)
|
||||
}
|
||||
else {
|
||||
@ -186,9 +186,9 @@ mod test {
|
||||
password: bcrypt::hash("mypassword", 5).unwrap(),
|
||||
guildcard: 0,
|
||||
team_id: None,
|
||||
banned: false,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
banned_until: None,
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
flags: 0,
|
||||
})
|
||||
}
|
||||
@ -270,9 +270,9 @@ mod test {
|
||||
password: bcrypt::hash("notpassword", 5).unwrap(),
|
||||
guildcard: 0,
|
||||
team_id: None,
|
||||
banned: false,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
banned_until: None,
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
flags: 0,
|
||||
})
|
||||
}
|
||||
@ -315,9 +315,9 @@ mod test {
|
||||
password: bcrypt::hash("mypassword", 5).unwrap(),
|
||||
guildcard: 0,
|
||||
team_id: None,
|
||||
banned: true,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
banned_until: Some(chrono::Utc::now() + chrono::Duration::days(1)),
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
flags: 0,
|
||||
})
|
||||
}
|
||||
|
@ -565,5 +565,9 @@ impl CharacterInventory {
|
||||
(item, slot)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &InventoryItem> {
|
||||
self.items.iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,35 @@ pub enum ItemManagerError {
|
||||
ItemIdNotInInventory(ClientItemId)
|
||||
}
|
||||
|
||||
|
||||
async fn update_inventory_slots<EG: EntityGateway>(entity_gateway: &mut EG, character: &CharacterEntity, inventory: &CharacterInventory) {
|
||||
for (slot, item) in inventory.iter().enumerate() {
|
||||
match item {
|
||||
InventoryItem::Individual(individual_inventory_item) => {
|
||||
entity_gateway.change_item_location(
|
||||
&individual_inventory_item.entity_id,
|
||||
ItemLocation::Inventory {
|
||||
character_id: character.id,
|
||||
slot: slot,
|
||||
equipped: individual_inventory_item.equipped,
|
||||
}
|
||||
).await
|
||||
},
|
||||
InventoryItem::Stacked(stacked_inventory_item) => {
|
||||
for entity_id in stacked_inventory_item.entity_ids.iter() {
|
||||
entity_gateway.change_item_location(
|
||||
entity_id,
|
||||
ItemLocation::Inventory {
|
||||
character_id: character.id,
|
||||
slot: slot,
|
||||
equipped: false,
|
||||
}).await;
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct ItemManager {
|
||||
id_counter: u32,
|
||||
|
||||
@ -449,6 +478,7 @@ impl ItemManager {
|
||||
},
|
||||
}
|
||||
|
||||
update_inventory_slots(entity_gateway, character, &inventory).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -528,6 +558,7 @@ impl ItemManager {
|
||||
ItemLocation::Consumed).await;
|
||||
}
|
||||
|
||||
update_inventory_slots(entity_gateway, character, &inventory).await;
|
||||
Ok(consumed_item)
|
||||
}
|
||||
|
||||
@ -564,6 +595,7 @@ impl ItemManager {
|
||||
}
|
||||
}
|
||||
|
||||
update_inventory_slots(entity_gateway, character, &inventory).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -640,6 +672,7 @@ impl ItemManager {
|
||||
}).await;
|
||||
}
|
||||
|
||||
update_inventory_slots(entity_gateway, character, &inventory).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -754,6 +787,7 @@ impl ItemManager {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
update_inventory_slots(entity_gateway, character, &inventory).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
// TOOD: `pub(super) for most of these?`
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use crate::ship::room::Episode;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MapArea {
|
||||
Pioneer2Ep1,
|
||||
Forest1,
|
||||
|
@ -15,13 +15,14 @@ use libpso::{utf8_to_array, utf8_to_utf16_array};
|
||||
|
||||
pub async fn new_user_character<EG: EntityGateway>(entity_gateway: &mut EG, username: &str, password: &str) -> (UserAccountEntity, CharacterEntity) {
|
||||
let new_user = NewUserAccountEntity {
|
||||
email: format!("{}@pso.com", username),
|
||||
username: username.into(),
|
||||
password: bcrypt::hash(password, 5).unwrap(),
|
||||
guildcard: 1,
|
||||
team_id: None,
|
||||
banned: false,
|
||||
muted_until: SystemTime::now(),
|
||||
created_at: SystemTime::now(),
|
||||
banned_until: None,
|
||||
muted_until: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
|
@ -506,6 +506,7 @@ async fn test_techs_disappear_from_shop_when_bought() {
|
||||
assert!(p1_items[0].item != p1_items[1].item);
|
||||
}
|
||||
|
||||
// TOOD: this is not deterministic and can randomly fail
|
||||
#[async_std::test]
|
||||
async fn test_units_disappear_from_shop_when_bought() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
Loading…
x
Reference in New Issue
Block a user