diff --git a/src/ship/items.rs b/src/ship/items.rs index 86f3d66..eb49ae1 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -7,6 +7,7 @@ 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::ship::ItemDropLocation; use crate::ship::drops::{ItemDrop, ItemDropType}; use crate::ship::location::{AreaClient, RoomId}; @@ -145,6 +146,8 @@ pub enum ItemManagerError { CouldNotAddToInventory(FloorItem), //ItemBelongsToOtherPlayer, Idunnoman, + CouldNotSplitItem(InventoryItem), + CouldNotDropMeseta, } pub struct ItemManager { @@ -152,11 +155,10 @@ pub struct ItemManager { character_inventory: HashMap>, character_floor: HashMap>, - character_item_id_counter: HashMap, character_room: HashMap, room_floor: HashMap>, - room_item_id_counter: HashMap, + room_item_id_counter: HashMap ClientItemId + Send>>, } impl ItemManager { @@ -165,7 +167,6 @@ impl ItemManager { 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(), @@ -177,12 +178,6 @@ impl ItemManager { 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) - } - // TODO: Result pub fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) { let items = entity_gateway.get_items_by_character(&character); @@ -234,7 +229,12 @@ impl ItemManager { 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); + + let mut inc = 0xF0000000; + self.room_item_id_counter.entry(room_id).or_insert(Box::new(move || { + inc += 1; + ClientItemId(inc) + })); } pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result { @@ -406,8 +406,8 @@ impl ItemManager { FloorItemType::Meseta(m) => ActiveItemEntityId::Meseta(m.clone()), }; - 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 room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); let floor_item = FloorItem { entity_id: entity_id, @@ -488,4 +488,88 @@ impl ItemManager { shared_floor.push(room_floor_item); Ok(()) } + + pub fn player_drops_meseta_on_shared_floor(&mut self, + entity_gateway: &mut EG, + character: &mut CharacterEntity, + drop_location: ItemDropLocation, + amount: u32) + -> Result { + 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 character.meseta <= amount { + return Err(ItemManagerError::CouldNotDropMeseta) + } + character.meseta -= amount; + entity_gateway.save_character(&character); + + let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); + let floor_item = FloorItem { + entity_id: ActiveItemEntityId::Meseta(Meseta(amount)), + item_id: item_id, + item: FloorItemType::Meseta(Meseta(amount)), + map_area: drop_location.map_area, + x: drop_location.x, + y: 0.0, + z: drop_location.z, + }; + + shared_floor.push(floor_item.clone()); + Ok(floor_item) + } + + pub fn player_drops_partial_stack_on_shared_floor(&mut self, + entity_gateway: &mut EG, + character: &CharacterEntity, + inventory_item: InventoryItem, + drop_location: ItemDropLocation, + amount: usize) + -> Result { + 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))?; + + let item_to_split = inventory.iter_mut() + .find(|i| i.item_id == inventory_item.item_id) + .ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id))?; + + if let (ActiveItemEntityId::Stacked(ref mut entity_ids), HeldItemType::Stacked(tool, ref mut tool_amount)) = (&mut item_to_split.entity_id, &mut item_to_split.item) { + if entity_ids.len() <= amount || *tool_amount <= amount { + return Err(ItemManagerError::CouldNotSplitItem(inventory_item)); + } + + *tool_amount -= amount; + + let dropped_entities = entity_ids.drain(..amount).collect::>(); + + dropped_entities.iter().for_each(|entity_id| { + entity_gateway.save_item(&ItemEntity { + id: *entity_id, + item: ItemDetail::Tool(*tool), + location: ItemLocation::SharedFloor { + map_area: drop_location.map_area, + x: drop_location.x, + y: 0.0, + z: drop_location.z, + } + }) + }); + + let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); + let floor_item = FloorItem { + entity_id: ActiveItemEntityId::Stacked(dropped_entities), + item_id: item_id, + item: FloorItemType::Stacked(*tool, amount), + map_area: drop_location.map_area, + x: drop_location.x, + y: 0.0, + z: drop_location.z, + }; + shared_floor.push(floor_item.clone()); + Ok(floor_item) + } + else { + Err(ItemManagerError::CouldNotSplitItem(inventory_item)) + } + } } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index 2f84cd0..30202a7 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -46,3 +46,20 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu item_id: item.item_id.0, }) } + +pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result { + let item_bytes = item.item.as_client_bytes(); + Ok(DropSplitStack { + client: area_client.local_client.id(), + target: 0, + variety: 0, + unknown1: 0, + map_area: item.map_area.area_value(), + x: item.x, + z: item.z, + item_bytes: item_bytes[0..12].try_into()?, + item_id: item.item_id.0, + item_bytes2: item_bytes[12..16].try_into()?, + unknown2: 0, + }) +} diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 40f8262..b950511 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -3,10 +3,11 @@ use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::entity::gateway::EntityGateway; use crate::common::serverstate::ClientId; -use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients}; +use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation}; use crate::ship::location::{ClientLocation, ClientLocationError, RoomLobby}; use crate::ship::map::{MapArea}; use crate::ship::items::{ItemManager, ClientItemId}; +use crate::ship::packet::builder; pub fn request_exp(id: ClientId, request_exp: &RequestExp, @@ -51,3 +52,73 @@ where (c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerDropItem(pdi.clone())))) }))) } + +pub fn drop_coordinates(id: ClientId, + drop_coordinates: &DropCoordinates, + client_location: &ClientLocation, + clients: &mut Clients, + rooms: &Rooms) + -> Result + Send>, ShipError> +{ + 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(room_id.0) + .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))? + .as_ref() + .ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?; + + client.item_drop_location = Some(ItemDropLocation { + map_area: MapArea::from_value(&room.mode.episode(), drop_coordinates.map_area)?, + x: drop_coordinates.x, + z: drop_coordinates.z, + item_id: ClientItemId(drop_coordinates.item_id), + }); + + Ok(Box::new(None.into_iter())) +} + +pub fn split_item_stack(id: ClientId, + split_item_stack: &PlayerSplitItemStack, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + item_manager: &mut ItemManager) + -> Result + Send>, ShipError> +where + EG: EntityGateway +{ + 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 drop_location = client.item_drop_location.ok_or(ShipError::ItemDropLocationNotSet)?; + + if drop_location.item_id.0 != split_item_stack.item_id { + return Err(ShipError::DropInvalidItemId(split_item_stack.item_id)); + } + + if split_item_stack.item_id == 0xFFFFFFFF { + let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, split_item_stack.amount as u32)?; + + let dropped_meseta_pkt = builder::message::drop_split_stack(area_client, &dropped_meseta)?; + client.item_drop_location = None; + + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + Ok(Box::new(clients_in_area.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_meseta_pkt.clone())))) + }))) + } + else { + let item_to_split = item_manager.get_inventory_item_by_id(&client.character, drop_location.item_id)?; + let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, item_to_split, drop_location, split_item_stack.amount as usize)?; + + let dropped_item_pkt = builder::message::drop_split_stack(area_client, &dropped_item)?; + client.item_drop_location = None; + + let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; + Ok(Box::new(clients_in_area.into_iter() + .map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::DropSplitStack(dropped_item_pkt.clone())))) + }))) + } +} diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 45b8d2f..05a2c28 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, MapAreaError}; +use crate::ship::map::{MapsError, MapAreaError, MapArea}; use crate::ship::packet::handler; pub const SHIP_PORT: u16 = 23423; @@ -48,7 +48,8 @@ pub enum ShipError { ItemError, // TODO: refine this PickUpInvalidItemId(u32), DropInvalidItemId(u32), - ItemManagerError(#[from] items::ItemManagerError) + ItemManagerError(#[from] items::ItemManagerError), + ItemDropLocationNotSet, } #[derive(Debug)] @@ -155,6 +156,14 @@ impl SendServerPacket for SendShipPacket { } } +#[derive(Debug, Clone, Copy)] +pub struct ItemDropLocation { + pub map_area: MapArea, + pub x: f32, + pub z: f32, + pub item_id: items::ClientItemId, +} + pub struct ClientState { pub user: UserAccountEntity, pub settings: UserSettingsEntity, @@ -162,6 +171,7 @@ pub struct ClientState { session: Session, //guildcard: GuildCard, pub block: u32, + pub item_drop_location: Option, } impl ClientState { @@ -172,6 +182,7 @@ impl ClientState { character: character, session: session, block: 1, + item_drop_location: None, } } } @@ -208,6 +219,12 @@ impl ShipServerState { GameMessage::PlayerDropItem(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() }, + GameMessage::DropCoordinates(drop_coordinates) => { + handler::message::drop_coordinates(id, drop_coordinates, &self.client_location, &mut self.clients, &self.rooms).unwrap() + }, + GameMessage::PlayerSplitItemStack(split_item_stack) => { + handler::message::split_item_stack(id, split_item_stack, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).unwrap() + }, _ => { let cmsg = msg.clone(); Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter()