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::ship::ItemDropLocation; 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, PartialEq)] pub enum ActiveItemEntityId { Individual(ItemEntityId), Stacked(Vec<ItemEntityId>), Meseta(Meseta), } #[derive(Debug, Clone, PartialEq)] pub 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, PartialEq)] 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 { pub entity_id: ActiveItemEntityId, pub item_id: ClientItemId, pub item: HeldItemType, pub 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, } #[derive(Debug)] pub struct CharacterInventory<'a>(&'a Vec<InventoryItem>); 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 slot(&self, slot: usize) -> Option<&'a InventoryItem> { self.0.get(slot) } pub fn count(&self) -> usize { self.0.len() } } #[derive(Error, Debug)] #[error("")] pub enum ItemManagerError { EntityGatewayError, NoSuchItemId(ClientItemId), NoCharacter(CharacterEntityId), CouldNotAddToInventory(FloorItem), //ItemBelongsToOtherPlayer, Idunnoman, CouldNotSplitItem(InventoryItem), CouldNotDropMeseta, } pub struct ItemManager { id_counter: u32, character_inventory: HashMap<CharacterEntityId, Vec<InventoryItem>>, character_floor: HashMap<CharacterEntityId, Vec<FloorItem>>, character_room: HashMap<CharacterEntityId, RoomId>, room_floor: HashMap<RoomId, Vec<FloorItem>>, room_item_id_counter: HashMap<RoomId, Box<dyn FnMut() -> ClientItemId + Send>>, } impl ItemManager { pub fn new() -> ItemManager { ItemManager { id_counter: 0, character_inventory: HashMap::new(), character_floor: 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) } // TODO: Result pub fn load_character<EG: EntityGateway>(&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()); 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> { 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<InventoryItem, ItemManagerError> { 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<FloorItem, ItemManagerError> { 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<EG: EntityGateway>(&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))?; match &floor_item.item { FloorItemType::Individual(_item) => { if inventory.len() >= 30 { return Err(ItemManagerError::CouldNotAddToInventory(floor_item)); } }, FloorItemType::Stacked(floor_tooltype, floor_amount) => { let tool_overflow = inventory.iter() .find(|item| { if let HeldItemType::Stacked(inv_tooltype, inv_amount) = item.item { if floor_tooltype.tool == inv_tooltype.tool { if floor_tooltype.tool.max_stack() < (inv_amount + floor_amount) { return true } } } false }); if tool_overflow.is_some() { return Err(ItemManagerError::CouldNotAddToInventory(floor_item)); } }, FloorItemType::Meseta(_meseta) => { if character.meseta == 999999 { return Err(ItemManagerError::CouldNotAddToInventory(floor_item)); } }, } 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)) } 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, amount) => { let inventory_item = inventory.iter_mut() .filter(|i| { if let HeldItemType::Stacked(tooltype, _amount) = i.item { tooltype == tool } else { false } }) .next() .map(|existing_inv_item| { if let (ActiveItemEntityId::Stacked(ref mut inv_item_id), ActiveItemEntityId::Stacked(floor_item_id)) = (&mut existing_inv_item.entity_id, &floor_item.entity_id) { inv_item_id.append(&mut floor_item_id.clone()); } if let (HeldItemType::Stacked(_inv_tooltype, ref mut inv_amount), FloorItemType::Stacked(_floor_tooltype, floor_amount)) = (&mut existing_inv_item.item, &floor_item.item) { // TODO: check tools are eq? *inv_amount += floor_amount } existing_inv_item.clone() }) .unwrap_or_else(|| { let picked_up_item = InventoryItem { entity_id: floor_item.entity_id, item_id: floor_item.item_id, item: HeldItemType::Stacked(tool, amount), equipped: false, }; inventory.push(picked_up_item.clone()); picked_up_item }); 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 }; } // else something went very wrong TODO: log it }, FloorItemType::Meseta(meseta) => { character.meseta = std::cmp::min(character.meseta + meseta.0, 999999); entity_gateway.save_character(&character); } } Ok(()) } pub fn enemy_drop_item_on_local_floor<EG: EntityGateway>(&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<ItemEntityId, ItemManagerError> { let e = entity.ok_or(ItemManagerError::EntityGatewayError)?; Ok(e.id) }); ActiveItemEntityId::Stacked(entities.collect::<Result<Vec<_>, _>>()?) }, 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.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.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<EG: EntityGateway>(&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(()) } 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)) } } }