Compare commits

..

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

20 changed files with 2616 additions and 675 deletions

2388
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ async-recursion= "1.0.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] } barrel = { version = "0.6.5", features = ["pg"] }
refinery = { version = "0.5.0", features = ["postgres"] } refinery = { version = "0.5.0", features = ["postgres"] }
sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] } sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] }
strum = "0.19.5" strum = "0.19.5"
strum_macros = "0.19" strum_macros = "0.19"
anyhow = { version = "1.0.68", features = ["backtrace"] } anyhow = { version = "1.0.68", features = ["backtrace"] }

View File

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

View File

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

View File

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

View File

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

View File

@ -7,10 +7,7 @@ use libpso::util::vec_to_array;
use crate::entity::account::*; use crate::entity::account::*;
use crate::entity::character::*; use crate::entity::character::*;
use crate::entity::item::*; use crate::entity::item::*;
use crate::entity::room::*;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::ship::room::{Episode, Difficulty};
use crate::ship::monster::MonsterType;
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount { pub struct PgUserAccount {
@ -580,16 +577,6 @@ pub enum PgItemNoteDetail {
}, },
EnemyDrop { EnemyDrop {
character_id: u32, character_id: u32,
room_id: u32,
monster_type: MonsterType,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: u32,
room_id: u32,
map_area: MapArea, map_area: MapArea,
x: f32, x: f32,
y: f32, y: f32,
@ -605,19 +592,14 @@ pub enum PgItemNoteDetail {
y: f32, y: f32,
z: f32, z: f32,
}, },
Consumed { Consumed,
character_id: u32,
},
FedToMag { FedToMag {
character_id: u32,
mag: u32, mag: u32,
}, },
BoughtAtShop { BoughtAtShop {
character_id: u32, character_id: u32,
}, },
SoldToShop { SoldToShop,
character_id: u32,
},
Trade { Trade {
trade_id: u32, trade_id: u32,
character_to: u32, character_to: u32,
@ -642,16 +624,8 @@ impl From<ItemNote> for PgItemNoteDetail {
ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation { ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation {
character_id: character_id.0, character_id: character_id.0,
}, },
ItemNote::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop { ItemNote::EnemyDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
character_id: character_id.0, character_id: character_id.0,
room_id: room_id.0,
monster_type,
map_area,
x,y,z,
},
ItemNote::BoxDrop{character_id, room_id, map_area, x, y, z} => PgItemNoteDetail::BoxDrop {
character_id: character_id.0,
room_id: room_id.0,
map_area, map_area,
x,y,z, x,y,z,
}, },
@ -663,19 +637,14 @@ impl From<ItemNote> for PgItemNoteDetail {
map_area, map_area,
x,y,z, x,y,z,
}, },
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed { ItemNote::Consumed => PgItemNoteDetail::Consumed,
character_id: character_id.0, ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
},
ItemNote::FedToMag{character_id, mag} => PgItemNoteDetail::FedToMag{
character_id: character_id.0,
mag: mag.0 mag: mag.0
}, },
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop { ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
character_id: character_id.0, character_id: character_id.0,
}, },
ItemNote::SoldToShop{character_id} => PgItemNoteDetail::SoldToShop { ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop,
character_id: character_id.0,
},
ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade { ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade {
trade_id: trade_id.0, trade_id: trade_id.0,
character_to: character_to.0, character_to: character_to.0,
@ -708,16 +677,8 @@ impl From<PgItemNoteDetail> for ItemNote {
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation { PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
}, },
PgItemNoteDetail::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => ItemNote::EnemyDrop { PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
room_id: RoomEntityId(room_id),
monster_type,
map_area,
x,y,z,
},
PgItemNoteDetail::BoxDrop{character_id, room_id, map_area, x, y, z} => ItemNote::BoxDrop {
character_id: CharacterEntityId(character_id),
room_id: RoomEntityId(room_id),
map_area, map_area,
x,y,z, x,y,z,
}, },
@ -729,19 +690,14 @@ impl From<PgItemNoteDetail> for ItemNote {
map_area, map_area,
x,y,z, x,y,z,
}, },
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed { PgItemNoteDetail::Consumed => ItemNote::Consumed,
character_id: CharacterEntityId(character_id), PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
},
PgItemNoteDetail::FedToMag{character_id, mag} => ItemNote::FedToMag{
character_id: CharacterEntityId(character_id),
mag: ItemEntityId(mag) mag: ItemEntityId(mag)
}, },
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop { PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
character_id: CharacterEntityId(character_id), character_id: CharacterEntityId(character_id),
}, },
PgItemNoteDetail::SoldToShop{character_id} => ItemNote::SoldToShop { PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop,
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade { PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade {
trade_id: TradeId(trade_id), trade_id: TradeId(trade_id),
character_to: CharacterEntityId(character_to), character_to: CharacterEntityId(character_to),
@ -924,27 +880,3 @@ impl From<PgTradeEntity> for TradeEntity {
} }
} }
} }
#[derive(Debug, sqlx::FromRow, Serialize)]
pub struct PgRoomEntity {
id: i32,
name: String,
section_id: i8,
mode: i8,
episode: i8,
difficulty: i8,
}
impl From<PgRoomEntity> for RoomEntity {
fn from(other: PgRoomEntity) -> RoomEntity {
RoomEntity {
id: RoomEntityId(other.id as u32),
name: other.name,
section_id: SectionID::from(other.section_id as u8),
mode: RoomEntityMode::from(other.mode as u8),
episode: Episode::try_from(other.episode as u8).unwrap(),
difficulty: Difficulty::try_from(other.difficulty as u8).unwrap(),
}
}
}

View File

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

View File

@ -10,9 +10,7 @@ pub mod esweapon;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::entity::character::CharacterEntityId; use crate::entity::character::CharacterEntityId;
use crate::entity::room::RoomEntityId;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::ship::monster::MonsterType;
use crate::ship::drops::ItemDropType; use crate::ship::drops::ItemDropType;
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)]
@ -37,16 +35,8 @@ pub enum ItemNote {
}, },
EnemyDrop { EnemyDrop {
character_id: CharacterEntityId, character_id: CharacterEntityId,
room_id: RoomEntityId, //monster_type: MonsterType,
monster_type: MonsterType, //droprate: f32,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
BoxDrop {
character_id: CharacterEntityId,
room_id: RoomEntityId,
map_area: MapArea, map_area: MapArea,
x: f32, x: f32,
y: f32, y: f32,
@ -62,19 +52,15 @@ pub enum ItemNote {
y: f32, y: f32,
z: f32, z: f32,
}, },
Consumed { Consumed, // TODO: character_id
character_id: CharacterEntityId,
},
FedToMag { FedToMag {
character_id: CharacterEntityId, //character_id: CharacterEntityId,
mag: ItemEntityId, mag: ItemEntityId,
}, },
BoughtAtShop { BoughtAtShop {
character_id: CharacterEntityId, character_id: CharacterEntityId,
}, },
SoldToShop { SoldToShop,
character_id: CharacterEntityId,
},
Trade { Trade {
trade_id: TradeId, trade_id: TradeId,
character_to: CharacterEntityId, character_to: CharacterEntityId,

View File

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

View File

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

View File

@ -9,23 +9,22 @@ use std::iter::IntoIterator;
use anyhow::Context; use anyhow::Context;
use libpso::packet::{ship::Message, messages::GameMessage}; use libpso::packet::{ship::Message, messages::GameMessage};
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::entity::item::{ItemDetail, NewItemEntity, TradeId, ItemModifier};
use crate::entity::item::tool::Tool;
use crate::entity::room::RoomEntityId;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::ship::ship::SendShipPacket; use crate::ship::ship::SendShipPacket;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::ship::items::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail}; use crate::ship::items::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail};
use crate::ship::items::bank::{BankItem, BankItemDetail}; use crate::ship::items::bank::{BankItem, BankItemDetail};
use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail};
use crate::ship::items::floor::{FloorItem, FloorItemDetail}; use crate::ship::items::floor::{FloorItem, FloorItemDetail};
use crate::ship::items::apply_item::{apply_item, ApplyItemAction}; use crate::ship::items::apply_item::{apply_item, ApplyItemAction};
use crate::entity::item::{ItemDetail, NewItemEntity, TradeId};
use crate::entity::item::tool::Tool;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem; use crate::ship::shops::ShopItem;
use crate::ship::drops::{ItemDrop, ItemDropType}; use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::packet::builder; use crate::ship::packet::builder;
use crate::ship::location::AreaClient; use crate::ship::location::AreaClient;
use crate::ship::monster::MonsterType;
pub enum TriggerCreateItem { pub enum TriggerCreateItem {
Yes, Yes,
@ -514,9 +513,7 @@ where
Box::pin(async move { Box::pin(async move {
let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move { async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed { transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed).await?;
character_id: character.id,
}).await?;
Ok(transaction) Ok(transaction)
}}).await?; }}).await?;
@ -551,7 +548,7 @@ where
let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| { let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| {
async move { async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag { transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag {
character_id: character.id, //character_id: character.id,
mag: mag_entity_id, mag: mag_entity_id,
}).await?; }).await?;
transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?; transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?;
@ -663,9 +660,7 @@ where
let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| {
async move { async move {
transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop { transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop).await?;
character_id,
}).await?;
Ok(transaction) Ok(transaction)
}}).await?; }}).await?;
transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?;
@ -909,6 +904,7 @@ where
pub(super) fn convert_item_drop_to_floor_item<'a, EG, TR>( pub(super) fn convert_item_drop_to_floor_item<'a, EG, TR>(
character_id: CharacterEntityId,
item_drop: ItemDrop, item_drop: ItemDrop,
) -> impl Fn((ItemStateProxy, TR), ()) ) -> impl Fn((ItemStateProxy, TR), ())
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone -> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
@ -950,6 +946,13 @@ where
let entity = transaction.gateway().create_item(NewItemEntity { let entity = transaction.gateway().create_item(NewItemEntity {
item: item_detail.clone(), item: item_detail.clone(),
}).await?; }).await?;
transaction.gateway().add_item_note(&entity.id, ItemNote::EnemyDrop {
character_id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}).await?;
FloorItem { FloorItem {
item_id, item_id,
item: FloorItemDetail::Individual(IndividualItemDetail { item: FloorItemDetail::Individual(IndividualItemDetail {
@ -966,6 +969,13 @@ where
let entity = transaction.gateway().create_item(NewItemEntity { let entity = transaction.gateway().create_item(NewItemEntity {
item: ItemDetail::Tool(tool), item: ItemDetail::Tool(tool),
}).await?; }).await?;
transaction.gateway().add_item_note(&entity.id, ItemNote::EnemyDrop {
character_id,
map_area: item_drop.map_area,
x: item_drop.x,
y: item_drop.y,
z: item_drop.z,
}).await?;
FloorItem { FloorItem {
item_id, item_id,
item: FloorItemDetail::Stacked(StackedItemDetail{ item: FloorItemDetail::Stacked(StackedItemDetail{
@ -995,88 +1005,6 @@ where
} }
} }
pub(super) fn item_note_enemy_drop<'a, EG, TR>(
character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, mut transaction), floor_item| {
Box::pin(async move {
match &floor_item.item {
FloorItemDetail::Individual(individual) => {
transaction.gateway().add_item_note(&individual.entity_id, ItemNote::EnemyDrop {
character_id,
room_id,
monster_type,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
FloorItemDetail::Stacked(stacked) => {
transaction.gateway().add_item_note(&stacked.entity_ids[0], ItemNote::EnemyDrop {
character_id,
room_id,
monster_type,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
_ => {},
}
Ok(((item_state, transaction), floor_item))
})
}
}
pub(super) fn item_note_box_drop<'a, EG, TR>(
character_id: CharacterEntityId,
room_id: RoomEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem)
-> BoxFuture<'a, Result<((ItemStateProxy, TR), FloorItem), anyhow::Error>> + Clone
where
EG: EntityGateway,
TR: EntityGatewayTransaction<ParentGateway = EG> + 'a,
{
move |(item_state, mut transaction), floor_item| {
Box::pin(async move {
match &floor_item.item {
FloorItemDetail::Individual(individual) => {
transaction.gateway().add_item_note(&individual.entity_id, ItemNote::BoxDrop {
character_id,
room_id,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
FloorItemDetail::Stacked(stacked) => {
transaction.gateway().add_item_note(&stacked.entity_ids[0], ItemNote::BoxDrop {
character_id,
room_id,
map_area: floor_item.map_area,
x: floor_item.x,
y: floor_item.y,
z: floor_item.z,
}).await?;
},
_ => {},
}
Ok(((item_state, transaction), floor_item))
})
}
}
pub(super) fn add_item_to_local_floor<'a, EG, TR>( pub(super) fn add_item_to_local_floor<'a, EG, TR>(
character_id: CharacterEntityId, character_id: CharacterEntityId,
) -> impl Fn((ItemStateProxy, TR), FloorItem) ) -> impl Fn((ItemStateProxy, TR), FloorItem)
@ -1241,4 +1169,4 @@ where
Ok(((item_state, transaction), ())) Ok(((item_state, transaction), ()))
}) })
} }
} }

View File

@ -6,17 +6,15 @@ use crate::ship::ship::SendShipPacket;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction}; use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::entity::item::ItemModifier;
use crate::entity::room::RoomEntityId;
use crate::ship::items::state::{ItemState, ItemStateProxy, IndividualItemDetail}; use crate::ship::items::state::{ItemState, ItemStateProxy, IndividualItemDetail};
use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction}; use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction};
use crate::ship::items::inventory::InventoryItem; use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::floor::FloorItem; use crate::ship::items::floor::FloorItem;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem; use crate::ship::shops::ShopItem;
use crate::ship::trade::TradeItem; use crate::ship::trade::TradeItem;
use crate::ship::location::AreaClient; use crate::ship::location::AreaClient;
use crate::ship::drops::ItemDrop; use crate::ship::drops::ItemDrop;
use crate::ship::monster::MonsterType;
use crate::ship::items::actions; use crate::ship::items::actions;
@ -467,8 +465,6 @@ pub fn enemy_drops_item<'a, EG> (
item_state: &'a mut ItemState, item_state: &'a mut ItemState,
entity_gateway: &'a mut EG, entity_gateway: &'a mut EG,
character_id: CharacterEntityId, character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
item_drop: ItemDrop) item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>> -> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where where
@ -477,32 +473,7 @@ where
entity_gateway.with_transaction(move |transaction| async move { entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone()); let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default() let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default()
.act(actions::convert_item_drop_to_floor_item(item_drop)) .act(actions::convert_item_drop_to_floor_item(character_id, item_drop))
.act(actions::item_note_enemy_drop(character_id, room_id, monster_type))
.act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, floor_item))
})
}
pub fn box_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
EG: EntityGateway + 'static,
{
entity_gateway.with_transaction(move |transaction| async move {
let item_state_proxy = ItemStateProxy::new(item_state.clone());
let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default()
.act(actions::convert_item_drop_to_floor_item(item_drop))
.act(actions::item_note_box_drop(character_id, room_id))
.act(actions::add_item_to_local_floor(character_id)) .act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction)) .commit((item_state_proxy, transaction))
.await?; .await?;

View File

@ -18,7 +18,7 @@ use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem};
use crate::ship::items::state::{ItemState, ItemStateError}; use crate::ship::items::state::{ItemState, ItemStateError};
use crate::ship::items::floor::{FloorType, FloorItemDetail}; use crate::ship::items::floor::{FloorType, FloorItemDetail};
use crate::ship::items::actions::TriggerCreateItem; use crate::ship::items::actions::TriggerCreateItem;
use crate::ship::items::tasks::{pick_up_item, withdraw_meseta, deposit_meseta, withdraw_item, deposit_item, buy_shop_item, enemy_drops_item, box_drops_item, take_meseta, apply_modifier}; use crate::ship::items::tasks::{pick_up_item, withdraw_meseta, deposit_meseta, withdraw_item, deposit_item, buy_shop_item, enemy_drops_item, take_meseta, apply_modifier};
const BANK_ACTION_DEPOSIT: u8 = 0; const BANK_ACTION_DEPOSIT: u8 = 0;
const BANK_ACTION_WITHDRAW: u8 = 1; const BANK_ACTION_WITHDRAW: u8 = 1;
@ -89,8 +89,8 @@ where
EG: EntityGateway + 'static, EG: EntityGateway + 'static,
{ {
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
let (room_entity_id, monster) = rooms.with(room_id, |room| Box::pin(async move { let monster = rooms.with(room_id, |room| Box::pin(async move {
Ok::<_, anyhow::Error>((room.room_id, room.maps.enemy_by_id(request_item.enemy_id as usize)?)) room.maps.enemy_by_id(request_item.enemy_id as usize)
})).await??; })).await??;
if monster.dropped_item { if monster.dropped_item {
@ -121,7 +121,7 @@ where
client.character.id client.character.id
})).await?; })).await?;
let floor_item = enemy_drops_item(item_state, entity_gateway, character_id, room_entity_id, monster.monster, item_drop).await?; let floor_item = enemy_drops_item(item_state, entity_gateway, character_id, item_drop).await?;
let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &floor_item)?; let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &floor_item)?;
item_drop_packets.push((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg))))); item_drop_packets.push((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg)))));
@ -200,8 +200,8 @@ where
EG: EntityGateway + Clone + 'static EG: EntityGateway + Clone + 'static
{ {
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
let (room_entity_id, box_object) = rooms.with(room_id, |room| Box::pin(async move { let box_object = rooms.with(room_id, |room| Box::pin(async move {
Ok::<_, anyhow::Error>((room.room_id, room.maps.object_by_id(box_drop_request.object_id as usize)?)) room.maps.object_by_id(box_drop_request.object_id as usize)
})).await??; })).await??;
if box_object.dropped_item { if box_object.dropped_item {
@ -232,7 +232,7 @@ where
let character_id = clients.with(area_client.client, |client| Box::pin(async move { let character_id = clients.with(area_client.client, |client| Box::pin(async move {
client.character.id client.character.id
})).await?; })).await?;
let floor_item = box_drops_item(item_state, entity_gateway, character_id, room_entity_id, item_drop).await?; let floor_item = enemy_drops_item(item_state, entity_gateway, character_id, item_drop).await?;
//let floor_item = enemy_drops_item(item_state, &mut entity_gateway, client.character.id, item_drop).await?; //let floor_item = enemy_drops_item(item_state, &mut entity_gateway, client.character.id, item_drop).await?;
let item_drop_msg = builder::message::item_drop(box_drop_request.client, box_drop_request.target, &floor_item)?; let item_drop_msg = builder::message::item_drop(box_drop_request.client, box_drop_request.target, &floor_item)?;
item_drop_packets.push((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg))))) item_drop_packets.push((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg)))))

View File

@ -8,7 +8,6 @@ use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationEr
use crate::ship::packet; use crate::ship::packet;
use crate::ship::items::state::ItemState; use crate::ship::items::state::ItemState;
use crate::entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
use crate::entity::room::RoomNote;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use futures::future::join_all; use futures::future::join_all;
@ -90,25 +89,14 @@ where
} }
}, },
RoomLobby::Room(old_room) => { RoomLobby::Room(old_room) => {
let room_entity_id = rooms.with(old_room, |room| Box::pin(async {
room.room_id
})).await?;
if client_location.get_client_neighbors(id).await?.is_empty() { if client_location.get_client_neighbors(id).await?.is_empty() {
rooms.remove(old_room).await; rooms.remove(old_room).await;
} }
let character_id = clients.with(id, |client| Box::pin(async {
client.character.id
})).await?;
clients.with(id, |client| { clients.with(id, |client| {
let mut item_state = item_state.clone(); let mut item_state = item_state.clone();
let mut entity_gateway = entity_gateway.clone();
Box::pin(async move { Box::pin(async move {
item_state.remove_character_from_room(&client.character).await; item_state.remove_character_from_room(&client.character).await;
entity_gateway.add_room_note(room_entity_id, RoomNote::PlayerLeave { })}).await?;
character_id
}).await
})}).await??;
}, },
} }
let leave_lobby = packet::builder::lobby::remove_from_lobby(id, client_location).await?; let leave_lobby = packet::builder::lobby::remove_from_lobby(id, client_location).await?;

View File

@ -46,9 +46,8 @@ pub async fn send_quest_category_list(id: ClientId,
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
let rql = rql.clone(); let rql = rql.clone();
rooms.with_mut(room_id, |room| Box::pin(async move { rooms.with_mut(room_id, |room| Box::pin(async move {
//let qcl = quest::quest_category_list(&room.quests[rql.flag.clamp(0, (room.quests.len() - 1) as u32) as usize]); let qcl = quest::quest_category_list(&room.quests[rql.flag.clamp(0, (room.quests.len() - 1) as u32) as usize]);
room.quest_group = rql.flag.into(); room.set_quest_group(rql.flag as usize);
let qcl = quest::quest_category_list(room.quests());
Ok(vec![(id, SendShipPacket::QuestCategoryList(qcl))]) Ok(vec![(id, SendShipPacket::QuestCategoryList(qcl))])
})).await? })).await?
} }
@ -60,10 +59,10 @@ pub async fn select_quest_category(id: ClientId,
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> { -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
rooms.with(room_id, |room| Box::pin(async move { rooms.with(room_id, |room| Box::pin(async move {
let (_, category_quests) = room.quests() let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.iter()
.nth(menuselect.item as usize) .nth(menuselect.item as usize)
.ok_or_else(|| ShipError::InvalidQuestCategory(menuselect.item as u16))?; .ok_or_else(|| ShipError::InvalidQuestCategory(menuselect.item as u16))?;
let ql = quest::quest_list(menuselect.item, category_quests); let ql = quest::quest_list(menuselect.item, category_quests);
Ok(vec![(id, SendShipPacket::QuestOptionList(ql))]) Ok(vec![(id, SendShipPacket::QuestOptionList(ql))])
})).await? })).await?
@ -77,7 +76,7 @@ pub async fn quest_detail(id: ClientId,
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> { -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
let room_id = client_location.get_room(id).await?; let room_id = client_location.get_room(id).await?;
rooms.with(room_id, |room| Box::pin(async move { rooms.with(room_id, |room| Box::pin(async move {
let (_, category_quests) = room.quests().iter() let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(questdetailrequest.category as usize) .nth(questdetailrequest.category as usize)
.ok_or_else(|| ShipError::InvalidQuestCategory(questdetailrequest.category))?; .ok_or_else(|| ShipError::InvalidQuestCategory(questdetailrequest.category))?;
@ -106,7 +105,7 @@ pub async fn player_chose_quest(id: ClientId,
rooms.with_mut(room_id, |room| { rooms.with_mut(room_id, |room| {
let clients = clients.clone(); let clients = clients.clone();
Box::pin(async move { Box::pin(async move {
let quest = room.quests().iter() let quest = room.quests[room.quest_group.value()].iter()
.nth(questmenuselect.category as usize) .nth(questmenuselect.category as usize)
.ok_or_else(|| ShipError::InvalidQuestCategory(questmenuselect.category))? .ok_or_else(|| ShipError::InvalidQuestCategory(questmenuselect.category))?
.1 .1
@ -150,7 +149,7 @@ pub async fn quest_file_request(id: ClientId,
let quest_file_request = quest_file_request.clone(); let quest_file_request = quest_file_request.clone();
rooms.with(room_id, |room| Box::pin(async move { rooms.with(room_id, |room| Box::pin(async move {
let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?; let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?;
let (_, category_quests) = room.quests().iter() let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(category_id as usize) .nth(category_id as usize)
.ok_or_else(|| ShipError::InvalidQuestCategory(category_id))?; .ok_or_else(|| ShipError::InvalidQuestCategory(category_id))?;
@ -183,7 +182,7 @@ pub async fn quest_chunk_ack(id: ClientId,
let quest_chunk_ack = quest_chunk_ack.clone(); let quest_chunk_ack = quest_chunk_ack.clone();
rooms.with(room_id, |room| Box::pin(async move { rooms.with(room_id, |room| Box::pin(async move {
let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?; let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?;
let (_, category_quests) = room.quests().iter() let (_, category_quests) = room.quests[room.quest_group.value()].iter()
.nth(category_id as usize) .nth(category_id as usize)
.ok_or_else(|| ShipError::InvalidQuestCategory(category_id))?; .ok_or_else(|| ShipError::InvalidQuestCategory(category_id))?;

View File

@ -7,9 +7,7 @@ use libpso::packet::ship::*;
use libpso::packet::messages::*; use libpso::packet::messages::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::common::leveltable::LEVEL_TABLE; use crate::common::leveltable::LEVEL_TABLE;
use crate::entity::gateway::EntityGateway;
use crate::entity::character::SectionID; use crate::entity::character::SectionID;
use crate::entity::room::{NewRoomEntity, RoomEntityMode, RoomNote};
use crate::ship::drops::DropTable; use crate::ship::drops::DropTable;
use crate::ship::ship::{SendShipPacket, Clients, ShipEvent}; use crate::ship::ship::{SendShipPacket, Clients, ShipEvent};
use crate::ship::room::{Rooms, Episode, Difficulty, RoomState, RoomMode}; use crate::ship::room::{Rooms, Episode, Difficulty, RoomState, RoomMode};
@ -19,25 +17,20 @@ use crate::ship::packet::builder;
use crate::ship::items::state::ItemState; use crate::ship::items::state::ItemState;
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn create_room<EG>(id: ClientId, pub async fn create_room(id: ClientId,
create_room: CreateRoom, create_room: CreateRoom,
entity_gateway: &mut EG, client_location: &mut ClientLocation,
client_location: &mut ClientLocation, clients: &Clients,
clients: &Clients, item_state: &mut ItemState,
item_state: &mut ItemState, rooms: &Rooms,
rooms: &Rooms, map_builder: Arc<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>,
map_builder: Arc<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>, drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>, event: ShipEvent)
event: ShipEvent) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
let level = clients.with(id, |client| Box::pin(async move { let level = clients.with(id, |client| Box::pin(async move {
LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp)
})).await?; })).await?;
let difficulty = Difficulty::try_from(create_room.difficulty)?; match Difficulty::try_from(create_room.difficulty)? {
match difficulty {
Difficulty::Ultimate if level < 80 => { Difficulty::Ultimate if level < 80 => {
return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto create Ultimate rooms.".into())))]) return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("You must be at least level 80 \nto create Ultimate rooms.".into())))])
}, },
@ -51,42 +44,15 @@ where
}; };
let area = client_location.get_area(id).await?; let area = client_location.get_area(id).await?;
let old_area_client = client_location.get_local_client(id).await?; let area_client = client_location.get_local_client(id).await?;
let lobby_neighbors = client_location.get_client_neighbors(id).await?; let lobby_neighbors = client_location.get_client_neighbors(id).await?;
let room_id = client_location.create_new_room(id).await?; let room_id = client_location.create_new_room(id).await?;
let new_area_client = client_location.get_local_client(id).await?;
let name = String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).to_string();
let mode = match (create_room.battle, create_room.challenge, create_room.single_player) {
(1, 0, 0) => RoomEntityMode::Battle,
(0, 1, 0) => RoomEntityMode::Challenge,
(0, 0, 1) => RoomEntityMode::Single,
_ => RoomEntityMode::Multi,
};
let episode = create_room.episode.try_into()?;
let difficulty = create_room.difficulty.try_into()?;
let room = clients.with(id, |client| { let room = clients.with(id, |client| {
let mut item_state = item_state.clone(); let mut item_state = item_state.clone();
let mut entity_gateway = entity_gateway.clone();
Box::pin(async move { Box::pin(async move {
item_state.add_character_to_room(room_id, &client.character, new_area_client).await; item_state.add_character_to_room(room_id, &client.character, area_client).await;
let room_entity = entity_gateway.create_room(NewRoomEntity { let mut room = RoomState::from_create_room(&create_room, map_builder, drop_table_builder, client.character.section_id, event)?;
name: name.clone(),
section_id: client.character.section_id,
mode,
episode,
difficulty,
}).await?;
entity_gateway.add_room_note(room_entity.id, RoomNote::Create {
character_id: client.character.id,
}).await?;
let mut room = RoomState::new(room_entity.id, mode, episode, difficulty,
client.character.section_id, name, create_room.password, event,
map_builder, drop_table_builder)?;
room.bursting = true; room.bursting = true;
Ok::<_, anyhow::Error>(room) Ok::<_, anyhow::Error>(room)
})}).await??; })}).await??;
@ -96,7 +62,7 @@ where
let mut result = vec![(id, SendShipPacket::JoinRoom(join_room))]; let mut result = vec![(id, SendShipPacket::JoinRoom(join_room))];
if let Ok(leader) = client_location.get_area_leader(area).await { if let Ok(leader) = client_location.get_area_leader(area).await {
let leave_lobby = SendShipPacket::LeaveLobby(LeaveLobby::new(old_area_client.local_client.id(), leader.local_client.id())); let leave_lobby = SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id()));
result.extend(lobby_neighbors result.extend(lobby_neighbors
.into_iter() .into_iter()
.map(move |c| { .map(move |c| {
@ -124,19 +90,14 @@ pub async fn room_name_request(id: ClientId,
} }
} }
#[allow(clippy::too_many_arguments)] pub async fn join_room(id: ClientId,
pub async fn join_room<EG>(id: ClientId, pkt: MenuSelect,
pkt: MenuSelect, client_location: &mut ClientLocation,
entity_gateway: &mut EG, clients: &Clients,
client_location: &mut ClientLocation, item_state: &mut ItemState,
clients: &Clients, rooms: &Rooms,
item_state: &mut ItemState, event: ShipEvent)
rooms: &Rooms, -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
event: ShipEvent)
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
where
EG: EntityGateway + Clone + 'static,
{
let room_id = RoomId(pkt.item as usize); let room_id = RoomId(pkt.item as usize);
if !rooms.exists(room_id).await { if !rooms.exists(room_id).await {
return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("This room no longer exists!".into())))]) return Ok(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("This room no longer exists!".into())))])
@ -144,8 +105,8 @@ where
let level = clients.with(id, |client| Box::pin(async move { let level = clients.with(id, |client| Box::pin(async move {
LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp)
})).await?; })).await?;
let (difficulty, bursting, room_entity_id) = rooms.with(room_id, |room| Box::pin(async move { let (difficulty, bursting) = rooms.with(room_id, |room| Box::pin(async move {
(room.mode.difficulty(), room.bursting, room.room_id) (room.mode.difficulty(), room.bursting)
})).await?; })).await?;
match difficulty { match difficulty {
@ -174,14 +135,9 @@ where
clients.with(id, |client| { clients.with(id, |client| {
let mut item_state = item_state.clone(); let mut item_state = item_state.clone();
let mut entity_gateway = entity_gateway.clone();
Box::pin(async move { Box::pin(async move {
entity_gateway.add_room_note(room_entity_id, RoomNote::PlayerJoin {
character_id: client.character.id,
}).await?;
item_state.add_character_to_room(room_id, &client.character, area_client).await; item_state.add_character_to_room(room_id, &client.character, area_client).await;
Ok::<_, anyhow::Error>(()) })}).await?;
})}).await??;
let join_room = rooms.with(room_id, |room| { let join_room = rooms.with(room_id, |room| {
let clients = clients.clone(); let clients = clients.clone();
@ -198,7 +154,7 @@ where
rooms.with_mut(room_id, |room| Box::pin(async move { rooms.with_mut(room_id, |room| Box::pin(async move {
room.bursting = true; room.bursting = true;
})).await?; })).await?;
Ok(vec![(id, SendShipPacket::JoinRoom(join_room))] Ok(vec![(id, SendShipPacket::JoinRoom(join_room))]
.into_iter() .into_iter()
.chain(original_room_clients.into_iter() .chain(original_room_clients.into_iter()

View File

@ -11,7 +11,7 @@ use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder};
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use libpso::util::array_to_utf16; use libpso::util::array_to_utf16;
use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream}; use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream};
use crate::ship::room::{Episode, RoomMode}; use crate::ship::room::Episode;
use crate::ship::map::area::{MapAreaLookup, MapAreaLookupBuilder}; use crate::ship::map::area::{MapAreaLookup, MapAreaLookupBuilder};
@ -152,14 +152,11 @@ fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("")]
pub enum QuestLoadError { pub enum QuestLoadError {
#[error("io error {0}")]
IoError(#[from] std::io::Error), IoError(#[from] std::io::Error),
#[error("parse dat error {0}")]
ParseDatError(#[from] ParseDatError), ParseDatError(#[from] ParseDatError),
#[error("could not read metadata")]
CouldNotReadMetadata, CouldNotReadMetadata,
#[error("could not load config file")]
CouldNotLoadConfigFile, CouldNotLoadConfigFile,
} }
@ -236,7 +233,7 @@ pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf, quest_path: PathBuf) ->
} }
pub fn load_quests_path(mut quest_path: PathBuf) -> Result<QuestList, QuestLoadError> { pub fn load_quests(mut quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
let mut s = String::new(); let mut s = String::new();
f.read_to_string(&mut s)?; f.read_to_string(&mut s)?;
@ -245,59 +242,29 @@ pub fn load_quests_path(mut quest_path: PathBuf) -> Result<QuestList, QuestLoadE
let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
Ok(ql.into_iter().map(|(category, category_details)| { Ok(ql.into_iter().map(|(category, category_details)| {
( let quests = category_details.quests
QuestCategory { .into_iter()
index: category_details.list_order, .filter_map(|quest| {
name: category, load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf())
description: category_details.description, .and_then(|quest | {
}, if used_quest_ids.contains(&quest.id) {
category_details.quests warn!("quest id already exists: {}", quest.id);
.into_iter() return None;
.filter_map(|quest| { }
load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf()) used_quest_ids.insert(quest.id);
.and_then(|quest | { Some(quest)
if used_quest_ids.contains(&quest.id) { })
warn!("quest id already exists: {}", quest.id); });
return None; (QuestCategory{
} index: category_details.list_order,
used_quest_ids.insert(quest.id); name: category,
Some(quest) description: category_details.description,
}) }, quests.collect())
})
.collect()
)
}).collect()) }).collect())
} }
pub fn load_standard_quests(mode: RoomMode) -> Result<QuestList, QuestLoadError> {
match mode {
RoomMode::Single {episode, .. } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "single", "quests.toml"]))
},
RoomMode::Multi {episode, .. } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "multi", "quests.toml"]))
},
_ => {
Ok(BTreeMap::new())
}
}
}
pub fn load_government_quests(mode: RoomMode) -> Result<QuestList, QuestLoadError> {
match mode {
RoomMode::Single {episode, .. } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
},
RoomMode::Multi {episode, .. } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
},
_ => {
Ok(BTreeMap::new())
}
}
}

View File

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::{From, Into, TryFrom}; use std::convert::{From, Into, TryFrom, TryInto};
use std::path::PathBuf;
use async_std::sync::{Arc, RwLock, RwLockReadGuard}; use async_std::sync::{Arc, RwLock, RwLockReadGuard};
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures::stream::{FuturesOrdered, Stream}; use futures::stream::{FuturesOrdered, Stream};
@ -10,7 +11,6 @@ use rand::Rng;
use crate::ship::map::Maps; use crate::ship::map::Maps;
use crate::ship::drops::DropTable; use crate::ship::drops::DropTable;
use crate::entity::character::SectionID; use crate::entity::character::SectionID;
use crate::entity::room::{RoomEntityId, RoomEntityMode};
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
use crate::ship::map::area::MapAreaLookup; use crate::ship::map::area::MapAreaLookup;
use crate::ship::quests; use crate::ship::quests;
@ -55,7 +55,7 @@ impl Rooms {
None => false, None => false,
} }
} }
pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error> pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result<T, anyhow::Error>
where where
T: Send, T: Send,
@ -92,7 +92,7 @@ impl Rooms {
Err(ShipError::InvalidRoom(room_id.0 as u32).into()) Err(ShipError::InvalidRoom(room_id.0 as u32).into())
} }
} }
pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard<Option<RoomState>> { pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard<Option<RoomState>> {
self.0 self.0
.get(room_id.0) .get(room_id.0)
@ -282,15 +282,8 @@ impl From<usize> for QuestCategoryType {
fn from(f: usize) -> QuestCategoryType { fn from(f: usize) -> QuestCategoryType {
match f { match f {
0 => QuestCategoryType::Standard, 0 => QuestCategoryType::Standard,
_ => QuestCategoryType::Government, 1 => QuestCategoryType::Government,
} _ => QuestCategoryType::Standard, // TODO: panic?
}
}
impl From<u32> for QuestCategoryType {
fn from(f: u32) -> QuestCategoryType {
match f {
0 => QuestCategoryType::Standard,
_ => QuestCategoryType::Government,
} }
} }
} }
@ -305,7 +298,6 @@ impl QuestCategoryType {
} }
pub struct RoomState { pub struct RoomState {
pub room_id: RoomEntityId,
pub mode: RoomMode, pub mode: RoomMode,
pub name: String, pub name: String,
pub password: [u16; 16], pub password: [u16; 16],
@ -317,22 +309,22 @@ pub struct RoomState {
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>, pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
pub map_areas: MapAreaLookup, pub map_areas: MapAreaLookup,
pub quest_group: QuestCategoryType, pub quest_group: QuestCategoryType,
pub standard_quests: quests::QuestList, pub quests: Vec<quests::QuestList>,
pub government_quests: quests::QuestList, // items on ground
// enemy info // enemy info
} }
impl RoomState { impl RoomState {
pub fn get_flags_for_room_list(&self) -> u8 { pub fn get_flags_for_room_list(&self) -> u8 {
let mut flags = 0u8; let mut flags = 0u8;
match self.mode { match self.mode {
RoomMode::Single {..} => {flags += 0x04} RoomMode::Single {..} => {flags += 0x04}
RoomMode::Battle {..} => {flags += 0x10}, RoomMode::Battle {..} => {flags += 0x10},
RoomMode::Challenge {..} => {flags += 0x20}, RoomMode::Challenge {..} => {flags += 0x20},
_ => {flags += 0x40}, _ => {flags += 0x40},
}; };
if self.password[0] > 0 { if self.password[0] > 0 {
flags += 0x02; flags += 0x02;
} }
@ -353,59 +345,85 @@ impl RoomState {
difficulty + 0x22 difficulty + 0x22
} }
pub fn quests(&self) -> &quests::QuestList { pub fn set_quest_group(&mut self, group: usize) {
match self.quest_group { self.quest_group = QuestCategoryType::from(group);
QuestCategoryType::Standard => &self.standard_quests,
QuestCategoryType::Government => &self.government_quests,
}
} }
#[allow(clippy::too_many_arguments)] pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom,
pub fn new (room_id: RoomEntityId, map_builder: Arc<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>,
mode: RoomEntityMode, drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>,
episode: Episode, section_id: SectionID,
difficulty: Difficulty, event: ShipEvent)
section_id: SectionID, -> Result<RoomState, RoomCreationError> {
name: String, if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::<u8>() > 1 {
password: [u16; 16], return Err(RoomCreationError::InvalidMode)
event: ShipEvent, }
map_builder: Arc<Box<dyn Fn(RoomMode, ShipEvent) -> Maps + Send + Sync>>,
drop_table_builder: Arc<Box<dyn Fn(Episode, Difficulty, SectionID) -> DropTable + Send + Sync>>, let room_mode = if create_room.battle == 1 {
) -> Result<RoomState, anyhow::Error> { RoomMode::Battle {
let mode = match mode { episode: create_room.episode.try_into()?,
RoomEntityMode::Single => RoomMode::Single { difficulty: create_room.difficulty.try_into()?,
episode, }
difficulty, }
}, else if create_room.challenge == 1 {
RoomEntityMode::Multi => RoomMode::Multi { RoomMode::Challenge {
episode, episode: create_room.episode.try_into()?,
difficulty, }
}, }
RoomEntityMode::Challenge => RoomMode::Challenge { else if create_room.single_player == 1 {
episode, RoomMode::Single {
}, episode: create_room.episode.try_into()?,
RoomEntityMode::Battle => RoomMode::Battle { difficulty: create_room.difficulty.try_into()?,
episode, }
difficulty, }
}, else { // normal multimode
RoomMode::Multi {
episode: create_room.episode.try_into()?,
difficulty: create_room.difficulty.try_into()?,
}
}; };
Ok(RoomState {
room_id,
monster_stats: Box::new(load_monster_stats_table(&mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(mode))?),
mode,
random_seed: rand::thread_rng().gen(),
name,
password,
maps: map_builder(mode, event),
section_id,
drop_table: Box::new(drop_table_builder(episode, difficulty, section_id)),
bursting: false,
map_areas: MapAreaLookup::new(&episode),
quest_group: QuestCategoryType::Standard,
standard_quests: quests::load_standard_quests(mode)?,
government_quests: quests::load_government_quests(mode)?,
})
// push the usual set of quests for the selected mode
let mut qpath = PathBuf::from("data/quests/bb");
qpath.push(room_mode.episode().to_string());
qpath.push(room_mode.to_string());
qpath.push("quests.toml");
let mut room_quests = Vec::new();
let quest_list = match quests::load_quests(qpath) {
Ok(qlist) => qlist,
Err(_) => return Err(RoomCreationError::CouldNotLoadQuests),
};
room_quests.push(quest_list);
// if multiplayer also push the government quests
if let RoomMode::Multi {..} = room_mode {
qpath = PathBuf::from("data/quests/bb/");
qpath.push(room_mode.episode().to_string());
qpath.push("government/quests.toml");
let quest_list = match quests::load_quests(qpath) {
Ok(qlist) => qlist,
Err(_) => return Err(RoomCreationError::CouldNotLoadQuests),
};
room_quests.push(quest_list);
}
Ok(RoomState {
monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?),
mode: room_mode,
random_seed: rand::thread_rng().gen(),
name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(),
password: create_room.password,
maps: map_builder(room_mode, event),
section_id,
drop_table: Box::new(drop_table_builder(room_mode.episode(), room_mode.difficulty(), section_id)),
bursting: false,
map_areas: MapAreaLookup::new(&room_mode.episode()),
quest_group: QuestCategoryType::Standard,
quests: room_quests,
})
} }
} }

View File

@ -20,7 +20,6 @@ use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, Lo
use crate::login::character::SHIP_MENU_ID; use crate::login::character::SHIP_MENU_ID;
use crate::entity::gateway::{EntityGateway, GatewayError}; use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::SectionID; use crate::entity::character::SectionID;
use crate::entity::room::RoomNote;
use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId}; use crate::ship::location::{ClientLocation, RoomLobby, ClientLocationError, RoomId};
use crate::ship::drops::DropTable; use crate::ship::drops::DropTable;
use crate::ship::items; use crate::ship::items;
@ -699,7 +698,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter(); let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter();
leave_lobby.chain(select_block).collect() leave_lobby.chain(select_block).collect()
} }
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?, ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?,
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &block.rooms).await?, QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &block.rooms).await?,
_ => unreachable!(), _ => unreachable!(),
} }
@ -724,7 +723,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
menu: room_password_req.menu, menu: room_password_req.menu,
item: room_password_req.item, item: room_password_req.item,
}; };
handler::room::join_room(id, menuselect, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await? handler::room::join_room(id, menuselect, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?
} }
else { else {
vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))] vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))]
@ -756,8 +755,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
}, },
RecvShipPacket::CreateRoom(create_room) => { RecvShipPacket::CreateRoom(create_room) => {
let block = self.blocks.get_from_client(id, &self.clients).await?; let block = self.blocks.get_from_client(id, &self.clients).await?;
handler::room::create_room(id, create_room, &mut self.entity_gateway, &mut block.client_location, &self.clients, &mut self.item_state, handler::room::create_room(id, create_room, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.map_builder.clone(), self.drop_table_builder.clone(), self.event).await?
&block.rooms, self.map_builder.clone(), self.drop_table_builder.clone(), self.event).await?
}, },
RecvShipPacket::RoomNameRequest(_req) => { RecvShipPacket::RoomNameRequest(_req) => {
let block = self.blocks.get_from_client(id, &self.clients).await?; let block = self.blocks.get_from_client(id, &self.clients).await?;
@ -852,16 +850,6 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
let pkt = match block.client_location.get_area(id).await? { let pkt = match block.client_location.get_area(id).await? {
RoomLobby::Room(room) => { RoomLobby::Room(room) => {
let character_id = self.clients.with(id, |client| Box::pin(async {
client.character.id
})).await?;
block.rooms.with(room, |room| {
let mut entity_gateway = self.entity_gateway.clone();
Box::pin(async move {
entity_gateway.add_room_note(room.room_id, RoomNote::PlayerJoin {
character_id,
}).await
})}).await;
if neighbors.is_empty() { if neighbors.is_empty() {
block.rooms.remove(room).await; block.rooms.remove(room).await;
} }