From 9a721e0980887f99df7d82d0362d38d3ac1cb25b Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 5 May 2020 21:53:04 -0600 Subject: [PATCH] item structure overhaul also, item pickup --- src/entity/character.rs | 3 + src/entity/gateway/inmemory.rs | 1 + src/entity/item/mod.rs | 13 +- src/login/character.rs | 10 +- src/main.rs | 4 +- src/ship/character.rs | 6 +- src/ship/items.rs | 516 ++++++++++------------ src/ship/packet/builder/message.rs | 31 +- src/ship/packet/handler/auth.rs | 6 +- src/ship/packet/handler/direct_message.rs | 102 ++++- src/ship/room.rs | 3 + src/ship/ship.rs | 14 +- 12 files changed, 392 insertions(+), 317 deletions(-) diff --git a/src/entity/character.rs b/src/entity/character.rs index e2e44ed..a9d868b 100644 --- a/src/entity/character.rs +++ b/src/entity/character.rs @@ -247,6 +247,7 @@ pub struct NewCharacterEntity { pub guildcard: CharacterGuildCard, pub tech_menu: CharacterTechMenu, + pub meseta: u32, } impl NewCharacterEntity { @@ -264,6 +265,7 @@ impl NewCharacterEntity { info_board: CharacterInfoboard::new(), guildcard: CharacterGuildCard::default(), tech_menu: CharacterTechMenu::new(), + meseta: 0, } } } @@ -287,4 +289,5 @@ pub struct CharacterEntity { pub guildcard: CharacterGuildCard, pub tech_menu: CharacterTechMenu, + pub meseta: u32, } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index d4365a7..15c9bb2 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -120,6 +120,7 @@ impl EntityGateway for InMemoryGateway { info_board: character.info_board, guildcard: character.guildcard, tech_menu: character.tech_menu, + meseta: character.meseta, }; characters.insert(new_character.id, new_character.clone()); Some(new_character) diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 9b31172..04b64e0 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -21,14 +21,21 @@ pub struct BankName(String); pub enum ItemLocation { Inventory { character_id: CharacterEntityId, - index: usize, + slot: usize, equipped: bool, }, Bank { character_id: CharacterEntityId, slot: BankName, }, - Floor { + LocalFloor { + character_id: CharacterEntityId, + map_area: MapArea, + x: f32, + y: f32, + z: f32, + }, + SharedFloor { map_area: MapArea, x: f32, y: f32, @@ -44,7 +51,7 @@ pub enum ItemLocation { */ } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Meseta(pub u32); impl Meseta { diff --git a/src/login/character.rs b/src/login/character.rs index 8336fcb..2c83a25 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -220,7 +220,7 @@ fn new_character(entity_gateway: &mut EG, user: &UserAccountE }), location: ItemLocation::Inventory { character_id: character.id, - index: 0, + slot: 0, equipped: true, }}); @@ -235,7 +235,7 @@ fn new_character(entity_gateway: &mut EG, user: &UserAccountE }), location: ItemLocation::Inventory { character_id: character.id, - index: 1, + slot: 1, equipped: true, }}); @@ -255,7 +255,7 @@ fn new_character(entity_gateway: &mut EG, user: &UserAccountE }), location: ItemLocation::Inventory { character_id: character.id, - index: 2, + slot: 2, equipped: true, }}); @@ -268,7 +268,7 @@ fn new_character(entity_gateway: &mut EG, user: &UserAccountE }), location: ItemLocation::Inventory { character_id: character.id, - index: 3, + slot: 3, equipped: false, }}); entity_gateway.create_item( @@ -279,7 +279,7 @@ fn new_character(entity_gateway: &mut EG, user: &UserAccountE }), location: ItemLocation::Inventory { character_id: character.id, - index: 4, + slot: 4, equipped: false, }}); } diff --git a/src/main.rs b/src/main.rs index f4fefe1..f588177 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,7 @@ fn main() { entity_gateway.create_character(character); let mut character = NewCharacterEntity::new(fake_user.id); character.slot = 2; - character.name = "\tE12345678".into(); + character.name = "no progress".into(); character.exp = 80000000; let character = entity_gateway.create_character(character).unwrap(); @@ -96,7 +96,7 @@ fn main() { ), location: ItemLocation::Inventory { character_id: character.id, - index: 0, + slot: 0, equipped: true, } }); diff --git a/src/ship/character.rs b/src/ship/character.rs index f0771c2..fef94ea 100644 --- a/src/ship/character.rs +++ b/src/ship/character.rs @@ -1,7 +1,7 @@ use libpso::character::character; use crate::common::leveltable::CharacterStats; use crate::entity::character::CharacterEntity; -use crate::ship::items::ActiveInventory; +use crate::ship::items::CharacterInventory; // TODO: exp pub struct CharacterBytesBuilder<'a> { @@ -79,7 +79,7 @@ pub struct FullCharacterBytesBuilder<'a> { character: Option<&'a CharacterEntity>, stats: Option<&'a CharacterStats>, level: Option, - inventory: Option<&'a ActiveInventory>, + inventory: Option<&'a CharacterInventory>, key_config: Option<&'a [u8; 0x16C]>, joystick_config: Option<&'a [u8; 0x38]>, symbol_chat: Option<&'a [u8; 1248]>, @@ -122,7 +122,7 @@ impl<'a> FullCharacterBytesBuilder<'a> { } } - pub fn inventory(self, inventory: &'a ActiveInventory) -> FullCharacterBytesBuilder<'a> { + pub fn inventory(self, inventory: &'a CharacterInventory) -> FullCharacterBytesBuilder<'a> { FullCharacterBytesBuilder { inventory: Some(inventory), ..self diff --git a/src/ship/items.rs b/src/ship/items.rs index 53a0257..568c100 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -1,37 +1,37 @@ #![allow(dead_code)] -use std::collections::HashMap; -use libpso::character::character::InventoryItem; +use std::collections::{HashMap, BTreeMap}; +use libpso::character::character;//::InventoryItem; use crate::entity::gateway::EntityGateway; use crate::entity::character::CharacterEntity; -use crate::entity::item::{ItemEntity, ItemDetail, ItemLocation}; +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::ShipError; +use crate::ship::ship::ClientState; -#[derive(Debug, PartialEq)] -enum ItemInstance { - Individual(ItemEntity), - Stacked(Vec), - Meseta(Meseta), -} - #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ActiveItemId(pub u32); +#[derive(Debug)] +enum ActiveItemEntityId { + Individual(ItemEntityId), + Stacked(Vec), + Meseta(Meseta), +} #[derive(Debug)] -pub struct ActiveItem { - pub id: ActiveItemId, - item: ItemInstance, +enum HeldItemType { + Individual(ItemDetail), + Stacked(Tool, usize), } -impl ActiveItem { +impl HeldItemType { pub fn as_client_bytes(&self) -> [u8; 16] { - match &self.item { - ItemInstance::Individual(i) => { - match &i.item { + match self { + HeldItemType::Individual(item) => { + match &item { ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Armor(a) => a.as_bytes(), ItemDetail::Shield(s) => s.as_bytes(), @@ -41,328 +41,278 @@ impl ActiveItem { ItemDetail::Mag(m) => m.as_bytes(), } }, - ItemInstance::Stacked(i) => { - let len = i.len(); - match &i[0].item { - ItemDetail::Tool(t) => t.as_stacked_bytes(len), - _ => panic!(), - } + HeldItemType::Stacked(tool, count) => { + tool.as_stacked_bytes(*count) }, - ItemInstance::Meseta(m) => { - m.as_bytes() - } } } } -pub struct ActiveInventory(Vec); - -impl ActiveInventory { - pub fn as_client_inventory_items(&self) -> [InventoryItem; 30] { - self.0.iter() - .enumerate() - .fold([InventoryItem::default(); 30], |mut inventory, (index, item)| { - let bytes = item.as_client_bytes(); - inventory[index].data1.copy_from_slice(&bytes[0..12]); - inventory[index].data2.copy_from_slice(&bytes[12..16]); - inventory[index].item_id = item.id.0; +#[derive(Debug)] +pub struct InventoryItem { + id: ActiveItemId, + item: HeldItemType, + //slot: usize, + equipped: bool, +} - // does this do anything? - inventory[index].equipped = match item.item { - ItemInstance::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 1, - _ => 0, - }; - // because this actually equips the item - inventory[index].flags |= match item.item { - ItemInstance::Individual(ItemEntity {location: ItemLocation::Inventory{ equipped: true, ..}, ..}) => 8, - _ => 0, - }; - inventory - }) - } +#[derive(Debug)] +pub struct BankItem { + id: ActiveItemId, + item: HeldItemType, +} - pub fn count(&self) -> usize { - self.0.len() - } +#[derive(Debug)] +pub enum FloorItemType { + Individual(ItemDetail), + Stacked(Tool, usize), + Meseta(Meseta), } -fn inventory_item_index(item: &ItemInstance) -> usize { - match item { - ItemInstance::Individual(i) => { - match i.location { - ItemLocation::Inventory{index, ..} => index, - _ => panic!() - } - }, - ItemInstance::Stacked(i) => { - match i[0].location { - ItemLocation::Inventory{index, ..} => index, - _ => panic!() +impl FloorItemType { + pub fn as_client_bytes(&self) -> [u8; 16] { + match self { + FloorItemType::Individual(item) => { + match &item { + ItemDetail::Weapon(w) => w.as_bytes(), + ItemDetail::Armor(a) => a.as_bytes(), + ItemDetail::Shield(s) => s.as_bytes(), + ItemDetail::Unit(u) => u.as_bytes(), + ItemDetail::Tool(t) => t.as_individual_bytes(), + ItemDetail::TechniqueDisk(d) => d.as_bytes(), + ItemDetail::Mag(m) => m.as_bytes(), + } + }, + FloorItemType::Stacked(tool, count) => { + tool.as_stacked_bytes(*count) + }, + FloorItemType::Meseta(m) => { + m.as_bytes() } - }, - _ => panic!(), + } } } -pub struct ActiveItemOnFloor { +#[derive(Debug)] +pub struct FloorItem { + pub id: ActiveItemId, + pub item: FloorItemType, 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(); +#[derive(Debug)] +pub enum InventoryError { +} + +pub struct CharacterInventory(Vec); + +impl CharacterInventory { + pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] { + self.0.iter() + .enumerate() + .fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| { + 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; + // does this do anything? + inventory[slot].equipped = if item.equipped { 1 } else { 0 }; + // because this actually equips the item + inventory[slot].flags |= if item.equipped { 8 } else { 0 }; + inventory + }) + } - for item in items { - stacks.entry(item.item.item_type()).or_insert(Vec::new()).push(item); + pub fn add_item(&mut self, item: InventoryItem) -> Result { + self.0.push(item); + Ok(self.count() - 1) } - stacks.into_iter() - .map(|(_, items)| { - match items[0].item.is_stackable() { - true => { - vec![ItemInstance::Stacked(items)] - }, - false => { - items.into_iter().map(|i| { - ItemInstance::Individual(i) - }).collect() - } - } - }) - .flatten() - .collect() + pub fn count(&self) -> usize { + self.0.len() + } } -struct ActiveBank([Option; 200]); -pub struct ActiveItemDatabase { - id: u32, +#[derive(Debug)] +pub enum ItemManagerError { + EntityGatewayError, + CouldNotAddToInventory(FloorItem), +} + +pub struct ItemManager { + id: usize, + active_to_entity: HashMap, } -impl ActiveItemDatabase { - pub fn new() -> ActiveItemDatabase { - ActiveItemDatabase { +impl ItemManager { + pub fn new() -> ItemManager { + ItemManager { id: 0, + active_to_entity: HashMap::new() } } - fn activate_item(&mut self, item: ItemInstance) -> ActiveItem { + fn next_id(&mut self) -> ActiveItemId { self.id += 1; - ActiveItem { - id: ActiveItemId(self.id), - item: item, - } + ActiveItemId(self.id as u32) } - // TODO: deactivate item - - pub fn get_character_inventory(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> ActiveInventory { + pub fn get_character_inventory(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> CharacterInventory { let items = entity_gateway.get_items_by_character(&character); let inventory_items = items.into_iter() - .filter(|item| { + .filter_map(|item| { match item.location { - ItemLocation::Inventory{..} => true, - _ => false, + ItemLocation::Inventory{slot, equipped, ..} => Some((item.id, item.item, slot, equipped)), + _ => None, + } + }) + .fold(BTreeMap::new(), |mut acc, (id, item, slot, equipped)| { + if item.is_stackable() { + if let ItemDetail::Tool(tool) = item { + let stacked = acc.entry(slot).or_insert((HeldItemType::Stacked(tool, 0), ActiveItemEntityId::Stacked(Vec::new()), false)); + if let HeldItemType::Stacked(_, ref mut item_count) = stacked.0 { + *item_count += 1; + } + if let ActiveItemEntityId::Stacked(ref mut id_list) = stacked.1 { + id_list.push(id); + } + } + } + else { + acc.insert(slot, (HeldItemType::Individual(item), ActiveItemEntityId::Individual(id), equipped)); } - }).collect(); - let mut stacked = stack_items(inventory_items); - stacked.sort_by(|a, b| { - inventory_item_index(a).partial_cmp(&inventory_item_index(b)).unwrap() - }); - 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 + acc + }) + .into_iter() + .map(|(_slot, (held_item, entity_id, equipped))| { + let id = self.next_id(); + self.active_to_entity.insert(id, entity_id); + InventoryItem { + id: id, + item: held_item, + equipped: equipped, + } + }); + CharacterInventory(inventory_items.take(30).collect()) + } + + pub fn drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result { + let item = match item_drop.item { + ItemDropType::Weapon(w) => FloorItemType::Individual(ItemDetail::Weapon(w)), + ItemDropType::Armor(w) => FloorItemType::Individual(ItemDetail::Armor(w)), + ItemDropType::Shield(w) => FloorItemType::Individual(ItemDetail::Shield(w)), + ItemDropType::Unit(w) => FloorItemType::Individual(ItemDetail::Unit(w)), + ItemDropType::TechniqueDisk(w) => FloorItemType::Individual(ItemDetail::TechniqueDisk(w)), + ItemDropType::Mag(w) => FloorItemType::Individual(ItemDetail::Mag(w)), + ItemDropType::Tool(w) => FloorItemType::Individual(ItemDetail::Tool(w)), + //ItemDropType::Tool(t) if t.is_stackable() => FloorItemType::Stacked(t, ), + //ItemDropType::Tool(t) if !t.is_stackable() => FloorItemType::Individual(ItemDetail::Tool(w)), + ItemDropType::Meseta(m) => FloorItemType::Meseta(Meseta(m)) }; - let item_instance = match item_detail { - Some(item) => { - let item_entity = entity_gateway.create_item(NewItemEntity { - item: item, - location: ItemLocation::Floor { + + let active_entity_ids = match &item { + FloorItemType::Individual(i) => { + let entity = entity_gateway.create_item(NewItemEntity { + item: i.clone(), + location: ItemLocation::LocalFloor { + character_id: character.id, 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)? + }).ok_or(ItemManagerError::EntityGatewayError)?; + ActiveItemEntityId::Individual(entity.id) }, - None => { - let meseta = match item_drop.item { - ItemDropType::Meseta(m) => m, - _ => panic!(), - }; - ItemInstance::Meseta(Meseta(meseta)) - } + FloorItemType::Stacked(tool, count) => { + let entities = (0..*count).map(|_| { + entity_gateway.create_item(NewItemEntity { + item: ItemDetail::Tool(*tool), + location: ItemLocation::LocalFloor { + character_id: character.id, + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + } + })}) + .map(|entity| -> Result { + let e = entity.ok_or(ItemManagerError::EntityGatewayError)?; + Ok(e.id) + }); + ActiveItemEntityId::Stacked(entities.collect::, _>>()?) + }, + FloorItemType::Meseta(m) => ActiveItemEntityId::Meseta(m.clone()), }; - let active_item = self.activate_item(item_instance); - Ok(ActiveItemOnFloor { + let id = self.next_id(); + self.active_to_entity.insert(id, active_entity_ids); + Ok(FloorItem { + id: id, + item: item, map_area: item_drop.map_area, x: item_drop.x, y: item_drop.y, z: item_drop.z, - item: active_item, }) } -} -#[cfg(test)] -mod test { - use super::*; - use crate::entity::character::CharacterEntityId; - use crate::entity::item; - use crate::entity::item::{ItemEntity, ItemDetail, ItemEntityId, ItemLocation}; - use crate::entity::item::tool::Tool; - #[test] - fn test_stack_items() { - let item1 = ItemEntity { - id: ItemEntityId(1), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 0, - equipped: false, - }, - item: ItemDetail::Weapon(item::weapon::Weapon { - weapon: item::weapon::WeaponType::Saber, - grind: 0, - special: None, - attrs: [None; 3], - tekked: true, - }) - }; - let item2 = ItemEntity { - id: ItemEntityId(2), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 1, - equipped: false, - }, - item: ItemDetail::Tool(Tool { - tool: item::tool::ToolType::Monofluid, - }) - }; - let item3 = ItemEntity { - id: ItemEntityId(3), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 2, - equipped: false, - }, - item: ItemDetail::Weapon(item::weapon::Weapon { - weapon: item::weapon::WeaponType::Handgun, - grind: 12, - special: None, - attrs: [None; 3], - tekked: true, - }) - }; - let item4 = ItemEntity { - id: ItemEntityId(4), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 1, - equipped: false, - }, - item: ItemDetail::Tool(Tool { - tool: item::tool::ToolType::Monofluid, - }) - }; - let item5 = ItemEntity { - id: ItemEntityId(5), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 1, - equipped: false, - }, - item: ItemDetail::Tool(Tool { - tool: item::tool::ToolType::Monofluid, - }) - }; - let item6 = ItemEntity { - id: ItemEntityId(6), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 3, - equipped: false, - }, - item: ItemDetail::Weapon(item::weapon::Weapon { - weapon: item::weapon::WeaponType::Handgun, - grind: 12, - special: None, - attrs: [None; 3], - tekked: true, - }) - }; - let item7 = ItemEntity { - id: ItemEntityId(7), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 4, - equipped: false, - }, - item: ItemDetail::Tool(Tool { - tool: item::tool::ToolType::Monomate, - }) - }; - let item8 = ItemEntity { - id: ItemEntityId(8), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 4, - equipped: false, - }, - item: ItemDetail::Tool(Tool { - tool: item::tool::ToolType::Monomate, - }) - }; - let item9 = ItemEntity { - id: ItemEntityId(9), - location: ItemLocation::Inventory { - character_id: CharacterEntityId(0), - index: 4, - equipped: false, - }, - item: ItemDetail::Tool(Tool { - tool: item::tool::ToolType::Monomate, - }) - }; - let item_vec = vec![item1.clone(), item2.clone(), item3.clone(), item4.clone(), item5.clone(), item6.clone(), item7.clone(), item8.clone(), item9.clone()]; - - let stacked = stack_items(item_vec); - - assert!(stacked.len() == 5); - assert!(stacked.iter().filter(|k| { - **k == ItemInstance::Individual(item6.clone()) - }).count() == 1); - - assert!(stacked.iter().filter(|k| { - **k == ItemInstance::Individual(item3.clone()) - }).count() == 1); + 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, + }; - assert!(stacked.iter().filter(|k| { - **k == ItemInstance::Individual(item1.clone()) - }).count() == 1); + 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 + entity_gateway.save_item(&ItemEntity { + id: *item_id, + item: item, + location: ItemLocation::Inventory { + character_id: client.character.id, + slot: slot, + equipped: false, + }, + }); // TODO: error check + } // 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, + }; - assert!(stacked.iter().filter(|k| { - **k == ItemInstance::Stacked(vec![item2.clone(), item4.clone(), item5.clone()]) - }).count() == 1); + 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 + 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, + }, + }); // TODO: error check + } + } // else something went very wrong TODO: log it + }, + FloorItemType::Meseta(meseta) => { + client.character.meseta += meseta.0; + } + } - assert!(stacked.iter().filter(|k| { - **k == ItemInstance::Stacked(vec![item7.clone(), item8.clone(), item9.clone()]) - }).count() == 1); + Ok(()) } } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index f588c28..cad61f2 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -1,10 +1,11 @@ use libpso::packet::messages::*; use crate::ship::ship::{ShipError}; -use crate::ship::items::{ActiveItemOnFloor}; +use crate::ship::items::{FloorItem}; +use crate::ship::location::AreaClient; use std::convert::TryInto; -pub fn item_drop(client: u8, target: u8, item_drop: &ActiveItemOnFloor) -> Result { +pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result { let item_bytes = item_drop.item.as_client_bytes(); Ok(ItemDrop { client: client, @@ -16,8 +17,32 @@ pub fn item_drop(client: u8, target: u8, item_drop: &ActiveItemOnFloor) -> Resul z: item_drop.z, y: item_drop.y, item_bytes: item_bytes[0..12].try_into()?, - item_id: item_drop.item.id.0, + item_id: item_drop.id.0, item_bytes2: item_bytes[12..16].try_into()?, unknown2: 0, }) } + +pub fn create_item(area_client: AreaClient, item: &FloorItem) -> Result { + let bytes = item.item.as_client_bytes(); + Ok(CreateItem { + client: area_client.local_client.id(), + target: 0, + item_data: bytes[0..12].try_into()?, + item_id: item.id.0, + item_data2: bytes[12..16].try_into()?, + unknown: 0, + }) +} + +pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Result { + Ok(RemoveItemFromFloor { + client: area_client.local_client.id(), + target: 0, + client_id: area_client.local_client.id(), + unknown: 0, + area: item.map_area.area_value(), + unknown2: 0, + item_id: item.id.0, + }) +} diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index 0aae3c4..0f990fb 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -4,13 +4,13 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients}; use crate::login::login::get_login_status; use crate::entity::gateway::EntityGateway; -use crate::ship::items::ActiveItemDatabase; +use crate::ship::items::ItemManager; pub fn validate_login(id: ClientId, pkt: &Login, entity_gateway: &mut EG, clients: &mut Clients, - item_database: &mut ActiveItemDatabase, + item_manager: &mut ItemManager, ship_name: &String) -> Result, ShipError> { Ok(match get_login_status(entity_gateway, pkt) { @@ -26,7 +26,7 @@ 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_database.get_character_inventory(entity_gateway, &character); + let inventory = item_manager.get_character_inventory(entity_gateway, &character); clients.insert(id, ClientState::new(user, settings, character, inventory, pkt.session)); vec![SendShipPacket::LoginResponse(response), SendShipPacket::ShipBlockList(ShipBlockList::new(&&ship_name, 3))] diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index a292618..3b6b297 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -4,10 +4,10 @@ use libpso::packet::messages::*; 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::ActiveItemDatabase; +use crate::ship::drops::ItemDrop; +use crate::ship::items::{ItemManager, ItemManagerError}; use crate::entity::gateway::EntityGateway; -use libpso::{utf8_to_utf16_array}; +use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation) @@ -50,9 +50,11 @@ pub fn request_item(id: ClientId, client_location: &ClientLocation, clients: &mut Clients, rooms: &mut Rooms, - active_items: &mut ActiveItemDatabase) + item_manager: &mut ItemManager) -> Result + Send>, ShipError> -where EG: EntityGateway { +where + EG: EntityGateway +{ 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))? @@ -64,7 +66,6 @@ where EG: EntityGateway { 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() @@ -82,11 +83,12 @@ where EG: EntityGateway { z: request_item.z, item: item_drop_type, }; - - let activated_item = active_items.activate_item_drop(entity_gateway, item_drop)?; + //let floor_item_instance = FloorItemInstance::from_item_drop(entity_gateway, item_drop)?; + //let activated_item = item_manager.activate_floor_item(floor_item_instance); let 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); + let floor_item = item_manager.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| { @@ -97,3 +99,83 @@ where EG: EntityGateway { Ok(Box::new(item_drop_packets.into_iter())) } + +pub fn pickup_item(id: ClientId, + pickup_item: &PickupItem, + 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 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 remove_item = builder::message::remove_item_from_floor(area_client, &item)?; + let create_item = builder::message::create_item(area_client, &item)?; + //match client.inventory.add_item(item.item) { + match item_manager.move_item_from_floor_to_inventory(entity_gateway, &mut client, 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().map(move |c| { + (c.client, SendShipPacket::Message(Message::new(GameMessage::CreateItem(create_item.clone())))) + }))) + ) + }, + 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), + } + } + Ok(Box::new(None.into_iter())) + } + } +} + diff --git a/src/ship/room.rs b/src/ship/room.rs index 516c9d7..ba998ab 100644 --- a/src/ship/room.rs +++ b/src/ship/room.rs @@ -5,6 +5,7 @@ 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 { @@ -152,6 +153,7 @@ pub struct RoomState { pub section_id: SectionID, pub random_seed: u32, pub bursting: bool, + pub floor_items: Vec, // items on ground // enemy info } @@ -231,6 +233,7 @@ 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(), }) } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 78256cd..7672c17 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -45,6 +45,7 @@ pub enum ShipError { MonsterAlreadyDroppedItem(ClientId, u16), SliceError(#[from] std::array::TryFromSliceError), ItemError, // TODO: refine this + PickUpInvalidItemId(u32), } #[derive(Debug)] @@ -157,14 +158,14 @@ pub struct ClientState { pub character: CharacterEntity, session: Session, //guildcard: GuildCard, - pub inventory: items::ActiveInventory, + pub inventory: items::CharacterInventory, //bank: Bank, - pub floor_items: Vec, + pub floor_items: Vec, pub block: u32, } impl ClientState { - pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, inventory: items::ActiveInventory, /*bank: Bank,*/ session: Session) -> ClientState { + pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, inventory: items::CharacterInventory, /*bank: Bank,*/ session: Session) -> ClientState { ClientState { user: user, settings: settings, @@ -186,7 +187,7 @@ pub struct ShipServerState { level_table: CharacterLevelTable, name: String, rooms: Rooms, - item_database: items::ActiveItemDatabase, + item_database: items::ItemManager, } impl ShipServerState { @@ -198,7 +199,7 @@ impl ShipServerState { level_table: CharacterLevelTable::new(), name: "Sona-Nyl".into(), rooms: [None; MAX_ROOMS], - item_database: items::ActiveItemDatabase::new(), + item_database: items::ItemManager::new(), } } @@ -226,6 +227,9 @@ impl ShipServerState { 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() }, + 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() + }, _ => { let cmsg = msg.clone(); Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()