Browse Source

Merge pull request 'room notes, etc' (#139) from more_itemnotes into master

Reviewed-on: #139
fuckin_andy_making_me_do_this_now
jake 1 year ago
parent
commit
1f7dd1eafe
  1. 2388
      Cargo.lock
  2. 2
      Cargo.toml
  3. 10
      src/entity/gateway/entitygateway.rs
  4. 18
      src/entity/gateway/inmemory.rs
  5. 14
      src/entity/gateway/postgres/migrations/V0012__room.sql
  6. 17
      src/entity/gateway/postgres/migrations/V0013__room2.sql
  7. 88
      src/entity/gateway/postgres/models.rs
  8. 105
      src/entity/gateway/postgres/postgres.rs
  9. 24
      src/entity/item/mod.rs
  10. 1
      src/entity/mod.rs
  11. 83
      src/entity/room.rs
  12. 120
      src/ship/items/actions.rs
  13. 33
      src/ship/items/tasks.rs
  14. 14
      src/ship/packet/handler/direct_message.rs
  15. 14
      src/ship/packet/handler/lobby.rs
  16. 17
      src/ship/packet/handler/quest.rs
  17. 98
      src/ship/packet/handler/room.rs
  18. 75
      src/ship/quests.rs
  19. 142
      src/ship/room.rs
  20. 18
      src/ship/ship.rs

2388
Cargo.lock
File diff suppressed because it is too large
View File

2
Cargo.toml

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

10
src/entity/gateway/entitygateway.rs

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

18
src/entity/gateway/inmemory.rs

@ -6,6 +6,7 @@ use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::entity::room::*;
use async_std::sync::{Arc, Mutex};
@ -766,4 +767,21 @@ impl EntityGateway for InMemoryGateway {
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(())
}
}

14
src/entity/gateway/postgres/migrations/V0012__room.sql

@ -0,0 +1,14 @@
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
);

17
src/entity/gateway/postgres/migrations/V0013__room2.sql

@ -0,0 +1,17 @@
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
);

88
src/entity/gateway/postgres/models.rs

@ -7,7 +7,10 @@ use libpso::util::vec_to_array;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::item::*;
use crate::entity::room::*;
use crate::ship::map::MapArea;
use crate::ship::room::{Episode, Difficulty};
use crate::ship::monster::MonsterType;
#[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount {
@ -577,6 +580,16 @@ pub enum PgItemNoteDetail {
},
EnemyDrop {
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,
x: f32,
y: f32,
@ -592,14 +605,19 @@ pub enum PgItemNoteDetail {
y: f32,
z: f32,
},
Consumed,
Consumed {
character_id: u32,
},
FedToMag {
character_id: u32,
mag: u32,
},
BoughtAtShop {
character_id: u32,
},
SoldToShop,
SoldToShop {
character_id: u32,
},
Trade {
trade_id: u32,
character_to: u32,
@ -624,8 +642,16 @@ impl From<ItemNote> for PgItemNoteDetail {
ItemNote::CharacterCreation{character_id} => PgItemNoteDetail::CharacterCreation {
character_id: character_id.0,
},
ItemNote::EnemyDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
ItemNote::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => PgItemNoteDetail::EnemyDrop {
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,
x,y,z,
},
@ -637,14 +663,19 @@ impl From<ItemNote> for PgItemNoteDetail {
map_area,
x,y,z,
},
ItemNote::Consumed => PgItemNoteDetail::Consumed,
ItemNote::FedToMag{mag} => PgItemNoteDetail::FedToMag{
ItemNote::Consumed{character_id} => PgItemNoteDetail::Consumed {
character_id: character_id.0,
},
ItemNote::FedToMag{character_id, mag} => PgItemNoteDetail::FedToMag{
character_id: character_id.0,
mag: mag.0
},
ItemNote::BoughtAtShop{character_id} => PgItemNoteDetail::BoughtAtShop {
character_id: character_id.0,
},
ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop,
ItemNote::SoldToShop{character_id} => PgItemNoteDetail::SoldToShop {
character_id: character_id.0,
},
ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade {
trade_id: trade_id.0,
character_to: character_to.0,
@ -677,8 +708,16 @@ impl From<PgItemNoteDetail> for ItemNote {
PgItemNoteDetail::CharacterCreation{character_id} => ItemNote::CharacterCreation {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::EnemyDrop{character_id, map_area, x, y, z} => ItemNote::EnemyDrop {
PgItemNoteDetail::EnemyDrop{character_id, room_id, monster_type, map_area, x, y, z} => ItemNote::EnemyDrop {
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,
x,y,z,
},
@ -690,14 +729,19 @@ impl From<PgItemNoteDetail> for ItemNote {
map_area,
x,y,z,
},
PgItemNoteDetail::Consumed => ItemNote::Consumed,
PgItemNoteDetail::FedToMag{mag} => ItemNote::FedToMag{
PgItemNoteDetail::Consumed{character_id} => ItemNote::Consumed {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::FedToMag{character_id, mag} => ItemNote::FedToMag{
character_id: CharacterEntityId(character_id),
mag: ItemEntityId(mag)
},
PgItemNoteDetail::BoughtAtShop{character_id} => ItemNote::BoughtAtShop {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop,
PgItemNoteDetail::SoldToShop{character_id} => ItemNote::SoldToShop {
character_id: CharacterEntityId(character_id),
},
PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade {
trade_id: TradeId(trade_id),
character_to: CharacterEntityId(character_to),
@ -880,3 +924,27 @@ 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(),
}
}
}

105
src/entity/gateway/postgres/postgres.rs

@ -10,6 +10,7 @@ use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
use crate::entity::item::*;
use crate::entity::room::*;
use super::models::*;
use sqlx::postgres::PgPoolOptions;
@ -178,7 +179,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>
{
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1")
.bind(id.0)
.bind(id.0 as i32)
.fetch_one(conn).await?;
Ok(user.into())
}
@ -199,8 +200,8 @@ async fn save_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> R
.bind(&user.password)
.bind(user.banned_until)
.bind(user.muted_until)
.bind(user.flags)
.bind(user.id.0)
.bind(user.flags as i32)
.bind(user.id.0 as i32)
.execute(conn).await?;
Ok(())
}
@ -209,7 +210,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)
values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;")
.bind(settings.user_id.0)
.bind(settings.user_id.0 as i32)
.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.gamepad_config.to_vec())
@ -224,7 +225,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>
{
let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1")
.bind(user.id.0)
.bind(user.id.0 as i32)
.fetch_one(conn).await?;
Ok(settings.into())
}
@ -235,11 +236,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.keyboard_config.to_vec())
.bind(&settings.settings.gamepad_config.to_vec())
.bind(settings.settings.option_flags)
.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.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::<Vec<u8>>())
.bind(settings.id.0)
.bind(settings.id.0 as i32)
.execute(conn).await?;
Ok(())
}
@ -262,7 +263,7 @@ async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntit
$26, $27, $28, $29, $30)
returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q)
.bind(char.user_id.0)
.bind(char.user_id.0 as i32)
.bind(char.slot as i16)
.bind(char.name)
.bind(char.exp as i32)
@ -300,7 +301,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>
{
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)
.bind(user.id.0 as i32)
.fetch(conn);
Ok(stream.fold(core::array::from_fn(|_| None), |mut acc, char| async move {
@ -320,7 +321,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
where id=$31;"#;
sqlx::query(q)
.bind(char.user_id.0) // $1
.bind(char.user_id.0 as i32) // $1
.bind(char.slot as i16) // $2
.bind(&char.name) // $3
.bind(char.exp as i32) // $4
@ -370,7 +371,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>
{
sqlx::query("insert into item_note(item, note) values ($1, $2)")
.bind(item_id.0)
.bind(item_id.0 as i32)
.bind(sqlx::types::Json(PgItemNoteDetail::from(item_note)))
.execute(conn).await?;
Ok(())
@ -379,7 +380,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>
{
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(mag_item_id.0 as i32)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id})))
.execute(conn).await?;
Ok(())
@ -388,7 +389,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>
{
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(mag_item_id.0 as i32)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id))))
.execute(conn).await?;
Ok(())
@ -397,7 +398,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>
{
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(mag_item_id.0 as i32)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id))))
.execute(conn).await?;
Ok(())
@ -406,7 +407,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>
{
sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);")
.bind(item_id.0)
.bind(item_id.0 as i32)
.bind(sqlx::types::Json(modifier))
.execute(conn).await?;
Ok(())
@ -416,7 +417,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 inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(&mut **conn.lock().await).await?;
Ok(InventoryEntity::new(
@ -441,14 +442,14 @@ async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
let bank = match bank_identifier {
BankIdentifier::Character => {
sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(&mut **conn.lock().await).await?
},
BankIdentifier::Shared(bank_name) => {
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
where player_character.id = $1 and shared_bank.name = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(&bank_name.0)
.fetch_optional(&mut **conn.lock().await)
.await?
@ -491,7 +492,7 @@ async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &Charac
.collect::<Vec<_>>();
sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(inventory))
.execute(conn)
.await?;
@ -516,7 +517,7 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
match bank_identifier {
BankIdentifier::Character => {
sqlx::query("insert into bank (pchar, items, name) values ($1, $2, '') on conflict (pchar, name) do update set items = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(bank))
.execute(conn)
.await?;
@ -526,7 +527,7 @@ async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEn
select player_character.user_account, $2, $3 from player_character
where player_character.id = $1
on conflict (user_account, name) do update set items = $2;")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(sqlx::types::Json(bank))
.bind(&bank_name.0)
.execute(conn)
@ -539,7 +540,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>
{
let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1")
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn)
.await?;
@ -550,7 +551,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)
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)
.bind(char_id.0 as i32)
.bind(equips.weapon.map(|i| i.0 as i32))
.bind(equips.armor.map(|i| i.0 as i32))
.bind(equips.shield.map(|i| i.0 as i32))
@ -568,7 +569,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>
{
sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set meseta = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(meseta.0 as i32)
.execute(conn)
.await?;
@ -580,7 +581,7 @@ async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &Character
#[derive(sqlx::FromRow)]
struct PgMeseta(i32);
let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where pchar = $1"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn)
.await?;
Ok(Meseta(meseta.0 as u32))
@ -591,7 +592,7 @@ async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
match bank_identifier {
BankIdentifier::Character => {
sqlx::query("insert into bank_meseta values ($1, '', $2) on conflict (pchar, bank) do update set meseta = $2")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(meseta.0 as i32)
.execute(conn)
.await?;
@ -601,7 +602,7 @@ async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
select player_character.user_account, $2, $3 from player_character
where player_character.id = $1
on conflict (user_account, name) do update set meseta = $3")
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(&bank_name.0)
.bind(meseta.0 as i32)
.execute(conn)
@ -620,7 +621,7 @@ async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntit
let meseta = match bank_identifier {
BankIdentifier::Character => {
sqlx::query_as::<_, PgMeseta>(r#"select meseta from bank_meseta where pchar = $1"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.fetch_one(conn)
.await?
},
@ -628,7 +629,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
join player_character on shared_bank_meseta.user_account = player_character.user_account
where player_character.id = $1 and shared_bank_meseta.name = $2"#)
.bind(char_id.0)
.bind(char_id.0 as i32)
.bind(&bank_name.0)
.fetch_optional(conn)
.await?
@ -641,8 +642,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>
{
let trade = sqlx::query_as::<_, PgTradeEntity>(r#"insert into trades (character1, character2) values ($1, $2) returning *;"#)
.bind(char_id1.0)
.bind(char_id2.0)
.bind(char_id1.0 as i32)
.bind(char_id2.0 as i32)
.fetch_one(conn)
.await?;
Ok(trade.into())
@ -651,8 +652,30 @@ 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>
{
sqlx::query(r#"update player_character set playtime=$2 where id=$1;"#)
.bind(char_id.0)
.bind(playtime)
.bind(char_id.0 as i32)
.bind(playtime as i32)
.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)
.await?;
Ok(())
@ -797,6 +820,14 @@ impl EntityGateway for PostgresGateway {
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
}
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
}
}
@ -923,5 +954,13 @@ impl<'c> EntityGateway for PostgresTransaction<'c> {
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
}
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
}
}

24
src/entity/item/mod.rs

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

1
src/entity/mod.rs

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

83
src/entity/room.rs

@ -0,0 +1,83 @@
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
},
}

120
src/ship/items/actions.rs

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

33
src/ship/items/tasks.rs

@ -6,15 +6,17 @@ use crate::ship::ship::SendShipPacket;
use crate::ship::map::MapArea;
use crate::entity::character::{CharacterEntity, CharacterEntityId};
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction};
use crate::entity::item::ItemModifier;
use crate::entity::room::RoomEntityId;
use crate::ship::items::state::{ItemState, ItemStateProxy, IndividualItemDetail};
use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction};
use crate::ship::items::inventory::InventoryItem;
use crate::ship::items::floor::FloorItem;
use crate::entity::item::ItemModifier;
use crate::ship::shops::ShopItem;
use crate::ship::trade::TradeItem;
use crate::ship::location::AreaClient;
use crate::ship::drops::ItemDrop;
use crate::ship::monster::MonsterType;
use crate::ship::items::actions;
@ -465,6 +467,32 @@ pub fn enemy_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
monster_type: MonsterType,
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_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
@ -473,7 +501,8 @@ where
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(character_id, item_drop))
.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))
.commit((item_state_proxy, transaction))
.await?;

14
src/ship/packet/handler/direct_message.rs

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

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

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

17
src/ship/packet/handler/quest.rs

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

98
src/ship/packet/handler/room.rs

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

75
src/ship/quests.rs

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

142
src/ship/room.rs

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

18
src/ship/ship.rs

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

Loading…
Cancel
Save