room notes, etc #139
| @ -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 | ||||
| ); | ||||
| @ -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; | ||||
| @ -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 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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 | ||||
| @ -474,6 +478,31 @@ 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_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(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?; | ||||
|  | ||||
| @ -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))))) | ||||
|  | ||||
| @ -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))?; | ||||
| 
 | ||||
|  | ||||
| @ -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??; | ||||
|  | ||||
| @ -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, 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
									
									
									
									
									
								
							
							
						
						
									
										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 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()?, | ||||
|             } | ||||
|     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)?, | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user