diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 0ee351f..8e3731d 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 diff --git a/src/ship/items.rs b/src/ship/items.rs index 52de888..575c1ef 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -11,7 +11,9 @@ 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}; #[derive(Debug)] @@ -22,12 +24,12 @@ enum ItemInstance { } #[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 +110,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 +148,6 @@ pub struct ActiveItemDatabase { id: u32, } - - impl ActiveItemDatabase { pub fn new() -> ActiveItemDatabase { ActiveItemDatabase { @@ -155,7 +163,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 +181,50 @@ impl ActiveItemDatabase { let activated = stacked.into_iter().map(|i| self.activate_item(i)); ActiveInventory(activated.take(30).collect()) } + + // TODO: Result + pub fn activate_item_drop(&mut self, entity_gateway: &mut EG, item_drop: ItemDrop) -> ActiveItemOnFloor { + 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().unwrap() + }, + None => { + let meseta = match item_drop.item { + ItemDropType::Meseta(m) => m, + _ => panic!(), + }; + ItemInstance::Meseta(Meseta(meseta)) + } + }; + let active_item = self.activate_item(item_instance); + + 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/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..4afafea 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 4d31242..a468dfd 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -42,6 +42,8 @@ pub enum ShipError { ClientLocationError(#[from] ClientLocationError), MapsError(#[from] MapsError), InvalidRoom(u32), + MonsterAlreadyDroppedItem(ClientId, u16), + SliceError(#[from] std::array::TryFromSliceError), } #[derive(Debug)] @@ -147,6 +149,7 @@ pub struct ClientState { //guildcard: GuildCard, pub inventory: items::ActiveInventory, //bank: Bank, + pub floor_items: Vec, pub block: u32, } @@ -159,6 +162,7 @@ impl ClientState { session: session, inventory: inventory, //bank: bank, + floor_items: Vec::new(), block: 1, } } @@ -209,6 +213,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()