Browse Source

minor room refactor, room db table, add room ids to itemnotes

pull/139/head
jake 1 year ago
parent
commit
318aeeba2b
  1. 10
      src/entity/gateway/entitygateway.rs
  2. 18
      src/entity/gateway/inmemory.rs
  3. 14
      src/entity/gateway/postgres/migrations/V0012__room.sql
  4. 88
      src/entity/gateway/postgres/models.rs
  5. 39
      src/entity/gateway/postgres/postgres.rs
  6. 24
      src/entity/item/mod.rs
  7. 28
      src/entity/room.rs
  8. 115
      src/ship/items/actions.rs
  9. 31
      src/ship/items/tasks.rs
  10. 14
      src/ship/packet/handler/direct_message.rs
  11. 19
      src/ship/packet/handler/quest.rs
  12. 54
      src/ship/packet/handler/room.rs
  13. 75
      src/ship/quests.rs
  14. 138
      src/ship/room.rs
  15. 3
      src/ship/ship.rs

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

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

39
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;
@ -658,6 +659,28 @@ async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &Charact
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)
.bind(sqlx::types::Json(note))
.execute(conn)
.await?;
Ok(())
}
#[async_trait::async_trait]
impl EntityGateway for PostgresGateway {
type Transaction<'t> = PostgresTransaction<'t> where Self: 't;
@ -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,

28
src/entity/room.rs

@ -11,12 +11,35 @@ pub struct RoomEntityId(pub u32);
#[derive(Debug, Copy, Clone)]
pub enum RoomEntityMode {
Single,
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 {
@ -29,6 +52,7 @@ pub struct RoomEntity {
}
#[derive(Debug, Clone)]
pub struct NewRoomEntity {
pub name: String,
@ -66,7 +90,7 @@ impl NewRoomEntity {
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Serialize)]
pub enum RoomNote {
Create {
character_id: CharacterEntityId,

115
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?;
@ -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?;
@ -946,13 +951,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 +967,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 +996,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)

31
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(character_id, item_drop))
.act(actions::item_note_enemy_drop(character_id, room_id, monster_type))
.act(actions::add_item_to_local_floor(character_id))
.commit((item_state_proxy, transaction))
.await?;
item_state_proxy.commit().await;
Ok((transaction, floor_item))
})
}
pub fn box_drops_item<'a, EG> (
item_state: &'a mut ItemState,
entity_gateway: &'a mut EG,
character_id: CharacterEntityId,
room_id: RoomEntityId,
item_drop: ItemDrop)
-> BoxFuture<'a, Result<FloorItem, anyhow::Error>>
where
@ -474,6 +502,7 @@ where
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::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)))))

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

@ -3,7 +3,7 @@ use futures::stream::{FuturesOrdered, StreamExt};
use libpso::packet::ship::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent};
use crate::ship::room::Rooms;
use crate::ship::room::{Rooms, QuestCategoryType};
use crate::ship::map::enemy::RareMonsterAppearTable;
use crate::ship::location::{ClientLocation};
use crate::ship::packet::builder::quest;
@ -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))?;

54
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::{RoomEntity, RoomEntityId, NewRoomEntity, RoomEntityMode};
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())))])
},
@ -49,11 +56,34 @@ pub async fn create_room(id: ClientId,
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, new_area_client).await;
let mut room = RoomState::from_create_room(&create_room, map_builder, drop_table_builder, client.character.section_id, event)?;
let room_entity = entity_gateway.create_room(NewRoomEntity {
name: name.clone(),
section_id: client.character.section_id,
mode,
episode,
difficulty,
}).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??;

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, difficulty } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "single", "quests.toml"]))
},
RoomMode::Multi {episode, difficulty } => {
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, difficulty } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
},
RoomMode::Multi {episode, difficulty } => {
load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"]))
},
_ => {
Ok(BTreeMap::new())
}
}
}

138
src/ship/room.rs

@ -11,6 +11,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 +56,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 +93,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 +283,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 +306,7 @@ impl QuestCategoryType {
}
pub struct RoomState {
pub room_id: RoomEntityId,
pub mode: RoomMode,
pub name: String,
pub password: [u16; 16],
@ -309,22 +318,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 +354,58 @@ 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),
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)?,
})
}
}

3
src/ship/ship.rs

@ -755,7 +755,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?;

Loading…
Cancel
Save