Browse Source

Merge pull request 'postgres' (#229) from postgres into master

pbs
jake 4 years ago
parent
commit
a796c4f07b
  1. 6
      Cargo.toml
  2. 3
      data/item_stats/mag_stats.toml
  3. 10
      src/bin/main.rs
  4. 2
      src/common/mainloop/client.rs
  5. 37
      src/common/mainloop/interserver.rs
  6. 13
      src/entity/account.rs
  7. 10
      src/entity/character.rs
  8. 4
      src/entity/gateway/entitygateway.rs
  9. 2
      src/entity/gateway/inmemory.rs
  10. 2
      src/entity/gateway/mod.rs
  11. 121
      src/entity/gateway/postgres/migrations/V0001__initial.sql
  12. 3
      src/entity/gateway/postgres/migrations/mod.rs
  13. 5
      src/entity/gateway/postgres/mod.rs
  14. 691
      src/entity/gateway/postgres/models.rs
  15. 404
      src/entity/gateway/postgres/postgres.rs
  16. 7
      src/entity/item/esweapon.rs
  17. 25
      src/entity/item/tech.rs
  18. 2
      src/entity/item/unit.rs
  19. 4
      src/entity/item/weapon.rs
  20. 12
      src/login/character.rs
  21. 20
      src/login/login.rs
  22. 4
      src/ship/items/inventory.rs
  23. 34
      src/ship/items/manager.rs
  24. 3
      src/ship/map/area.rs
  25. 16
      src/ship/packet/handler/lobby.rs
  26. 7
      tests/common.rs
  27. 1
      tests/test_shops.rs

6
Cargo.toml

@ -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
data/item_stats/mag_stats.toml

@ -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

10
src/bin/main.rs

@ -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();

2
src/common/mainloop/client.rs

@ -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?;

37
src/common/mainloop/interserver.rs

@ -139,27 +139,32 @@ where
}
async fn interserver_send_loop<S>(server_id: ServerId,
mut socket: async_std::net::TcpStream,
output_loop_receiver: async_std::sync::Receiver<S>)
mut socket: async_std::net::TcpStream,
output_loop_receiver: async_std::sync::Receiver<S>)
where
S: Serialize + std::fmt::Debug + Send + 'static,
{
async_std::task::spawn(async move {
loop {
info!("login send loop");
let msg = output_loop_receiver.recv().await.unwrap();
let payload = serde_json::to_string(&msg);
if let Ok(payload) = payload {
let len_bytes = u32::to_le_bytes(payload.len() as u32);
match socket.write_all(&len_bytes).await {
Ok(_) => {},
Err(err) => warn!("send failed: {:?}", err),
}
match socket.write_all(&payload.as_bytes()).await {
Ok(_) => {},
Err(err) => warn!("send failed: {:?}", err),
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);
match socket.write_all(&len_bytes).await {
Ok(_) => {},
Err(err) => warn!("send failed: {:?}", err),
}
match socket.write_all(&payload.as_bytes()).await {
Ok(_) => {},
Err(err) => warn!("send failed: {:?}", err),
}
}
},
Err(err) => {
warn!("error in send_loop: {:?}, {:?}", server_id, err)
}
}
}
@ -179,7 +184,7 @@ pub fn login_listen_mainloop<EG: EntityGateway + 'static>(state: Arc<Mutex<Chara
loop {
let (socket, addr) = listener.accept().await.unwrap();
info!("new ship server: {:?} {:?}", socket, addr);
id += 1;
let server_id = crate::common::interserver::ServerId(id);

13
src/entity/account.rs

@ -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?
}

10
src/entity/character.rs

@ -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 {

4
src/entity/gateway/entitygateway.rs

@ -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!();
}

2
src/entity/gateway/inmemory.rs

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

2
src/entity/gateway/mod.rs

@ -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

@ -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

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

5
src/entity/gateway/postgres/mod.rs

@ -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

@ -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

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

7
src/entity/item/esweapon.rs

@ -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,
@ -305,4 +306,4 @@ mod test {
}
}
}

25
src/entity/item/tech.rs

@ -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)]

2
src/entity/item/unit.rs

@ -323,7 +323,7 @@ impl UnitType {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum UnitModifier {
PlusPlus,
Plus,

4
src/entity/item/weapon.rs

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

12
src/login/character.rs

@ -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,
});

20
src/login/login.rs

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

4
src/ship/items/inventory.rs

@ -565,5 +565,9 @@ impl CharacterInventory {
(item, slot)
})
}
pub fn iter(&self) -> impl Iterator<Item = &InventoryItem> {
self.items.iter()
}
}

34
src/ship/items/manager.rs

@ -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(())
}

3
src/ship/map/area.rs

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

16
src/ship/packet/handler/lobby.rs

@ -91,15 +91,15 @@ pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
let old_neighbors = client_location.get_client_neighbors(id).unwrap();
let mut lobby = LobbyId(requested_lobby as usize);
if let Err(_) = client_location.add_client_to_lobby(id, lobby) {
match prev_area {
RoomLobby::Lobby(_lobby) => {
let dialog = SmallDialog::new(String::from("Lobby is full."));
return Ok(vec![(id, SendShipPacket::SmallDialog(dialog))])
}
RoomLobby::Room(_room) => {
lobby = client_location.add_client_to_next_available_lobby(id, lobby).map_err(|_| ShipError::TooManyClients)?;
}
match prev_area {
RoomLobby::Lobby(_lobby) => {
let dialog = SmallDialog::new(String::from("Lobby is full."));
return Ok(vec![(id, SendShipPacket::SmallDialog(dialog))])
}
RoomLobby::Room(_room) => {
lobby = client_location.add_client_to_next_available_lobby(id, lobby).map_err(|_| ShipError::TooManyClients)?;
}
}
}
item_manager.load_character(entity_gateway, &client.character).await;
let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?;

7
tests/common.rs

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

1
tests/test_shops.rs

@ -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…
Cancel
Save