Browse Source

drop partial stacks of items/meseta

pbs
jake 5 years ago
parent
commit
25f342b510
  1. 108
      src/ship/items.rs
  2. 17
      src/ship/packet/builder/message.rs
  3. 73
      src/ship/packet/handler/message.rs
  4. 21
      src/ship/ship.rs

108
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::{Meseta, NewItemEntity};
use crate::entity::item::tool::Tool; use crate::entity::item::tool::Tool;
use crate::ship::map::MapArea; use crate::ship::map::MapArea;
use crate::ship::ship::ItemDropLocation;
use crate::ship::drops::{ItemDrop, ItemDropType}; use crate::ship::drops::{ItemDrop, ItemDropType};
use crate::ship::location::{AreaClient, RoomId}; use crate::ship::location::{AreaClient, RoomId};
@ -145,6 +146,8 @@ pub enum ItemManagerError {
CouldNotAddToInventory(FloorItem), CouldNotAddToInventory(FloorItem),
//ItemBelongsToOtherPlayer, //ItemBelongsToOtherPlayer,
Idunnoman, Idunnoman,
CouldNotSplitItem(InventoryItem),
CouldNotDropMeseta,
} }
pub struct ItemManager { pub struct ItemManager {
@ -152,11 +155,10 @@ pub struct ItemManager {
character_inventory: HashMap<CharacterEntityId, Vec<InventoryItem>>, character_inventory: HashMap<CharacterEntityId, Vec<InventoryItem>>,
character_floor: HashMap<CharacterEntityId, Vec<FloorItem>>, character_floor: HashMap<CharacterEntityId, Vec<FloorItem>>,
character_item_id_counter: HashMap<CharacterEntityId, u32>,
character_room: HashMap<CharacterEntityId, RoomId>, character_room: HashMap<CharacterEntityId, RoomId>,
room_floor: HashMap<RoomId, Vec<FloorItem>>, room_floor: HashMap<RoomId, Vec<FloorItem>>,
room_item_id_counter: HashMap<RoomId, u32>,
room_item_id_counter: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>,
} }
impl ItemManager { impl ItemManager {
@ -165,7 +167,6 @@ impl ItemManager {
id_counter: 0, id_counter: 0,
character_inventory: HashMap::new(), character_inventory: HashMap::new(),
character_floor: HashMap::new(), character_floor: HashMap::new(),
character_item_id_counter: HashMap::new(),
character_room: HashMap::new(), character_room: HashMap::new(),
room_floor: HashMap::new(), room_floor: HashMap::new(),
room_item_id_counter: HashMap::new(), room_item_id_counter: HashMap::new(),
@ -177,12 +178,6 @@ impl ItemManager {
ClientItemId(self.id_counter) 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 // TODO: Result
pub fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) { pub fn load_character<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) {
let items = entity_gateway.get_items_by_character(&character); 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_room.insert(character.id, room_id);
self.character_floor.insert(character.id, Vec::new()); self.character_floor.insert(character.id, Vec::new());
self.room_floor.entry(room_id).or_insert(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<CharacterInventory, ItemManagerError> { pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<CharacterInventory, ItemManagerError> {
@ -406,8 +406,8 @@ impl ItemManager {
FloorItemType::Meseta(m) => ActiveItemEntityId::Meseta(m.clone()), 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 { let floor_item = FloorItem {
entity_id: entity_id, entity_id: entity_id,
@ -488,4 +488,88 @@ impl ItemManager {
shared_floor.push(room_floor_item); shared_floor.push(room_floor_item);
Ok(()) Ok(())
} }
pub fn player_drops_meseta_on_shared_floor<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &mut CharacterEntity,
drop_location: ItemDropLocation,
amount: u32)
-> Result<FloorItem, ItemManagerError> {
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<EG: EntityGateway>(&mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
inventory_item: InventoryItem,
drop_location: ItemDropLocation,
amount: usize)
-> Result<FloorItem, 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))?;
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::<Vec<_>>();
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))
}
}
} }

17
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, item_id: item.item_id.0,
}) })
} }
pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
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,
})
}

73
src/ship/packet/handler/message.rs

@ -3,10 +3,11 @@ use libpso::packet::ship::*;
use libpso::packet::messages::*; use libpso::packet::messages::*;
use crate::entity::gateway::EntityGateway; use crate::entity::gateway::EntityGateway;
use crate::common::serverstate::ClientId; 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::location::{ClientLocation, ClientLocationError, RoomLobby};
use crate::ship::map::{MapArea}; use crate::ship::map::{MapArea};
use crate::ship::items::{ItemManager, ClientItemId}; use crate::ship::items::{ItemManager, ClientItemId};
use crate::ship::packet::builder;
pub fn request_exp(id: ClientId, pub fn request_exp(id: ClientId,
request_exp: &RequestExp, request_exp: &RequestExp,
@ -51,3 +52,73 @@ where
(c.client, SendShipPacket::Message(Message::new(GameMessage::PlayerDropItem(pdi.clone())))) (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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
split_item_stack: &PlayerSplitItemStack,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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()))))
})))
}
}

21
src/ship/ship.rs

@ -25,7 +25,7 @@ use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocation
use crate::ship::items; use crate::ship::items;
use crate::ship::room; use crate::ship::room;
use crate::ship::map::{MapsError, MapAreaError};
use crate::ship::map::{MapsError, MapAreaError, MapArea};
use crate::ship::packet::handler; use crate::ship::packet::handler;
pub const SHIP_PORT: u16 = 23423; pub const SHIP_PORT: u16 = 23423;
@ -48,7 +48,8 @@ pub enum ShipError {
ItemError, // TODO: refine this ItemError, // TODO: refine this
PickUpInvalidItemId(u32), PickUpInvalidItemId(u32),
DropInvalidItemId(u32), DropInvalidItemId(u32),
ItemManagerError(#[from] items::ItemManagerError)
ItemManagerError(#[from] items::ItemManagerError),
ItemDropLocationNotSet,
} }
#[derive(Debug)] #[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 struct ClientState {
pub user: UserAccountEntity, pub user: UserAccountEntity,
pub settings: UserSettingsEntity, pub settings: UserSettingsEntity,
@ -162,6 +171,7 @@ pub struct ClientState {
session: Session, session: Session,
//guildcard: GuildCard, //guildcard: GuildCard,
pub block: u32, pub block: u32,
pub item_drop_location: Option<ItemDropLocation>,
} }
impl ClientState { impl ClientState {
@ -172,6 +182,7 @@ impl ClientState {
character: character, character: character,
session: session, session: session,
block: 1, block: 1,
item_drop_location: None,
} }
} }
} }
@ -208,6 +219,12 @@ impl<EG: EntityGateway> ShipServerState<EG> {
GameMessage::PlayerDropItem(player_drop_item) => { 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() 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(); let cmsg = msg.clone();
Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter() Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter()

Loading…
Cancel
Save