diff --git a/src/entity/character.rs b/src/entity/character.rs index 5d898b0..4c7f140 100644 --- a/src/entity/character.rs +++ b/src/entity/character.rs @@ -153,29 +153,8 @@ impl CharacterTechniques { pub fn as_bytes(&self) -> [u8; 20] { self.techs.iter() .fold([0xFF; 20], |mut techlist, (tech, level)| { - let index = match tech { - Technique::Foie => 0, - Technique::Gifoie => 1, - Technique::Rafoie => 2, - Technique::Barta => 3, - Technique::Gibarta => 4, - Technique::Rabarta => 5, - Technique::Zonde => 6, - Technique::Gizonde => 7, - Technique::Razonde => 8, - Technique::Grants => 9, - Technique::Deband => 10, - Technique::Jellen => 11, - Technique::Zalure => 12, - Technique::Shifta => 13, - Technique::Ryuker => 14, - Technique::Resta => 15, - Technique::Anti => 16, - Technique::Reverser => 17, - Technique::Megid => 18, - }; - - techlist[index] = level.0 - 1; + let index = tech.as_value(); + techlist[index as usize] = level.0 - 1; techlist }) } diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 303edf4..e3c801d 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -7,6 +7,7 @@ pub mod unit; pub mod mag; use crate::entity::character::CharacterEntityId; +use crate::ship::map::MapArea; #[derive(PartialEq, Copy, Clone, Debug, Hash, Eq)] pub struct ItemEntityId(pub u32); @@ -27,8 +28,10 @@ pub enum ItemLocation { slot: BankName, }, Floor { - // floor: eventually - // x y z: ????? + map_area: MapArea, + x: f32, + y: f32, + z: f32, }, /*Destroyed { // marks an item that has been consumed in some way @@ -46,6 +49,7 @@ pub struct Meseta(pub u32); impl Meseta { pub fn as_bytes(&self) -> [u8; 16] { let mut result = [0; 16]; + result[0] = 4; result[12..16].copy_from_slice(&u32::to_le_bytes(self.0)); result } @@ -96,7 +100,7 @@ impl ItemDetail { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct NewItemEntity { pub location: ItemLocation, pub item: ItemDetail, diff --git a/src/entity/item/tech.rs b/src/entity/item/tech.rs index 8976904..326aab7 100644 --- a/src/entity/item/tech.rs +++ b/src/entity/item/tech.rs @@ -24,8 +24,33 @@ pub enum Technique { Megid, } +impl Technique { + pub fn as_value(&self) -> u8 { + match self { + Technique::Foie => 0, + Technique::Gifoie => 1, + Technique::Rafoie => 2, + Technique::Barta => 3, + Technique::Gibarta => 4, + Technique::Rabarta => 5, + Technique::Zonde => 6, + Technique::Gizonde => 7, + Technique::Razonde => 8, + Technique::Grants => 9, + Technique::Deband => 10, + Technique::Jellen => 11, + Technique::Zalure => 12, + Technique::Shifta => 13, + Technique::Ryuker => 14, + Technique::Resta => 15, + Technique::Anti => 16, + Technique::Reverser => 17, + Technique::Megid => 18, + } + } +} -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TechniqueDisk { pub tech: Technique, pub level: u32, @@ -33,6 +58,11 @@ pub struct TechniqueDisk { impl TechniqueDisk { pub fn as_bytes(&self) -> [u8; 16] { - [0; 16] + let mut result = [0; 16]; + result[0] = 3; + result[1] = 2; + result[2] = self.level as u8 - 1; + result[4] = self.tech.as_value(); + result } } diff --git a/src/entity/item/tool.rs b/src/entity/item/tool.rs index 3583a63..dbde12b 100644 --- a/src/entity/item/tool.rs +++ b/src/entity/item/tool.rs @@ -394,7 +394,7 @@ impl ToolType { } -#[derive(Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct Tool { pub tool: ToolType, } diff --git a/src/ship/drops/mod.rs b/src/ship/drops/mod.rs index 294eed4..65aa1e2 100644 --- a/src/ship/drops/mod.rs +++ b/src/ship/drops/mod.rs @@ -1,3 +1,9 @@ +// TODO: there is some structure duplication that occurs here: +// the rare and box tables instantiate their own copies of the +// generic drop tables as they need them to apply their modifiers +// to their drops + + mod drop_table; mod rare_drop_table; mod generic_weapon; @@ -78,25 +84,20 @@ pub enum ItemDropType { Shield(shield::Shield), Unit(unit::Unit), Tool(tool::Tool), + //Tools(Vec), TechniqueDisk(tech::TechniqueDisk), Mag(mag::Mag), Meseta(u32), } -struct ItemDrop { - x: f32, - y: f32, - z: f32, - item: ItemDropType, +pub struct ItemDrop { + pub map_area: MapArea, + pub x: f32, + pub y: f32, + pub z: f32, + pub item: ItemDropType, } -impl ItemDrop { - pub fn as_client_bytes(&self) -> u8 { - 0 - } -} - - pub struct DropTable { monster_stats: HashMap, @@ -110,7 +111,6 @@ pub struct DropTable { rng: R, } - impl DropTable { pub fn new(episode: Episode, difficulty: Difficulty, section_id: SectionID) -> DropTable { let monster_stats: HashMap = load_data_file(episode, difficulty, section_id, "monster_dar.toml"); diff --git a/src/ship/drops/tech_table.rs b/src/ship/drops/tech_table.rs index 370f9c5..f576598 100644 --- a/src/ship/drops/tech_table.rs +++ b/src/ship/drops/tech_table.rs @@ -100,7 +100,7 @@ impl TechniqueTable { let tech_weights = WeightedIndex::new(tech_rates.clone().map(|(_, stat)| stat.rate)).unwrap(); let (tech, stat) = tech_rates.nth(tech_weights.sample(rng)).unwrap(); - let level = rng.gen_range(stat.min, stat.max+1); + let level = rng.gen_range(stat.min, stat.max+1) + 1; Some(ItemDropType::TechniqueDisk(TechniqueDisk { tech: *tech, @@ -118,10 +118,10 @@ mod test { let mut rng = rand_chacha::ChaCha20Rng::from_seed([23;32]); let tt = TechniqueTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly); - let tech_tests = vec![(MapArea::Forest1, Technique::Resta, 13), - (MapArea::Caves3, Technique::Foie, 24), - (MapArea::Mines2, Technique::Gibarta, 20), - (MapArea::DarkFalz, Technique::Razonde, 22)]; + let tech_tests = vec![(MapArea::Forest1, Technique::Resta, 14), + (MapArea::Caves3, Technique::Foie, 25), + (MapArea::Mines2, Technique::Gibarta, 21), + (MapArea::DarkFalz, Technique::Razonde, 23)]; for (area, tech, level) in tech_tests { assert!(tt.get_drop(&area, &mut rng) == Some(ItemDropType::TechniqueDisk( diff --git a/src/ship/items.rs b/src/ship/items.rs index 9dcd068..a00a1b1 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -11,23 +11,26 @@ use crate::entity::item::shield::Shield; use crate::entity::item::unit::Unit; use crate::entity::item::tool::Tool; use crate::entity::item::mag::Mag; -use crate::entity::item::Meseta; +use crate::entity::item::{Meseta, NewItemEntity}; +use crate::ship::map::MapArea; +use crate::ship::drops::{ItemDrop, ItemDropType}; +use crate::ship::ship::ShipError; #[derive(Debug, PartialEq)] -pub enum ItemInstance { +enum ItemInstance { Individual(ItemEntity), Stacked(Vec), Meseta(Meseta), } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct ActiveItemId(u32); +pub struct ActiveItemId(pub u32); #[derive(Debug)] pub struct ActiveItem { - id: ActiveItemId, + pub id: ActiveItemId, item: ItemInstance, } @@ -108,6 +111,14 @@ fn inventory_item_index(item: &ItemInstance) -> usize { } } +pub struct ActiveItemOnFloor { + pub map_area: MapArea, + pub x: f32, + pub y: f32, + pub z: f32, + pub item: ActiveItem, +} + fn stack_items(items: Vec) -> Vec { let mut stacks = HashMap::new(); @@ -138,8 +149,6 @@ pub struct ActiveItemDatabase { id: u32, } - - impl ActiveItemDatabase { pub fn new() -> ActiveItemDatabase { ActiveItemDatabase { @@ -155,7 +164,7 @@ impl ActiveItemDatabase { } } - // deactivate item + // TODO: deactivate item pub fn get_character_inventory(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> ActiveInventory { let items = entity_gateway.get_items_by_character(&character); @@ -173,6 +182,49 @@ impl ActiveItemDatabase { let activated = stacked.into_iter().map(|i| self.activate_item(i)); ActiveInventory(activated.take(30).collect()) } + + pub fn activate_item_drop(&mut self, entity_gateway: &mut EG, item_drop: ItemDrop) -> Result { + let item_detail = match item_drop.item { + ItemDropType::Weapon(w) => Some(ItemDetail::Weapon(w)), + ItemDropType::Armor(w) => Some(ItemDetail::Armor(w)), + ItemDropType::Shield(w) => Some(ItemDetail::Shield(w)), + ItemDropType::Unit(w) => Some(ItemDetail::Unit(w)), + ItemDropType::Tool(w) => Some(ItemDetail::Tool(w)), + ItemDropType::TechniqueDisk(w) => Some(ItemDetail::TechniqueDisk(w)), + ItemDropType::Mag(w) => Some(ItemDetail::Mag(w)), + ItemDropType::Meseta(_) => None + }; + let item_instance = match item_detail { + Some(item) => { + let item_entity = entity_gateway.create_item(NewItemEntity { + item: item, + location: ItemLocation::Floor { + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + } + }).unwrap(); + stack_items(vec![item_entity]).pop().ok_or(ShipError::ItemError)? + }, + None => { + let meseta = match item_drop.item { + ItemDropType::Meseta(m) => m, + _ => panic!(), + }; + ItemInstance::Meseta(Meseta(meseta)) + } + }; + let active_item = self.activate_item(item_instance); + + Ok(ActiveItemOnFloor { + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + item: active_item, + }) + } } #[cfg(test)] diff --git a/src/ship/location.rs b/src/ship/location.rs index e3b1c78..bc15201 100644 --- a/src/ship/location.rs +++ b/src/ship/location.rs @@ -52,6 +52,8 @@ pub enum JoinLobbyError { #[derive(Error, Debug, PartialEq)] #[error("")] pub enum GetAreaError { + NotInRoom, + NotInLobby, InvalidClient, } @@ -331,6 +333,24 @@ impl ClientLocation { .map(Clone::clone) } + pub fn get_room(&self, id: ClientId) -> Result { + if let RoomLobby::Room(room) = self.client_location.get(&id).ok_or(GetAreaError::InvalidClient)? { + Ok(*room) + } + else { + Err(GetAreaError::NotInRoom) + } + } + + pub fn get_lobby(&self, id: ClientId) -> Result { + if let RoomLobby::Lobby(lobby) = self.client_location.get(&id).ok_or(GetAreaError::InvalidClient)? { + Ok(*lobby) + } + else { + Err(GetAreaError::NotInLobby) + } + } + pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> { let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?; let client_list = match area { diff --git a/src/ship/map.rs b/src/ship/map.rs index d548056..52e297e 100644 --- a/src/ship/map.rs +++ b/src/ship/map.rs @@ -7,6 +7,7 @@ use std::fs::File; use byteorder::{LittleEndian, ReadBytesExt}; use rand::Rng; +use thiserror::Error; use crate::ship::monster::MonsterType; use crate::ship::room::Episode; @@ -16,7 +17,7 @@ struct RawMapEnemy { id: u32, _unknown1: u16, children: u16, - _unknown3: u16, + map_area: u16, _unknown4: u16, section: u16, wave_idd: u16, @@ -42,7 +43,7 @@ impl RawMapEnemy { id: cursor.read_u32::()?, _unknown1: cursor.read_u16::()?, children: cursor.read_u16::()?, - _unknown3: cursor.read_u16::()?, + map_area: cursor.read_u16::()?, _unknown4: cursor.read_u16::()?, section: cursor.read_u16::()?, wave_idd: cursor.read_u16::()?, @@ -65,18 +66,22 @@ impl RawMapEnemy { } -#[derive(Debug)] +#[derive(Error, Debug)] +#[error("")] enum MapEnemyError { UnknownEnemyId(u32), + MapAreaError(#[from] MapAreaError), } #[derive(Debug, Copy, Clone)] pub struct MapEnemy { pub monster: MonsterType, + pub map_area: MapArea, hp: u32, - // other stats from bp.n - dead: bool, + // TODO: other stats from battleparam + pub dropped_item: bool, + pub gave_exp: bool, } impl MapEnemy { @@ -169,16 +174,20 @@ impl MapEnemy { Ok(MapEnemy { monster: monster, + map_area: MapArea::from_value(&episode, enemy.map_area as u32)?, hp: 0, - dead: false, + dropped_item: false, + gave_exp: false, }) } - fn new(monster: MonsterType) -> MapEnemy { + fn new(monster: MonsterType, map_area: MapArea) -> MapEnemy { MapEnemy { monster: monster, + map_area: map_area, hp: 0, - dead: false, + dropped_item: false, + gave_exp: false, } } } @@ -340,7 +349,7 @@ enum MapVariantMode { Offline, } -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum MapArea { Pioneer2Ep1, Forest1, @@ -359,12 +368,14 @@ pub enum MapArea { DarkFalz, } +#[derive(Error, Debug)] +#[error("")] pub enum MapAreaError { UnknownMapArea(u32), } impl MapArea { - pub fn from_value(episode: Episode, area: u32) -> Result { + pub fn from_value(episode: &Episode, area: u32) -> Result { match (episode, area) { (Episode::One, 0) => Ok(MapArea::Pioneer2Ep1), (Episode::One, 1) => Ok(MapArea::Forest1), @@ -404,6 +415,26 @@ impl MapArea { _ => None } } + + pub fn area_value(&self) -> u8 { + match self { + MapArea::Pioneer2Ep1 => 0, + MapArea::Forest1 => 1, + MapArea::Forest2 => 2, + MapArea::Caves1 => 3, + MapArea::Caves2 => 4, + MapArea::Caves3 => 5, + MapArea::Mines1 => 6, + MapArea::Mines2 => 7, + MapArea::Ruins1 => 8, + MapArea::Ruins2 => 9, + MapArea::Ruins3 => 10, + MapArea::Dragon => 11, + MapArea::DeRolLe => 12, + MapArea::VolOpt => 13, + MapArea::DarkFalz => 14, + } + } } @@ -516,76 +547,76 @@ fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec { for _ in 0..30 { - monsters.push(Some(MapEnemy::new(MonsterType::Mothmant))); + monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area))); } }, MonsterType::PofuillySlime => { for _ in 0..4 { - monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime))); + monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area))); } }, MonsterType::PanArms => { - monsters.push(Some(MapEnemy::new(MonsterType::Hidoom))); - monsters.push(Some(MapEnemy::new(MonsterType::Migium))); + monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area))); + monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area))); }, MonsterType::SinowBeat => { for _ in 0..4 { - monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat))); + monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area))); } }, MonsterType::SinowGold => { for _ in 0..4 { - monsters.push(Some(MapEnemy::new(MonsterType::SinowGold))); + monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area))); } }, MonsterType::Canane => { for _ in 0..8 { - monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine))); + monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area))); } }, MonsterType::ChaosSorcerer => { - monsters.push(Some(MapEnemy::new(MonsterType::BeeR))); - monsters.push(Some(MapEnemy::new(MonsterType::BeeL))); + monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area))); + monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area))); }, MonsterType::Bulclaw => { for _ in 0..4 { - monsters.push(Some(MapEnemy::new(MonsterType::Claw))); + monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area))); } }, MonsterType::DeRolLe => { for _ in 0..10 { - monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody))); + monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area))); } for _ in 0..9 { - monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine))); + monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area))); } }, MonsterType::VolOptPartA => { for _ in 0..6 { - monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar))); + monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area))); } for _ in 0..24 { - monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor))); + monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area))); } for _ in 0..2 { - monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused))); + monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area))); } - monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp))); - monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore))); - monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused))); + monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp, monster.map_area))); + monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore, monster.map_area))); + monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area))); }, // TOOD: this cares about difficulty (theres an ult-specific darvant?) MonsterType::DarkFalz => { for _ in 0..509 { - monsters.push(Some(MapEnemy::new(MonsterType::Darvant))); + monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area))); } - monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3))); - monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2))); - monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1))); + monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3, monster.map_area))); + monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2, monster.map_area))); + monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1, monster.map_area))); }, _ => { for _ in 0..enemy.children { - monsters.push(Some(MapEnemy::new(monster.monster))); + monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area))); } } } @@ -596,6 +627,12 @@ fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec MapEnemy { - self.enemy_data[id].unwrap() + pub fn enemy_by_id(&self, id: usize) -> Result { + self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id)) } pub fn map_headers(&self) -> [u32; 0x20] { diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs new file mode 100644 index 0000000..e724fc0 --- /dev/null +++ b/src/ship/packet/builder/message.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; +use libpso::packet::ship::*; +use libpso::packet::messages::*; +use crate::common::serverstate::ClientId; +use crate::common::leveltable::CharacterLevelTable; +use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients}; +use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder}; +use crate::ship::location::{ClientLocation, LobbyId, AreaClient, ClientLocationError}; +use crate::entity::character::CharacterEntity; +use crate::ship::items::{ActiveInventory, ActiveItemOnFloor}; +use crate::ship::packet::builder::{player_header, player_info}; +use std::convert::TryInto; +use libpso::character::character::{Inventory, InventoryItem}; +use libpso::utf8_to_utf16_array; + + +pub fn item_drop(client: u8, target: u8, item_drop: &ActiveItemOnFloor) -> Result { + let item_bytes = item_drop.item.as_client_bytes(); + Ok(ItemDrop { + client: client, + target: target, + area: item_drop.map_area.area_value(), + variety: 0, + unknown: 0, + x: item_drop.x, + z: item_drop.z, + y: item_drop.y, + item_bytes: item_bytes[0..12].try_into()?, + item_id: item_drop.item.id.0, + item_bytes2: item_bytes[12..16].try_into()?, + unknown2: 0, + }) +} diff --git a/src/ship/packet/builder/mod.rs b/src/ship/packet/builder/mod.rs index 19120f1..2e8866e 100644 --- a/src/ship/packet/builder/mod.rs +++ b/src/ship/packet/builder/mod.rs @@ -1,4 +1,5 @@ pub mod lobby; +pub mod message; pub mod room; use libpso::character::character::Inventory; diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index a824684..03855cb 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -6,10 +6,14 @@ use crate::common::serverstate::ClientId; use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients, Rooms}; use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder}; -use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOMS}; +use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOMS, ClientLocationError}; +use crate::ship::room::RoomState; +use crate::ship::drops::{ItemDrop, ItemDropType}; +use crate::ship::items::ActiveItemDatabase; use libpso::character::character; use crate::entity::gateway::EntityGateway; use libpso::{utf8_to_array, utf8_to_utf16_array}; +use crate::ship::packet::builder; fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation) -> Box + Send> { @@ -20,8 +24,6 @@ fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: })) } - - pub fn guildcard_send(id: ClientId, guildcard_send: &GuildcardSend, target: u32, @@ -46,3 +48,57 @@ pub fn guildcard_send(id: ClientId, }; send_to_client(id, target as u8, msg, &client_location) } + +pub fn request_item(id: ClientId, + request_item: &RequestItem, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + rooms: &mut Rooms, + active_items: &mut ActiveItemDatabase) + -> Result + Send>, ShipError> +where EG: EntityGateway { + let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?; + let mut room = rooms.get_mut(room_id.0) + .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))? + .as_mut() + .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?; + + let monster = room.maps.enemy_by_id(request_item.enemy_id as usize)?; + if monster.dropped_item { + return Err(ShipError::MonsterAlreadyDroppedItem(id, request_item.enemy_id)) + } + + let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + + let item_drop_packets = clients_in_area.into_iter() + .filter_map(|area_client| { + room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| { + warn!("drop is? {:?}", item_drop_type); + (area_client, item_drop_type) + }) + }) + .map(|(area_client, item_drop_type)| -> Result<_, ShipError> { + let item_drop = ItemDrop { + map_area: monster.map_area, + x: request_item.x, + y: request_item.y, + z: request_item.z, + item: item_drop_type, + }; + + let activated_item = active_items.activate_item_drop(entity_gateway, item_drop)?; + let mut client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?; + let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &activated_item)?; + client.floor_items.push(activated_item); + Ok((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg))))) + }) + .filter_map(|item_drop_pkt| { + // TODO: log errors here + item_drop_pkt.ok() + }) + .collect::>(); // TODO: can EntityGateway be Sync? + + Ok(Box::new(item_drop_packets.into_iter())) +} diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index a58de7c..d597768 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -19,7 +19,7 @@ pub fn request_exp(id: ClientId, match client_location.get_area(id).unwrap() { RoomLobby::Room(room) => { let r = rooms[room.0].as_ref().unwrap(); - warn!("killed a {:?}", r.maps.enemy_by_id(request_exp.enemy_id as usize).monster); + warn!("killed a {:?}", r.maps.enemy_by_id(request_exp.enemy_id as usize).unwrap().monster); }, _ => {} }; diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 8936afd..10d2db5 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -25,6 +25,7 @@ use crate::ship::location::{ClientLocation, LobbyId, RoomId, RoomLobby, MAX_ROOM use crate::ship::character::{CharacterBytesBuilder, FullCharacterBytesBuilder}; use crate::ship::items; use crate::ship::room; +use crate::ship::map::MapsError; use crate::ship::packet::handler; pub const SHIP_PORT: u16 = 23423; @@ -39,7 +40,11 @@ pub enum ShipError { InvalidSlot(ClientId, u32), TooManyClients, ClientLocationError(#[from] ClientLocationError), + MapsError(#[from] MapsError), InvalidRoom(u32), + MonsterAlreadyDroppedItem(ClientId, u16), + SliceError(#[from] std::array::TryFromSliceError), + ItemError, // TODO: refine this } #[derive(Debug)] @@ -145,6 +150,7 @@ pub struct ClientState { //guildcard: GuildCard, pub inventory: items::ActiveInventory, //bank: Bank, + pub floor_items: Vec, pub block: u32, } @@ -157,6 +163,7 @@ impl ClientState { session: session, inventory: inventory, //bank: bank, + floor_items: Vec::new(), block: 1, } } @@ -207,6 +214,9 @@ impl ShipServerState { GameMessage::GuildcardSend(guildcard_send) => { handler::direct_message::guildcard_send(id, guildcard_send, target, &self.client_location, &self.clients) }, + GameMessage::RequestItem(request_item) => { + handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_database).unwrap() + }, _ => { let cmsg = msg.clone(); Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()