Merge pull request 'room notes, etc' (#139) from more_itemnotes into master
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #139
This commit is contained in:
commit
1f7dd1eafe
2388
Cargo.lock
generated
2388
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"] }
|
||||
|
@ -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!();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
Normal file
14
src/entity/gateway/postgres/migrations/V0012__room.sql
Normal file
@ -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
Normal file
17
src/entity/gateway/postgres/migrations/V0013__room2.sql
Normal file
@ -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
|
||||
);
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -2,3 +2,4 @@ pub mod gateway;
|
||||
pub mod account;
|
||||
pub mod character;
|
||||
pub mod item;
|
||||
pub mod room;
|
||||
|
83
src/entity/room.rs
Normal file
83
src/entity/room.rs
Normal file
@ -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
|
||||
},
|
||||
|
||||
}
|
@ -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), ()))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,8 @@ 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
|
||||
@ -473,7 +477,32 @@ 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_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))
|
||||
.commit((item_state_proxy, transaction))
|
||||
.await?;
|
||||
|
@ -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)))))
|
||||
|
@ -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?;
|
||||
|
@ -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))?;
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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,29 +245,59 @@ 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
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 quests(&self) -> &quests::QuestList {
|
||||
match self.quest_group {
|
||||
QuestCategoryType::Standard => &self.standard_quests,
|
||||
QuestCategoryType::Government => &self.government_quests,
|
||||
}
|
||||
}
|
||||
|
||||
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()?,
|
||||
}
|
||||
}
|
||||
else { // normal multimode
|
||||
RoomMode::Multi {
|
||||
episode: create_room.episode.try_into()?,
|
||||
difficulty: create_room.difficulty.try_into()?,
|
||||
}
|
||||
#[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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// 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,
|
||||
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)?,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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…
x
Reference in New Issue
Block a user