use std::collections::{HashMap, BTreeMap}; use libpso::character::character;//::InventoryItem; use thiserror::Error; use crate::entity::gateway::EntityGateway; 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::location::{AreaClient, RoomId}; #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ActiveItemId(pub u32); #[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, Clone)] enum HeldItemType { Individual(ItemDetail), Stacked(Tool, usize), } impl HeldItemType { pub fn as_client_bytes(&self) -> [u8; 16] { 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(), 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(), } }, HeldItemType::Stacked(tool, count) => { tool.as_stacked_bytes(*count) }, } } } #[derive(Debug, Clone)] pub enum FloorItemType { Individual(ItemDetail), Stacked(Tool, usize), Meseta(Meseta), } 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() } } } } #[derive(Debug, Clone)] pub struct InventoryItem { entity_id: ActiveItemEntityId, item_id: ClientItemId, item: HeldItemType, equipped: bool, } #[derive(Debug, Clone)] pub struct FloorItem { entity_id: ActiveItemEntityId, pub item_id: ClientItemId, pub item: FloorItemType, pub map_area: MapArea, pub x: f32, pub y: f32, pub z: f32, } #[derive(Debug)] pub struct BankItem { id: ActiveItemId, item: HeldItemType, } pub struct CharacterInventory<'a>(&'a Vec); impl<'a> CharacterInventory<'a> { 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.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 }) } pub fn count(&self) -> usize { self.0.len() } } #[derive(Error, Debug)] #[error("")] pub enum ItemManagerError { EntityGatewayError, NoSuchItemId(ClientItemId), NoCharacter(CharacterEntityId), CouldNotAddToInventory(FloorItem), //ItemBelongsToOtherPlayer, Idunnoman, } pub struct ItemManager { 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_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(), } } 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) } // 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| { match item.location { 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)); } acc }) .into_iter() .map(|(_slot, (held_item, entity_id, equipped))| { let id = self.next_global_item_id(); InventoryItem { entity_id: entity_id, item_id: id, item: held_item, equipped: equipped, } }); 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 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)), 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 entity_id = 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, } }).ok_or(ItemManagerError::EntityGatewayError)?; ActiveItemEntityId::Individual(entity.id) }, 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 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 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 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.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 }, 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::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 }, _ => {}, // can meseta get here? } shared_floor.push(room_floor_item); Ok(()) } }