From ac28354dd7bededcd8968af6629a5f48fab54091 Mon Sep 17 00:00:00 2001 From: jake Date: Wed, 13 May 2020 22:32:30 -0600 Subject: [PATCH] redo items to account for client ignoring the assigned item_id and using its own. also item picking up --- src/main.rs | 1 + src/ship/character.rs | 2 +- src/ship/items.rs | 354 ++++++++++++++++------ src/ship/location.rs | 3 +- src/ship/packet/builder/lobby.rs | 9 +- src/ship/packet/builder/message.rs | 6 +- src/ship/packet/builder/mod.rs | 8 +- src/ship/packet/builder/room.rs | 4 +- src/ship/packet/handler/auth.rs | 4 +- src/ship/packet/handler/direct_message.rs | 66 +--- src/ship/packet/handler/lobby.rs | 27 +- src/ship/packet/handler/message.rs | 36 ++- src/ship/packet/handler/room.rs | 9 +- src/ship/room.rs | 15 - src/ship/ship.rs | 46 +-- 15 files changed, 382 insertions(+), 208 deletions(-) diff --git a/src/main.rs b/src/main.rs index f588177..fc66329 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ #![feature(const_generics)] #![feature(maybe_uninit_extra)] #![feature(const_in_array_repeat_expressions)] +#![feature(drain_filter)] diff --git a/src/ship/character.rs b/src/ship/character.rs index 8b9be03..2d79e8c 100644 --- a/src/ship/character.rs +++ b/src/ship/character.rs @@ -80,7 +80,7 @@ pub struct FullCharacterBytesBuilder<'a> { character: Option<&'a CharacterEntity>, stats: Option<&'a CharacterStats>, level: Option, - inventory: Option<&'a CharacterInventory>, + inventory: Option<&'a CharacterInventory<'a>>, key_config: Option<&'a [u8; 0x16C]>, joystick_config: Option<&'a [u8; 0x38]>, symbol_chat: Option<&'a [u8; 1248]>, diff --git a/src/ship/items.rs b/src/ship/items.rs index 20af9a7..86f3d66 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -1,27 +1,34 @@ -#![allow(dead_code)] use std::collections::{HashMap, BTreeMap}; use libpso::character::character;//::InventoryItem; +use thiserror::Error; use crate::entity::gateway::EntityGateway; -use crate::entity::character::CharacterEntity; +use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, ItemLocation}; use crate::entity::item::{Meseta, NewItemEntity}; use crate::entity::item::tool::Tool; use crate::ship::map::MapArea; use crate::ship::drops::{ItemDrop, ItemDropType}; -use crate::ship::ship::ClientState; +use crate::ship::location::{AreaClient, RoomId}; #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ActiveItemId(pub u32); -#[derive(Debug)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +struct RoomItemId(RoomId, u32); + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ClientItemId(pub u32); + +#[derive(Debug, Clone)] enum ActiveItemEntityId { Individual(ItemEntityId), Stacked(Vec), Meseta(Meseta), } -#[derive(Debug)] + +#[derive(Debug, Clone)] enum HeldItemType { Individual(ItemDetail), Stacked(Tool, usize), @@ -48,21 +55,7 @@ impl HeldItemType { } } -#[derive(Debug)] -pub struct InventoryItem { - id: ActiveItemId, - item: HeldItemType, - //slot: usize, - equipped: bool, -} - -#[derive(Debug)] -pub struct BankItem { - id: ActiveItemId, - item: HeldItemType, -} - -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum FloorItemType { Individual(ItemDetail), Stacked(Tool, usize), @@ -93,9 +86,18 @@ impl FloorItemType { } } -#[derive(Debug)] +#[derive(Debug, Clone)] +pub struct InventoryItem { + entity_id: ActiveItemEntityId, + item_id: ClientItemId, + item: HeldItemType, + equipped: bool, +} + +#[derive(Debug, Clone)] pub struct FloorItem { - pub id: ActiveItemId, + entity_id: ActiveItemEntityId, + pub item_id: ClientItemId, pub item: FloorItemType, pub map_area: MapArea, pub x: f32, @@ -104,12 +106,15 @@ pub struct FloorItem { } #[derive(Debug)] -pub enum InventoryError { +pub struct BankItem { + id: ActiveItemId, + item: HeldItemType, } -pub struct CharacterInventory(Vec); -impl CharacterInventory { +pub struct CharacterInventory<'a>(&'a Vec); + +impl<'a> CharacterInventory<'a> { pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] { self.0.iter() .enumerate() @@ -117,7 +122,7 @@ impl CharacterInventory { let bytes = item.item.as_client_bytes(); inventory[slot].data1.copy_from_slice(&bytes[0..12]); inventory[slot].data2.copy_from_slice(&bytes[12..16]); - inventory[slot].item_id = item.id.0; + inventory[slot].item_id = item.item_id.0; // does this do anything? inventory[slot].equipped = if item.equipped { 1 } else { 0 }; // because this actually equips the item @@ -126,42 +131,60 @@ impl CharacterInventory { }) } - pub fn add_item(&mut self, item: InventoryItem) -> Result { - self.0.push(item); - Ok(self.count() - 1) - } - pub fn count(&self) -> usize { self.0.len() } } - -#[derive(Debug)] +#[derive(Error, Debug)] +#[error("")] pub enum ItemManagerError { EntityGatewayError, + NoSuchItemId(ClientItemId), + NoCharacter(CharacterEntityId), CouldNotAddToInventory(FloorItem), + //ItemBelongsToOtherPlayer, + Idunnoman, } pub struct ItemManager { - id: usize, - active_to_entity: HashMap, + id_counter: u32, + + character_inventory: HashMap>, + character_floor: HashMap>, + character_item_id_counter: HashMap, + + character_room: HashMap, + room_floor: HashMap>, + room_item_id_counter: HashMap, } impl ItemManager { pub fn new() -> ItemManager { ItemManager { - id: 0, - active_to_entity: HashMap::new() + id_counter: 0, + character_inventory: HashMap::new(), + character_floor: HashMap::new(), + character_item_id_counter: HashMap::new(), + character_room: HashMap::new(), + room_floor: HashMap::new(), + room_item_id_counter: HashMap::new(), } } - fn next_id(&mut self) -> ActiveItemId { - self.id += 1; - ActiveItemId(self.id as u32) + pub fn next_global_item_id(&mut self) -> ClientItemId { + self.id_counter += 1; + ClientItemId(self.id_counter) + } + + pub fn next_drop_item_id(&mut self, room_id: RoomId) -> ClientItemId { + let next_id = self.room_item_id_counter.entry(room_id).or_insert(0xF0000000); + *next_id += 1; + ClientItemId(*next_id) } - pub fn get_character_inventory(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> CharacterInventory { + // TODO: Result + pub fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) { let items = entity_gateway.get_items_by_character(&character); let inventory_items = items.into_iter() .filter_map(|item| { @@ -190,18 +213,151 @@ impl ItemManager { }) .into_iter() .map(|(_slot, (held_item, entity_id, equipped))| { - let id = self.next_id(); - self.active_to_entity.insert(id, entity_id); + let id = self.next_global_item_id(); InventoryItem { - id: id, + entity_id: entity_id, + item_id: id, item: held_item, equipped: equipped, } }); - CharacterInventory(inventory_items.take(30).collect()) + let k = inventory_items.take(30).collect(); + self.character_inventory.insert(character.id, k); + } + + pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) { + let base_id = ((area_client.local_client.id() as u32) << 21) | 0x10000; + let inventory = self.character_inventory.get_mut(&character.id).unwrap(); + for (i, item) in inventory.iter_mut().enumerate() { + item.item_id = ClientItemId(base_id + i as u32); + } + self.character_room.insert(character.id, room_id); + self.character_floor.insert(character.id, Vec::new()); + self.room_floor.entry(room_id).or_insert(Vec::new()); + self.character_item_id_counter.insert(character.id, base_id + inventory.len() as u32); + } + + pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result { + Ok(CharacterInventory(self.character_inventory.get(&character.id) + .ok_or(ItemManagerError::NoCharacter(character.id))?)) + } + + pub fn remove_character_from_room(&mut self, character: &CharacterEntity) { + self.character_inventory.remove(&character.id); + self.character_floor.remove(&character.id); + self.character_room.remove(&character.id) + .as_ref() + .map(|room| { + if self.character_room.iter().find(|(_, r)| *r == room).is_none() { + self.room_floor.remove(room); + } + }); + } + + pub fn get_inventory_item_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result { + let inventory = self.character_inventory.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + inventory.iter() + .filter(|item| { + item.item_id == item_id + }) + .nth(0) + .ok_or(ItemManagerError::NoSuchItemId(item_id)) + .map(Clone::clone) + } + + pub fn get_floor_item_by_id(&self, character: &CharacterEntity, item_id: ClientItemId) -> Result { + let floor = self.character_floor.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let room = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let shared_floor = self.room_floor.get(room).ok_or(ItemManagerError::NoCharacter(character.id))?; + floor.iter() + .chain(shared_floor.iter()) + .filter(|item| { + item.item_id == item_id + }) + .nth(0) + .ok_or(ItemManagerError::NoSuchItemId(item_id)) + .map(Clone::clone) + } + + pub fn character_picks_up_item(&mut self, entity_gateway: &mut EG, character: &mut CharacterEntity, floor_item: FloorItem) -> Result<(), ItemManagerError> { + let local_floor = self.character_floor.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?; + + if let Some(_) = local_floor.iter().find(|i| i.item_id == floor_item.item_id) { + local_floor.retain(|item| { + item.item_id != floor_item.item_id + }); + } + else if let Some(_) = shared_floor.iter().find(|i| i.item_id == floor_item.item_id) { + shared_floor.retain(|item| { + item.item_id != floor_item.item_id + }); + } + else { + return Err(ItemManagerError::NoSuchItemId(floor_item.item_id)) + } + + if inventory.len() >= 30 { + return Err(ItemManagerError::CouldNotAddToInventory(floor_item)); + } + + match floor_item.item { + FloorItemType::Individual(item) => { + let inventory_item = InventoryItem { + entity_id: floor_item.entity_id, + item_id: floor_item.item_id, + item: HeldItemType::Individual(item.clone()), + equipped: false, + }; + + if let ActiveItemEntityId::Individual(item_id) = &inventory_item.entity_id { + entity_gateway.save_item(&ItemEntity { + id: *item_id, + item: item, + location: ItemLocation::Inventory { + character_id: character.id, + slot: inventory.len(), + equipped: false, + }, + }); // TODO: error check + inventory.push(inventory_item); + } // else something went very wrong TODO: log it + }, + FloorItemType::Stacked(tool, usize) => { + let inventory_item = InventoryItem { + entity_id: floor_item.entity_id, + item_id: floor_item.item_id, + item: HeldItemType::Stacked(tool, usize), + equipped: false, + }; + + if let ActiveItemEntityId::Stacked(item_ids) = &inventory_item.entity_id { + for item_id in item_ids { + entity_gateway.save_item(&ItemEntity { + id: *item_id, + item: ItemDetail::Tool(tool), + location: ItemLocation::Inventory { + character_id: character.id, + slot: inventory.len(), + equipped: false, + }, + }); // TODO: error check + }; + inventory.push(inventory_item); + } // else something went very wrong TODO: log it + }, + FloorItemType::Meseta(meseta) => { + character.meseta += meseta.0; + entity_gateway.save_character(&character); + } + } + + Ok(()) } - - pub fn drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result { + + pub fn enemy_drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&FloorItem, ItemManagerError> { let item = match item_drop.item { ItemDropType::Weapon(w) => FloorItemType::Individual(ItemDetail::Weapon(w)), ItemDropType::Armor(w) => FloorItemType::Individual(ItemDetail::Armor(w)), @@ -214,8 +370,8 @@ impl ItemManager { //ItemDropType::Tool(t) if !t.is_stackable() => FloorItemType::Individual(ItemDetail::Tool(w)), ItemDropType::Meseta(m) => FloorItemType::Meseta(Meseta(m)) }; - - let active_entity_ids = match &item { + + let entity_id = match &item { FloorItemType::Individual(i) => { let entity = entity_gateway.create_item(NewItemEntity { item: i.clone(), @@ -250,70 +406,86 @@ impl ItemManager { FloorItemType::Meseta(m) => ActiveItemEntityId::Meseta(m.clone()), }; - let id = self.next_id(); - self.active_to_entity.insert(id, active_entity_ids); - Ok(FloorItem { - id: id, + let room_id = *self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let item_id = self.next_drop_item_id(room_id); + + let floor_item = FloorItem { + entity_id: entity_id, + item_id: item_id, item: item, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, - }) + }; + + self.character_floor.entry(character.id).or_insert(Vec::new()).push(floor_item); + self.character_floor.get(&character.id).ok_or(ItemManagerError::Idunnoman)?.last().ok_or(ItemManagerError::Idunnoman) } - pub fn move_item_from_floor_to_inventory(&mut self, entity_gateway: &mut EG, client: &mut ClientState, floor_item: FloorItem) -> Result<(), ItemManagerError> { - match floor_item.item { - FloorItemType::Individual(item) => { - let inventory_item = InventoryItem { - id: floor_item.id, - item: HeldItemType::Individual(item.clone()), - equipped: false, - }; + pub fn player_drop_item_on_shared_floor(&mut self, + entity_gateway: &mut EG, + character: &CharacterEntity, + inventory_item: InventoryItem, + item_drop_location: (MapArea, f32, f32, f32)) + -> Result<(), ItemManagerError> { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let shared_floor = self.room_floor.get_mut(&room_id).ok_or(ItemManagerError::NoCharacter(character.id))?; + + inventory + .drain_filter(|i| i.item_id == inventory_item.item_id) + .nth(0) + .ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id))?; - let item_entity_id = self.active_to_entity.get(&floor_item.id).unwrap(); // TODO: unwrap - if let ActiveItemEntityId::Individual(item_id) = item_entity_id { - let slot = client.inventory.add_item(inventory_item).unwrap(); // TODO: unwrap + let room_floor_item = FloorItem { + entity_id: inventory_item.entity_id, + item_id: inventory_item.item_id, + item: match inventory_item.item { + HeldItemType::Individual(item) => FloorItemType::Individual(item), + HeldItemType::Stacked(tool, count) => FloorItemType::Stacked(tool, count), + }, + map_area: item_drop_location.0, + x: item_drop_location.1, + y: item_drop_location.2, + z: item_drop_location.3, + }; + + match &room_floor_item.item { + FloorItemType::Individual(item) => { + if let ActiveItemEntityId::Individual(item_id) = &room_floor_item.entity_id { entity_gateway.save_item(&ItemEntity { id: *item_id, - item: item, - location: ItemLocation::Inventory { - character_id: client.character.id, - slot: slot, - equipped: false, - }, + item: item.clone(), + location: ItemLocation::SharedFloor { + map_area: item_drop_location.0, + x: item_drop_location.1, + y: item_drop_location.2, + z: item_drop_location.3, + } }); // TODO: error check - } // else something went very wrong TODO: log it + } // else something went very wrong: TODO: log it }, - FloorItemType::Stacked(tool, usize) => { - let inventory_item = InventoryItem { - id: floor_item.id, - item: HeldItemType::Stacked(tool, usize), - equipped: false, - }; - - let item_entity_id = self.active_to_entity.get(&floor_item.id).unwrap(); // TODO: unwrap - if let ActiveItemEntityId::Stacked(item_ids) = item_entity_id { - let slot = client.inventory.add_item(inventory_item).unwrap(); // TODO: unwrap + FloorItemType::Stacked(tool, _count) => { + if let ActiveItemEntityId::Stacked(item_ids) = &room_floor_item.entity_id { for item_id in item_ids { entity_gateway.save_item(&ItemEntity { id: *item_id, - item: ItemDetail::Tool(tool), - location: ItemLocation::Inventory { - character_id: client.character.id, - slot: slot, - equipped: false, + item: ItemDetail::Tool(*tool), + location: ItemLocation::SharedFloor { + map_area: item_drop_location.0, + x: item_drop_location.1, + y: item_drop_location.2, + z: item_drop_location.3, }, }); // TODO: error check } } // else something went very wrong TODO: log it }, - FloorItemType::Meseta(meseta) => { - client.character.meseta += meseta.0; - entity_gateway.save_character(&client.character); - } + _ => {}, // can meseta get here? } + shared_floor.push(room_floor_item); Ok(()) } } diff --git a/src/ship/location.rs b/src/ship/location.rs index 44cf113..2248d5a 100644 --- a/src/ship/location.rs +++ b/src/ship/location.rs @@ -14,7 +14,8 @@ pub enum AreaType { #[derive(Debug, Copy, Clone, PartialEq)] pub struct LobbyId(pub usize); -#[derive(Debug, Copy, Clone, PartialEq)] + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RoomId(pub usize); impl LobbyId { diff --git a/src/ship/packet/builder/lobby.rs b/src/ship/packet/builder/lobby.rs index dadaf09..5e7d33b 100644 --- a/src/ship/packet/builder/lobby.rs +++ b/src/ship/packet/builder/lobby.rs @@ -4,19 +4,21 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{ShipError, Clients}; use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError}; use crate::ship::packet::builder::{player_info}; +use crate::ship::items::ItemManager; pub fn join_lobby(id: ClientId, lobby: LobbyId, client_location: &ClientLocation, clients: &Clients, + item_manager: &ItemManager, level_table: &CharacterLevelTable) -> Result { let lobby_clients = client_location.get_clients_in_lobby(lobby).map_err(|err| -> ClientLocationError { err.into() })?; let playerinfo = lobby_clients.iter() .map(|area_client| { let client = clients.get(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client)).unwrap(); - player_info(0x100, &client, area_client, level_table) + player_info(0x100, &client, area_client, item_manager, level_table) }); let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); @@ -38,6 +40,7 @@ pub fn add_to_lobby(id: ClientId, lobby: LobbyId, client_location: &ClientLocation, clients: &Clients, + item_manager: &ItemManager, level_table: &CharacterLevelTable) -> Result { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); @@ -52,7 +55,7 @@ pub fn add_to_lobby(id: ClientId, block: client.block as u16, event: 0, padding: 0, - playerinfo: player_info(0x100, &client, &area_client, level_table), + playerinfo: player_info(0x100, &client, &area_client, item_manager, level_table), }) } @@ -68,4 +71,4 @@ pub fn remove_from_lobby(id: ClientId, leader: prev_area_leader_index, _padding: 0, }) -} \ No newline at end of file +} diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index cad61f2..2f84cd0 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -17,7 +17,7 @@ pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result Result Resu unknown: 0, area: item.map_area.area_value(), unknown2: 0, - item_id: item.id.0, + item_id: item.item_id.0, }) } diff --git a/src/ship/packet/builder/mod.rs b/src/ship/packet/builder/mod.rs index 83c6d2a..0bcfc3c 100644 --- a/src/ship/packet/builder/mod.rs +++ b/src/ship/packet/builder/mod.rs @@ -8,6 +8,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::character::CharacterBytesBuilder; use crate::ship::ship::ClientState; use crate::ship::location::AreaClient; +use crate::ship::items::ItemManager; pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -> PlayerHeader { PlayerHeader { @@ -20,8 +21,9 @@ pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) - } } -pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, level_table: &CharacterLevelTable) -> PlayerInfo { +pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_manager: &ItemManager, level_table: &CharacterLevelTable) -> PlayerInfo { let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); + let inventory = item_manager.get_character_inventory(&client.character).unwrap(); let character = CharacterBytesBuilder::new() .character(&client.character) .stats(&stats) @@ -30,11 +32,11 @@ pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, lev PlayerInfo { header: player_header(tag, client, area_client), inventory: Inventory { - item_count: client.inventory.count() as u8, + item_count: inventory.count() as u8, hp_mats_used: 0, // TODO: materials tp_mats_used: 0, // TODO: materials language: 0, // TODO: account language - items: client.inventory.as_client_inventory_items(), + items: inventory.as_client_inventory_items(), }, character: character, } diff --git a/src/ship/packet/builder/room.rs b/src/ship/packet/builder/room.rs index 32716df..7dda664 100644 --- a/src/ship/packet/builder/room.rs +++ b/src/ship/packet/builder/room.rs @@ -4,6 +4,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{ShipError, ClientState, Clients}; use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationError}; use crate::ship::room::RoomState; +use crate::ship::items::ItemManager; use crate::ship::packet::builder::{player_header, player_info}; pub fn join_room(id: ClientId, @@ -51,6 +52,7 @@ pub fn add_to_room(_id: ClientId, client: &ClientState, area_client: &AreaClient, leader: &AreaClient, + item_manager: &ItemManager, level_table: &CharacterLevelTable, _room_id: RoomId, ) @@ -65,7 +67,7 @@ pub fn add_to_room(_id: ClientId, block: 0, event: 0, padding: 0, - playerinfo: player_info(0x10000, client, &area_client, level_table), + playerinfo: player_info(0x10000, client, &area_client, item_manager, level_table), }) } diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index 0f990fb..e05ed8b 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -26,9 +26,9 @@ pub fn validate_login(id: ClientId, .clone(); let settings = entity_gateway.get_user_settings_by_user(&user) .ok_or(ShipError::ClientNotFound(id))?; - let inventory = item_manager.get_character_inventory(entity_gateway, &character); - clients.insert(id, ClientState::new(user, settings, character, inventory, pkt.session)); + item_manager.load_character(entity_gateway, &character); + clients.insert(id, ClientState::new(user, settings, character, pkt.session)); vec![SendShipPacket::LoginResponse(response), SendShipPacket::ShipBlockList(ShipBlockList::new(&&ship_name, 3))] }, Err(err) => { diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index 5b4e228..8edb708 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -5,7 +5,7 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::drops::ItemDrop; -use crate::ship::items::{ItemManager, ItemManagerError, FloorItemType}; +use crate::ship::items::{ItemManager, FloorItemType, ClientItemId}; use crate::entity::gateway::EntityGateway; use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; @@ -84,9 +84,8 @@ where item: item_drop_type, }; let client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?; - let floor_item = item_manager.drop_item_on_local_floor(entity_gateway, &client.character, item_drop).unwrap(); // TODO: unwrap + let floor_item = item_manager.enemy_drop_item_on_local_floor(entity_gateway, &client.character, item_drop).unwrap(); // TODO: unwrap let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &floor_item)?; - client.floor_items.push(floor_item); Ok((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg))))) }) .filter_map(|item_drop_pkt| { @@ -103,81 +102,40 @@ pub fn pickup_item(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - rooms: &mut Rooms, item_manager: &mut ItemManager) -> Result + Send>, ShipError> where EG: EntityGateway { - #[derive(Copy, Clone)] - enum ItemFloor { - Local, - Shared - } - - let mut client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; + let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?; - let 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 clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; - - let (item_index, (_, floor)) = client.floor_items.iter() - .zip(std::iter::repeat(ItemFloor::Local)) - .enumerate() - .filter(|(_, (item, _))| item.id.0 == pickup_item.item_id) - .next() - .or_else(|| { - room.floor_items.iter() - .zip(std::iter::repeat(ItemFloor::Shared)) - .enumerate() - .filter(|(_, (item, _))| item.id.0 == pickup_item.item_id) - .next() - }) - .ok_or(ShipError::PickUpInvalidItemId(pickup_item.item_id))?; - - let item = match floor { - ItemFloor::Local => client.floor_items.remove(item_index), - ItemFloor::Shared => room.floor_items.remove(item_index), - }; - + let item = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?; let remove_item = builder::message::remove_item_from_floor(area_client, &item)?; let create_item = match item.item { FloorItemType::Meseta(_) => None, _ => Some(builder::message::create_item(area_client, &item)?), }; - match item_manager.move_item_from_floor_to_inventory(entity_gateway, &mut client, item) { + + match item_manager.character_picks_up_item(entity_gateway, &mut client.character, item) { Ok(_) => { Ok(Box::new(Vec::new().into_iter() .chain(clients_in_area.clone().into_iter() - .filter(move |c| { - match floor { - ItemFloor::Local => c.client == id, - ItemFloor::Shared => true, - } - }) .map(move |c| { (c.client, SendShipPacket::Message(Message::new(GameMessage::RemoveItemFromFloor(remove_item.clone())))) })) - .chain(clients_in_area.into_iter().filter_map(move |c| { - //(c.client, SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item.clone())))) - create_item.clone().map(|ci| (c.client, SendShipPacket::Message(Message::new(GameMessage::CreateItem(ci))))) - } + .chain(clients_in_area.into_iter(). + filter_map(move |c| { + create_item.clone().map(|ci| (c.client, SendShipPacket::Message(Message::new(GameMessage::CreateItem(ci))))) + } ))) ) }, Err(err) => { - // inventory full, probably - if let ItemManagerError::CouldNotAddToInventory(item) = err { - match floor { - ItemFloor::Local => client.floor_items.push(item), - ItemFloor::Shared => room.floor_items.push(item), - } - } + warn!("character {:?} could not pick up item: {:?}", client.character.id, err); Ok(Box::new(None.into_iter())) - } + }, } } diff --git a/src/ship/packet/handler/lobby.rs b/src/ship/packet/handler/lobby.rs index e488695..eace260 100644 --- a/src/ship/packet/handler/lobby.rs +++ b/src/ship/packet/handler/lobby.rs @@ -4,24 +4,29 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms}; use crate::ship::character::{FullCharacterBytesBuilder}; use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationError}; +//use crate::ship::items::; use crate::ship::packet; +use crate::ship::items::ItemManager; // this function needs a better home pub fn block_selected(id: ClientId, - pkt: &MenuSelect, - clients: &mut Clients, - level_table: &CharacterLevelTable) - -> Result, ShipError> { + pkt: &MenuSelect, + clients: &mut Clients, + item_manager: &ItemManager, + level_table: &CharacterLevelTable) + -> Result, ShipError> { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; client.block = pkt.item as u32; let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); + let inventory = item_manager.get_character_inventory(&client.character).unwrap(); + let fc = FullCharacterBytesBuilder::new() .character(&client.character) .stats(&stats) .level(level) - .inventory(&client.inventory) + .inventory(&inventory) .key_config(&client.settings.settings.key_config) .joystick_config(&client.settings.settings.joystick_config) .symbol_chat(&client.settings.settings.symbol_chats) @@ -41,11 +46,12 @@ pub fn send_player_to_lobby(id: ClientId, _pkt: &CharData, client_location: &mut ClientLocation, clients: &Clients, + item_manager: &ItemManager, level_table: &CharacterLevelTable) -> Result, ShipError> { let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).map_err(|_| ShipError::TooManyClients)?; - let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, level_table)?; - let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, level_table)?; + let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?; + let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?; let neighbors = client_location.get_client_neighbors(id).unwrap(); Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] .into_iter() @@ -57,9 +63,11 @@ pub fn change_lobby(id: ClientId, requested_lobby: u32, client_location: &mut ClientLocation, clients: &Clients, + item_manager: &mut ItemManager, level_table: &CharacterLevelTable, ship_rooms: &mut Rooms) -> Result, ShipError> { + let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let prev_area = client_location.get_area(id).map_err(|err| -> ClientLocationError {err.into()})?; match prev_area { RoomLobby::Lobby(old_lobby) => { @@ -71,6 +79,7 @@ pub fn change_lobby(id: ClientId, if client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError {err.into()})?.len() == 0 { ship_rooms[old_room.0] = None; } + item_manager.remove_character_from_room(&client.character); }, } let leave_lobby = packet::builder::lobby::remove_from_lobby(id, client_location)?; @@ -87,8 +96,8 @@ pub fn change_lobby(id: ClientId, } } } - let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, level_table)?; - let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, level_table)?; + let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?; + let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?; let neighbors = client_location.get_client_neighbors(id).unwrap(); Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] .into_iter() diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 003ac3e..1d7f6b5 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -1,8 +1,12 @@ use log::warn; +use libpso::packet::ship::*; use libpso::packet::messages::*; +use crate::entity::gateway::EntityGateway; use crate::common::serverstate::ClientId; -use crate::ship::ship::{SendShipPacket, Rooms}; -use crate::ship::location::{ClientLocation, RoomLobby}; +use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients}; +use crate::ship::location::{ClientLocation, ClientLocationError, RoomLobby}; +use crate::ship::map::{MapArea}; +use crate::ship::items::{ItemManager, ClientItemId}; pub fn request_exp(id: ClientId, request_exp: &RequestExp, @@ -19,3 +23,31 @@ pub fn request_exp(id: ClientId, }; Box::new(None.into_iter()) } + +pub fn player_drop_item(id: ClientId, + player_drop_item: &PlayerDropItem2, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + rooms: &mut Rooms, + item_manager: &mut ItemManager) + -> Result + Send>, ShipError> +where + EG: EntityGateway +{ + let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; + let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?; + let 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 area = MapArea::from_value(&room.mode.episode(), player_drop_item.area as u32)?; + let item = item_manager.get_inventory_item_by_id(&client.character, ClientItemId(player_drop_item.item_id))?; + item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, item, (area, player_drop_item.x, player_drop_item.y, player_drop_item.z))?; + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + let pdi = player_drop_item.clone(); + Ok(Box::new(clients_in_area.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerDropItem2(pdi.clone())))) + }))) +} diff --git a/src/ship/packet/handler/room.rs b/src/ship/packet/handler/room.rs index 04d439e..696dbb9 100644 --- a/src/ship/packet/handler/room.rs +++ b/src/ship/packet/handler/room.rs @@ -5,11 +5,13 @@ use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients}; use crate::ship::location::{ClientLocation, RoomId, RoomLobby, ClientLocationError}; use crate::ship::packet::builder; use crate::ship::room; +use crate::ship::items::ItemManager; pub fn create_room(id: ClientId, create_room: &CreateRoom, client_location: &mut ClientLocation, clients: &mut Clients, + item_manager: &mut ItemManager, rooms: &mut Rooms) -> Result + Send>, ShipError> { let area = client_location.get_area(id).unwrap(); @@ -21,6 +23,8 @@ pub fn create_room(id: ClientId, let mut room = room::RoomState::from_create_room(create_room, client.character.section_id).unwrap(); room.bursting = true; + item_manager.add_character_to_room(room_id, &client.character, area_client); + let join_room = builder::room::join_room(id, clients, client_location, room_id, &room)?; rooms[room_id.0] = Some(room); @@ -54,6 +58,7 @@ pub fn join_room(id: ClientId, pkt: &MenuSelect, client_location: &mut ClientLocation, clients: &mut Clients, + item_manager: &mut ItemManager, level_table: &CharacterLevelTable, rooms: &mut Rooms) -> Result + Send>, ShipError> { @@ -73,11 +78,13 @@ pub fn join_room(id: ClientId, let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; let leader = client_location.get_room_leader(room_id).map_err(|err| -> ClientLocationError { err.into() })?; let join_room = builder::room::join_room(id, clients, client_location, room_id, &room)?; - let add_to = builder::room::add_to_room(id, &client, &area_client, &leader, level_table, room_id)?; + let add_to = builder::room::add_to_room(id, &client, &area_client, &leader, item_manager, level_table, room_id)?; let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap(); room.bursting = true; + item_manager.add_character_to_room(room_id, &client.character, area_client); + let mut result: Box + Send> = Box::new( vec![(id, SendShipPacket::JoinRoom(join_room))] .into_iter() diff --git a/src/ship/room.rs b/src/ship/room.rs index ba998ab..7f8864c 100644 --- a/src/ship/room.rs +++ b/src/ship/room.rs @@ -1,11 +1,9 @@ -#![allow(dead_code)] use std::convert::{From, Into, TryFrom, TryInto}; use rand::Rng; use crate::ship::map::Maps; use crate::ship::drops::DropTable; use crate::entity::character::SectionID; -use crate::ship::items::FloorItem; #[derive(Debug)] pub enum RoomCreationError { @@ -147,24 +145,16 @@ pub struct RoomState { pub mode: RoomMode, pub name: String, pub password: [u16; 16], - //pub maps: [u32; 0x20], pub maps: Maps, pub drop_table: Box>, pub section_id: SectionID, pub random_seed: u32, pub bursting: bool, - pub floor_items: Vec, // items on ground // enemy info } impl RoomState { - /*fn new(mode: RoomMode) -> Room { - Room { - mode: mode, - } - }*/ - pub fn get_flags_for_room_list(&self) -> u8 { let mut flags = 0u8; @@ -233,11 +223,6 @@ impl RoomState { section_id: section_id, drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)), bursting: false, - floor_items: Vec::new(), }) } - - pub fn map_headers(&self) -> [u32; 0x20] { - self.maps.map_headers() - } } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 7672c17..086cefb 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -25,7 +25,7 @@ use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocation use crate::ship::items; use crate::ship::room; -use crate::ship::map::MapsError; +use crate::ship::map::{MapsError, MapAreaError}; use crate::ship::packet::handler; pub const SHIP_PORT: u16 = 23423; @@ -41,11 +41,14 @@ pub enum ShipError { TooManyClients, ClientLocationError(#[from] ClientLocationError), MapsError(#[from] MapsError), + MapAreaError(#[from] MapAreaError), InvalidRoom(u32), MonsterAlreadyDroppedItem(ClientId, u16), SliceError(#[from] std::array::TryFromSliceError), ItemError, // TODO: refine this PickUpInvalidItemId(u32), + DropInvalidItemId(u32), + ItemManagerError(#[from] items::ItemManagerError) } #[derive(Debug)] @@ -158,22 +161,16 @@ pub struct ClientState { pub character: CharacterEntity, session: Session, //guildcard: GuildCard, - pub inventory: items::CharacterInventory, - //bank: Bank, - pub floor_items: Vec, pub block: u32, } impl ClientState { - pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, inventory: items::CharacterInventory, /*bank: Bank,*/ session: Session) -> ClientState { + pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState { ClientState { user: user, settings: settings, character: character, session: session, - inventory: inventory, - //bank: bank, - floor_items: Vec::new(), block: 1, } } @@ -187,7 +184,7 @@ pub struct ShipServerState { level_table: CharacterLevelTable, name: String, rooms: Rooms, - item_database: items::ItemManager, + item_manager: items::ItemManager, } impl ShipServerState { @@ -199,7 +196,7 @@ impl ShipServerState { level_table: CharacterLevelTable::new(), name: "Sona-Nyl".into(), rooms: [None; MAX_ROOMS], - item_database: items::ItemManager::new(), + item_manager: items::ItemManager::new(), } } @@ -208,6 +205,9 @@ impl ShipServerState { GameMessage::RequestExp(request_exp) => { handler::message::request_exp(id, request_exp, &self.client_location, &self.rooms) }, + GameMessage::PlayerDropItem2(player_drop_item) => { + handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_manager).unwrap() + }, _ => { let cmsg = msg.clone(); Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter() @@ -225,10 +225,10 @@ impl ShipServerState { 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() + 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_manager).unwrap() }, GameMessage::PickupItem(pickup_item) => { - handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.rooms, &mut self.item_database).unwrap() + handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).unwrap() }, _ => { let cmsg = msg.clone(); @@ -265,12 +265,12 @@ impl ServerState for ShipServerState { -> Result + Send>, ShipError> { Ok(match pkt { RecvShipPacket::Login(login) => { - Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_database, &self.name)?.into_iter().map(move |pkt| (id, pkt))) + Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.name)?.into_iter().map(move |pkt| (id, pkt))) }, RecvShipPacket::MenuSelect(menuselect) => { match menuselect.menu { - BLOCK_MENU_ID => Box::new(handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.level_table)?.into_iter().map(move |pkt| (id, pkt))), - ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.client_location, &mut self.clients, &self.level_table, &mut self.rooms)?, + BLOCK_MENU_ID => Box::new(handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_manager, &self.level_table)?.into_iter().map(move |pkt| (id, pkt))), + ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)?, _ => unreachable!(), } }, @@ -282,14 +282,14 @@ impl ServerState for ShipServerState { menu: room_password_req.menu, item: room_password_req.item, }; - handler::room::join_room(id, &menuselect, &mut self.client_location, &mut self.clients, &self.level_table, &mut self.rooms)? + handler::room::join_room(id, &menuselect, &mut self.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)? } else { Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))].into_iter()) } }, RecvShipPacket::CharData(chardata) => { - Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut self.client_location, &self.clients, &self.level_table)?.into_iter()) + Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut self.client_location, &self.clients, &self.item_manager, &self.level_table)?.into_iter()) }, RecvShipPacket::Message(msg) => { self.message(id, msg) @@ -301,7 +301,7 @@ impl ServerState for ShipServerState { Box::new(handler::communication::player_chat(id, msg, &self.client_location, &self.clients)?.into_iter()) }, RecvShipPacket::CreateRoom(create_room) => { - handler::room::create_room(id, create_room, &mut self.client_location, &mut self.clients, &mut self.rooms)? + handler::room::create_room(id, create_room, &mut self.client_location, &mut self.clients, &mut self.item_manager, &mut self.rooms)? }, RecvShipPacket::RoomNameRequest(_req) => { handler::room::room_name_request(id, &self.client_location, &self.rooms) @@ -329,14 +329,15 @@ impl ServerState for ShipServerState { handler::room::done_bursting(id, &self.client_location, &mut self.rooms) }, RecvShipPacket::LobbySelect(pkt) => { - Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut self.client_location, &self.clients, &self.level_table, &mut self.rooms)?.into_iter()) + Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut self.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)?.into_iter()) } }) } fn on_disconnect(&mut self, id: ClientId) -> Vec<(ClientId, SendShipPacket)> { // TODO: don't unwrap! - let client = self.client_location.get_local_client(id).unwrap(); + let client = self.clients.get(&id).unwrap(); + let area_client = self.client_location.get_local_client(id).unwrap(); let neighbors = self.client_location.get_client_neighbors(id).unwrap(); let pkt = match self.client_location.get_area(id).unwrap() { @@ -345,15 +346,16 @@ impl ServerState for ShipServerState { self.rooms[room.0] = None; } let leader = self.client_location.get_room_leader(room).unwrap(); - SendShipPacket::LeaveRoom(LeaveRoom::new(client.local_client.id(), leader.local_client.id())) + SendShipPacket::LeaveRoom(LeaveRoom::new(area_client.local_client.id(), leader.local_client.id())) }, RoomLobby::Lobby(lobby) => { let leader = self.client_location.get_lobby_leader(lobby).unwrap(); - SendShipPacket::LeaveLobby(LeaveLobby::new(client.local_client.id(), leader.local_client.id())) + SendShipPacket::LeaveLobby(LeaveLobby::new(area_client.local_client.id(), leader.local_client.id())) } }; self.client_location.remove_client_from_area(id); + self.item_manager.remove_character_from_room(&client.character); neighbors.into_iter().map(|n| { (n.client, pkt.clone())