From f7b91772b093412b2eafa2bb0cc37ed39eae2707 Mon Sep 17 00:00:00 2001 From: jake Date: Mon, 29 Jun 2020 19:26:35 -0600 Subject: [PATCH 1/2] another item refactor (plus tool usage) --- src/entity/item/mod.rs | 13 + src/entity/item/tool.rs | 8 + src/ship/character.rs | 2 +- src/ship/items.rs | 864 ++++++++++++++-------- src/ship/packet/builder/message.rs | 30 +- src/ship/packet/handler/direct_message.rs | 6 +- src/ship/packet/handler/message.rs | 63 +- src/ship/ship.rs | 5 +- tests/common.rs | 1 + tests/test_exp_gain.rs | 10 +- tests/test_item_pickup.rs | 192 +++-- 11 files changed, 771 insertions(+), 423 deletions(-) diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index b5f4db6..03979ff 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -42,6 +42,7 @@ pub enum ItemLocation { y: f32, z: f32, }, + Consumed, /*Destroyed { // marks an item that has been consumed in some way }, @@ -130,6 +131,18 @@ impl ItemDetail { _ => None, } } + + pub fn as_client_bytes(&self) -> [u8; 16] { + match self { + 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(), + } + } } #[derive(Clone, Debug)] diff --git a/src/entity/item/tool.rs b/src/entity/item/tool.rs index cd9029b..de92170 100644 --- a/src/entity/item/tool.rs +++ b/src/entity/item/tool.rs @@ -642,4 +642,12 @@ impl Tool { Err(ItemParseError::InvalidToolBytes) // TODO: error handling if wrong bytes are given } } + + pub fn is_stackable(&self) -> bool { + self.tool.is_stackable() + } + + pub fn max_stack(&self) -> usize { + self.tool.max_stack() + } } diff --git a/src/ship/character.rs b/src/ship/character.rs index a3f5281..0c3f471 100644 --- a/src/ship/character.rs +++ b/src/ship/character.rs @@ -46,7 +46,7 @@ impl<'a> CharacterBytesBuilder<'a> { let level = self.level.unwrap(); character::Character { name: libpso::utf8_to_utf16_array!(character.name, 16), - hp: stats.hp + character.materials.hp as u16 * 2, + hp: stats.hp, atp: stats.atp + character.materials.power as u16 * 2, mst: stats.mst + character.materials.mind as u16 * 2, evp: stats.evp + character.materials.evade as u16 * 2, diff --git a/src/ship/items.rs b/src/ship/items.rs index 1eb9dfd..9abef27 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, BTreeMap}; +use std::cmp::Ordering; use thiserror::Error; use futures::future::join_all; use libpso::character::character;//::InventoryItem; @@ -6,7 +7,7 @@ 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::entity::item::tool::{Tool, ToolType}; use crate::ship::map::MapArea; use crate::ship::ship::ItemDropLocation; use crate::ship::drops::{ItemDrop, ItemDropType}; @@ -22,52 +23,109 @@ 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), - Meseta(Meseta), +#[derive(Debug, Clone)] +pub struct IndividualInventoryItem { + pub entity_id: ItemEntityId, + pub item_id: ClientItemId, + pub item: ItemDetail, + pub equipped: bool, } -#[derive(Debug, Clone, PartialEq)] -pub enum HeldItemType { - Individual(ItemDetail), - Stacked(Tool, usize), +#[derive(Debug, Clone)] +pub struct StackedInventoryItem { + pub entity_ids: Vec, + pub item_id: ClientItemId, + pub tool: Tool, } -impl HeldItemType { - pub fn as_client_bytes(&self) -> [u8; 16] { +impl StackedInventoryItem { + pub fn count(&self) -> usize { + self.entity_ids.len() + } +} + +#[derive(Debug, Clone)] +pub enum InventoryItem { + Individual(IndividualInventoryItem), + Stacked(StackedInventoryItem), +} + +pub enum InventoryItemAddToError { + BothAreNotStacked, + DifferentTool, + ExceedsCapacity, +} + +impl InventoryItem { + pub fn item_id(&self) -> ClientItemId { 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(), - } + InventoryItem::Individual(individual_inventory_item) => { + individual_inventory_item.item_id }, - HeldItemType::Stacked(tool, count) => { - tool.as_stacked_bytes(*count) + InventoryItem::Stacked(stacked_inventory_item) => { + stacked_inventory_item.item_id + } + } + } + + pub fn set_item_id(&mut self, item_id: ClientItemId) { + match self { + InventoryItem::Individual(individual_inventory_item) => { + individual_inventory_item.item_id = item_id }, + InventoryItem::Stacked(stacked_inventory_item) => { + stacked_inventory_item.item_id = item_id + } } } -} -#[derive(Debug, Clone, PartialEq)] -pub enum FloorItemType { - Individual(ItemDetail), - Stacked(Tool, usize), - Meseta(Meseta), -} + pub fn are_same_stackable_tool(&self, other_stacked_item: &StackedFloorItem) -> bool { + match self { + InventoryItem::Stacked(self_stacked_item) => { + self_stacked_item.tool == other_stacked_item.tool + && self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable() + }, + _ => false + } + } + + pub fn can_combine_stacks(&self, other_stacked_item: &StackedFloorItem) -> bool { + match self { + InventoryItem::Stacked(self_stacked_item) => { + self_stacked_item.tool == other_stacked_item.tool + && self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable() + && self_stacked_item.count() + other_stacked_item.count() <= self_stacked_item.tool.max_stack() + }, + _ => false + } + } + + // TODO: result + pub fn combine_stacks(&mut self, other_stacked_item: &mut StackedFloorItem) { + match self { + InventoryItem::Stacked(self_stacked_item) => { + self_stacked_item.entity_ids.append(&mut other_stacked_item.entity_ids); + }, + _ => { + } + } + } + + pub fn equipped(&self) -> bool { + match self { + InventoryItem::Individual(individual_inventory_item) => { + individual_inventory_item.equipped + }, + InventoryItem::Stacked(_) => { + false + } + } + } -impl FloorItemType { pub fn as_client_bytes(&self) -> [u8; 16] { match self { - FloorItemType::Individual(item) => { - match &item { + InventoryItem::Individual(item) => { + match &item.item { ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Armor(a) => a.as_bytes(), ItemDetail::Shield(s) => s.as_bytes(), @@ -77,58 +135,185 @@ impl FloorItemType { ItemDetail::Mag(m) => m.as_bytes(), } }, - FloorItemType::Stacked(tool, count) => { - tool.as_stacked_bytes(*count) + InventoryItem::Stacked(item) => { + item.tool.as_stacked_bytes(item.entity_ids.len()) }, - FloorItemType::Meseta(m) => { - m.as_bytes() + } + } + + pub fn can_add_to(&mut self, stacked_floor_item: &StackedFloorItem) -> Result<(), InventoryItemAddToError> { + if let InventoryItem::Stacked(stacked_inventory_item) = self { + if stacked_floor_item.tool != stacked_inventory_item.tool { + return Err(InventoryItemAddToError::DifferentTool) + } + + if stacked_floor_item.tool.tool.max_stack() < (stacked_floor_item.count() + stacked_inventory_item.count()) { + return Err(InventoryItemAddToError::ExceedsCapacity) } + Ok(()) + } + else { + Err(InventoryItemAddToError::BothAreNotStacked) } } + + pub fn add_to(&mut self, mut stacked_floor_item: StackedFloorItem) -> Result<(), InventoryItemAddToError> { + self.can_add_to(&stacked_floor_item)?; + if let InventoryItem::Stacked(stacked_inventory_item) = self { + stacked_inventory_item.entity_ids.append(&mut stacked_floor_item.entity_ids); + } + Ok(()) + } } #[derive(Debug, Clone)] -pub struct InventoryItem { - pub entity_id: ActiveItemEntityId, +pub struct IndividualFloorItem { + pub entity_id: ItemEntityId, pub item_id: ClientItemId, - pub item: HeldItemType, - pub equipped: bool, + pub item: ItemDetail, + pub map_area: MapArea, + pub x: f32, + pub y: f32, + pub z: f32, } #[derive(Debug, Clone)] -pub struct FloorItem { - entity_id: ActiveItemEntityId, +pub struct StackedFloorItem { + pub entity_ids: Vec, pub item_id: ClientItemId, - pub item: FloorItemType, + pub tool: Tool, pub map_area: MapArea, pub x: f32, pub y: f32, pub z: f32, } -#[derive(Debug)] -pub struct BankItem { - id: ActiveItemId, - item: HeldItemType, +impl StackedFloorItem { + pub fn count(&self) -> usize { + self.entity_ids.len() + } +} + +#[derive(Debug, Clone)] +pub struct MesetaFloorItem { + pub item_id: ClientItemId, + pub meseta: Meseta, + pub map_area: MapArea, + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Debug, Clone)] +pub enum FloorItem { + Individual(IndividualFloorItem), + Stacked(StackedFloorItem), + Meseta(MesetaFloorItem), } +impl FloorItem { + pub fn item_id(&self) -> ClientItemId { + match self { + FloorItem::Individual(individual_floor_item) => { + individual_floor_item.item_id + }, + FloorItem::Stacked(stacked_floor_item) => { + stacked_floor_item.item_id + }, + FloorItem::Meseta(meseta_floor_item) => { + meseta_floor_item.item_id + } + } + } + + pub fn x(&self) -> f32 { + match self { + FloorItem::Individual(individual_floor_item) => { + individual_floor_item.x + }, + FloorItem::Stacked(stacked_floor_item) => { + stacked_floor_item.x + }, + FloorItem::Meseta(meseta_floor_item) => { + meseta_floor_item.x + } + } + } + + pub fn y(&self) -> f32 { + match self { + FloorItem::Individual(individual_floor_item) => { + individual_floor_item.y + }, + FloorItem::Stacked(stacked_floor_item) => { + stacked_floor_item.y + }, + FloorItem::Meseta(meseta_floor_item) => { + meseta_floor_item.y + } + } + } + + pub fn z(&self) -> f32 { + match self { + FloorItem::Individual(individual_floor_item) => { + individual_floor_item.z + }, + FloorItem::Stacked(stacked_floor_item) => { + stacked_floor_item.z + }, + FloorItem::Meseta(meseta_floor_item) => { + meseta_floor_item.z + } + } + } + + pub fn map_area(&self) -> MapArea { + match self { + FloorItem::Individual(individual_floor_item) => { + individual_floor_item.map_area + }, + FloorItem::Stacked(stacked_floor_item) => { + stacked_floor_item.map_area + }, + FloorItem::Meseta(meseta_floor_item) => { + meseta_floor_item.map_area + } + } + } + + pub fn as_client_bytes(&self) -> [u8; 16] { + match self { + FloorItem::Individual(individual_floor_item) => { + individual_floor_item.item.as_client_bytes() + }, + FloorItem::Stacked(stacked_floor_item) => { + stacked_floor_item.tool.as_stacked_bytes(stacked_floor_item.count()) + }, + FloorItem::Meseta(meseta_floor_item) => { + meseta_floor_item.meseta.as_bytes() + } + } + } +} #[derive(Debug)] pub struct CharacterInventory<'a>(&'a Vec); +// TODO: this should actually do things 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(); + let bytes = 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; + inventory[slot].item_id = item.item_id().0; // does this do anything? - inventory[slot].equipped = if item.equipped { 1 } else { 0 }; + 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[slot].flags |= if item.equipped() { 8 } else { 0 }; inventory }) } @@ -140,6 +325,8 @@ impl<'a> CharacterInventory<'a> { pub fn count(&self) -> usize { self.0.len() } + + // TODO: function that gives out a handle to an item, allowing deletion of it from the inventory } pub enum TriggerCreateItem { @@ -159,6 +346,7 @@ pub enum ItemManagerError { Idunnoman, CouldNotSplitItem(InventoryItem), CouldNotDropMeseta, + NotEnoughTools(Tool, usize, usize), // have, expected } pub struct ItemManager { @@ -202,40 +390,36 @@ impl ItemManager { .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); + let inventory_item = acc.entry(slot).or_insert(InventoryItem::Stacked(StackedInventoryItem { + entity_ids: Vec::new(), + item_id: self.next_global_item_id(), + tool: tool, + })); + if let InventoryItem::Stacked(ref mut stacked_inventory_item) = inventory_item { + stacked_inventory_item.entity_ids.push(id); } } } else { - acc.insert(slot, (HeldItemType::Individual(item), ActiveItemEntityId::Individual(id), equipped)); + acc.insert(slot, InventoryItem::Individual(IndividualInventoryItem { + entity_id: id, + item_id: self.next_global_item_id(), + item: item, + equipped: 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); + let inventory = inventory_items.into_iter().map(|(k, v)| v).take(30).collect(); + self.character_inventory.insert(character.id, inventory); } 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); + item.set_item_id(ClientItemId(base_id + i as u32)); } self.character_room.insert(character.id, room_id); self.character_floor.insert(character.id, Vec::new()); @@ -269,7 +453,7 @@ impl ItemManager { let inventory = self.character_inventory.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; inventory.iter() .filter(|item| { - item.item_id == item_id + item.item_id() == item_id }) .nth(0) .ok_or(ItemManagerError::NoSuchItemId(item_id)) @@ -283,7 +467,7 @@ impl ItemManager { floor.iter() .chain(shared_floor.iter()) .filter(|item| { - item.item_id == item_id + item.item_id() == item_id }) .nth(0) .ok_or(ItemManagerError::NoSuchItemId(item_id)) @@ -296,157 +480,138 @@ impl ItemManager { 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) => { + let (floor_to_remove_from, position) = if let Some(position) = local_floor.iter().position(|item| item.item_id() == floor_item.item_id()) { + (local_floor, position) + } + else if let Some(position) = shared_floor.iter().position(|item| item.item_id() == floor_item.item_id()) { + (shared_floor, position) + } + else { + return Err(ItemManagerError::NoSuchItemId(floor_item.item_id())) + }; + + let trigger_create_item = match floor_item.clone() { + FloorItem::Individual(individual_floor_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)) - } + entity_gateway.save_item(&ItemEntity { + id: individual_floor_item.entity_id, + item: individual_floor_item.item.clone(), + location: ItemLocation::Inventory { + character_id: character.id, + slot: inventory.len(), + equipped: false, + } + }).await; - let trigger_create = 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()), + let inventory_item = InventoryItem::Individual(IndividualInventoryItem { + entity_id: individual_floor_item.entity_id, + item_id: individual_floor_item.item_id, + item: individual_floor_item.item, equipped: false, - }; + }); + inventory.push(inventory_item); - 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, - }, - }).await; // TODO: error check - inventory.push(inventory_item); - } // else something went very wrong TODO: log it TriggerCreateItem::Yes }, - FloorItemType::Stacked(tool, amount) => { - let (trigger_create, inventory_item) = inventory.iter_mut() - .filter(|i| { - if let HeldItemType::Stacked(tooltype, _amount) = i.item { - tooltype == tool + FloorItem::Stacked(mut stacked_floor_item) => { + let mut tool_in_inventory = inventory.iter_mut() + .enumerate() + .find(|(_, item)| { + item.are_same_stackable_tool(&stacked_floor_item) + }); + + match tool_in_inventory { + Some((slot, ref mut tool_in_inventory)) => { + if tool_in_inventory.can_combine_stacks(&stacked_floor_item) { + for entity_id in stacked_floor_item.entity_ids.iter() { + entity_gateway.save_item(&ItemEntity { + id: *entity_id, + item: ItemDetail::Tool(stacked_floor_item.tool), + location: ItemLocation::Inventory { + character_id: character.id, + slot: slot, + equipped: false, + } + }).await; + } + + tool_in_inventory.combine_stacks(&mut stacked_floor_item) } else { - false + return Err(ItemManagerError::CouldNotAddToInventory(floor_item)) } - }) - .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 + TriggerCreateItem::No + }, + None => { + let slot = inventory.len(); + for entity_id in stacked_floor_item.entity_ids.iter() { + entity_gateway.save_item(&ItemEntity { + id: *entity_id, + item: ItemDetail::Tool(stacked_floor_item.tool), + location: ItemLocation::Inventory { + character_id: character.id, + slot: slot, + equipped: false, + } + }).await; } - - (TriggerCreateItem::No, 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()); - (TriggerCreateItem::Yes, 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, - }, - }).await; // TODO: error check - }; - } // else something went very wrong TODO: log it - trigger_create + + inventory.push(InventoryItem::Stacked(StackedInventoryItem { + entity_ids: stacked_floor_item.entity_ids, + item_id: stacked_floor_item.item_id, + tool: stacked_floor_item.tool, + })); + TriggerCreateItem::Yes + } + } }, - FloorItemType::Meseta(meseta) => { - character.meseta = std::cmp::min(character.meseta + meseta.0, 999999); + FloorItem::Meseta(meseta_floor_item) => { + if character.meseta == 999999 { + return Err(ItemManagerError::CouldNotAddToInventory(floor_item)); + } + character.meseta = std::cmp::min(character.meseta + meseta_floor_item.meseta.0, 999999); entity_gateway.save_character(&character).await; TriggerCreateItem::No } }; - Ok(trigger_create) - } + floor_to_remove_from.remove(position); + Ok(trigger_create_item) + } + pub async fn enemy_drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&FloorItem, ItemManagerError> { + let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + + enum ItemOrMeseta { + Individual(ItemDetail), + Stacked(Tool), + Meseta(Meseta) + } + 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)) + ItemDropType::Weapon(w) => ItemOrMeseta::Individual(ItemDetail::Weapon(w)), + ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)), + ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)), + ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)), + ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)), + ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)), + //ItemDropType::IndividualTool(t) => ItemOrMeseta::Individual(ItemDetail::Tool(t)), + //ItemDropType::StackedTool(t, _) => ItemOrMeseta::Stacked(t), + ItemDropType::Tool(t) if t.tool.is_stackable() => ItemOrMeseta::Stacked(t), + ItemDropType::Tool(t) if !t.tool.is_stackable() => ItemOrMeseta::Individual(ItemDetail::Tool(t)), + ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)), + _ => unreachable!() // rust isnt smart enough to see that the conditional on tool catches everything }; - - //let eg = Arc::new(Mutex::new(entity_gateway.clone())) - let entity_id = match &item { - FloorItemType::Individual(i) => { + let item_id = self.room_item_id_counter.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?(); + let floor_item = match item { + ItemOrMeseta::Individual(item_detail) => { let entity = entity_gateway.create_item(NewItemEntity { - item: i.clone(), + item: item_detail.clone(), location: ItemLocation::LocalFloor { character_id: character.id, map_area: item_drop.map_area, @@ -455,53 +620,49 @@ impl ItemManager { z: item_drop.z, } }).await.ok_or(ItemManagerError::EntityGatewayError)?; - ActiveItemEntityId::Individual(entity.id) + FloorItem::Individual(IndividualFloorItem { + entity_id: entity.id, + item_id: item_id, + item: item_detail, + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + }) }, - FloorItemType::Stacked(tool, count) => { - let entities = (0..*count) - .map(|_| { - let mut eg = entity_gateway.clone(); - let item_drop = item_drop.clone(); - async move { - eg.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, - } - }).await - }}); - let entities = join_all(entities).await.into_iter() - .map(|entity| -> Result { - let e = entity.ok_or(ItemManagerError::EntityGatewayError)?; - // I am not able to manually specify a closure return type when also using the async keyword - Ok(e.id) - //let result: Result = Ok(e.id); - //result - }); - //ActiveItemEntityId::Stacked(join_all(entities).await.into_iter().collect::, _>>()?) - ActiveItemEntityId::Stacked(entities.collect::, _>>()?) - //ActiveItemEntityId::Stacked(entities.collect().await?) + ItemOrMeseta::Stacked(tool) => { + let entity = 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, + } + }).await.ok_or(ItemManagerError::EntityGatewayError)?; + FloorItem::Stacked(StackedFloorItem { + entity_ids: vec![entity.id], + item_id: item_id, + tool: tool, + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + }) + }, + ItemOrMeseta::Meseta(meseta) => { + FloorItem::Meseta(MesetaFloorItem { + item_id: item_id, + meseta: meseta, + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + }) }, - 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) } @@ -516,59 +677,60 @@ impl ItemManager { 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) + let dropped_inventory_item = 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, - }; + .ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?; - match &room_floor_item.item { - FloorItemType::Individual(item) => { - if let ActiveItemEntityId::Individual(item_id) = &room_floor_item.entity_id { + let dropped_floor_item = match dropped_inventory_item { + InventoryItem::Individual(individual_inventory_item) => { + entity_gateway.save_item(&ItemEntity { + id: individual_inventory_item.entity_id, + item: individual_inventory_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, + } + }).await; + + FloorItem::Individual(IndividualFloorItem { + entity_id: individual_inventory_item.entity_id, + item_id: individual_inventory_item.item_id, + item: individual_inventory_item.item, + map_area: item_drop_location.0, + x: item_drop_location.1, + y: item_drop_location.2, + z: item_drop_location.3, + }) + }, + InventoryItem::Stacked(stacked_inventory_item) => { + for entity_id in stacked_inventory_item.entity_ids.iter() { entity_gateway.save_item(&ItemEntity { - id: *item_id, - item: item.clone(), + id: *entity_id, + item: ItemDetail::Tool(stacked_inventory_item.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, } - }).await; // 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, - }, - }).await; // TODO: error check - } - } // else something went very wrong TODO: log it + }).await; + } + FloorItem::Stacked(StackedFloorItem { + entity_ids: stacked_inventory_item.entity_ids, + item_id: stacked_inventory_item.item_id, + tool: stacked_inventory_item.tool, + map_area: item_drop_location.0, + x: item_drop_location.1, + y: item_drop_location.2, + z: item_drop_location.3, + }) }, - _ => {}, // can meseta get here? - } + }; - shared_floor.push(room_floor_item); + shared_floor.push(dropped_floor_item); Ok(()) } @@ -587,65 +749,45 @@ impl ItemManager { entity_gateway.save_character(&character).await; 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)), + let floor_item = FloorItem::Meseta(MesetaFloorItem { item_id: item_id, - item: FloorItemType::Meseta(Meseta(amount)), + 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 async 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 { + 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))?; + .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 { + if let InventoryItem::Stacked(ref mut stacked_inventory_item) = item_to_split { + if stacked_inventory_item.entity_ids.len() <= amount { return Err(ItemManagerError::CouldNotSplitItem(inventory_item)); } - - *tool_amount -= amount; - - let dropped_entities = entity_ids.drain(..amount).collect::>(); - - /* - dropped_entities.iter().map(|entity_id| { - let mut eg = entity_gateway.clone(); - let tool = tool.clone(); - async move { - eg.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, - } - }).await - }}).await; - */ - for de in dropped_entities.iter() { + + let dropped_entity_ids = stacked_inventory_item.entity_ids.drain(..amount).collect::>(); + for de in dropped_entity_ids.iter() { entity_gateway.save_item(&ItemEntity { id: *de, - item: ItemDetail::Tool(*tool), + item: ItemDetail::Tool(stacked_inventory_item.tool), location: ItemLocation::SharedFloor { map_area: drop_location.map_area, x: drop_location.x, @@ -656,15 +798,15 @@ impl ItemManager { } 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), + let floor_item = FloorItem::Stacked(StackedFloorItem { + entity_ids: dropped_entity_ids, item_id: item_id, - item: FloorItemType::Stacked(*tool, amount), + tool: stacked_inventory_item.tool, 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) } @@ -672,4 +814,68 @@ impl ItemManager { Err(ItemManagerError::CouldNotSplitItem(inventory_item)) } } + + pub async fn player_consumes_tool(&mut self, + entity_gateway: &mut EG, + character: &CharacterEntity, + inventory_item: InventoryItem, + amount: usize) + -> Result { + let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; + + match inventory_item.clone() { + InventoryItem::Individual(individual_inventory_item) => { + // TODO: use remove + let _used_inventory_item = inventory + .drain_filter(|i| i.item_id() == inventory_item.item_id()) + .nth(0) + .ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?; + + entity_gateway.save_item(&ItemEntity { + id: individual_inventory_item.entity_id, + item: individual_inventory_item.item.clone(), + location: ItemLocation::Consumed, + }).await; + Ok(individual_inventory_item.item) + }, + InventoryItem::Stacked(stacked_inventory_item) => { + let used_entity_ids = match stacked_inventory_item.count().cmp(&amount) { + Ordering::Equal => { + let _used_inventory_item = inventory + .drain_filter(|i| i.item_id() == inventory_item.item_id()) + .nth(0) + .ok_or(ItemManagerError::NoSuchItemId(inventory_item.item_id()))?; + stacked_inventory_item.entity_ids + }, + Ordering::Greater => { + let tool_used = inventory + .iter_mut() + .find(|i| i.item_id() == stacked_inventory_item.item_id) + .ok_or(ItemManagerError::NoSuchItemId(stacked_inventory_item.item_id))?; + if let InventoryItem::Stacked(ref mut stacked_tool_used) = tool_used { + stacked_tool_used.entity_ids.drain(..amount).collect::>() + } + else { + unreachable!() + } + //stacked_inventory_item.entity_ids.iter_mut().drain(..amount).collect::>() + } + Ordering::Less => { + //return Err(ItemManagerError::NotEnoughTools(tool, held_amount, amount)) + return Err(ItemManagerError::Idunnoman) + }, + }; + + for used_entity_id in used_entity_ids { + entity_gateway.save_item(&ItemEntity { + id: used_entity_id, + item: ItemDetail::Tool(stacked_inventory_item.tool), + location: ItemLocation::Consumed, + }).await; + } + Ok(ItemDetail::Tool(stacked_inventory_item.tool)) + }, + } + + } } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index 14ca50f..83db7f8 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -7,30 +7,30 @@ use std::convert::TryInto; pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result { - let item_bytes = item_drop.item.as_client_bytes(); + let item_bytes = item_drop.as_client_bytes(); Ok(ItemDrop { client: client, target: target, - area: item_drop.map_area.area_value(), + area: item_drop.map_area().area_value(), variety: 0, unknown: 0, - x: item_drop.x, - z: item_drop.z, - y: item_drop.y, + x: item_drop.x(), + 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.item_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(); + let bytes = item.as_client_bytes(); Ok(CreateItem { client: area_client.local_client.id(), target: 0, item_data: bytes[0..12].try_into()?, - item_id: item.item_id.0, + item_id: item.item_id().0, item_data2: bytes[12..16].try_into()?, unknown: 0, }) @@ -42,24 +42,24 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu target: 0, client_id: area_client.local_client.id(), unknown: 0, - area: item.map_area.area_value(), + area: item.map_area().area_value(), unknown2: 0, - item_id: item.item_id.0, + 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(); + let item_bytes = 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, + 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_id: item.item_id().0, item_bytes2: item_bytes[12..16].try_into()?, unknown2: 0, }) diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index 9b50805..30b4ed5 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -5,7 +5,7 @@ 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::{ItemManager, FloorItemType, ClientItemId, TriggerCreateItem}; +use crate::ship::items::{ItemManager, ClientItemId, TriggerCreateItem, FloorItem}; use crate::entity::gateway::EntityGateway; use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; @@ -150,8 +150,8 @@ where let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; let item = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?; let remove_item = builder::message::remove_item_from_floor(area_client, &item)?; - let create_item = match item.item { - FloorItemType::Meseta(_) => None, + let create_item = match item { + FloorItem::Meseta(_) => None, _ => Some(builder::message::create_item(area_client, &item)?), }; diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 0637903..011ebe1 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -1,12 +1,14 @@ use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::entity::gateway::EntityGateway; +use crate::entity::item::ItemDetail; +use crate::entity::item::tool::ToolType; use crate::common::serverstate::ClientId; use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::map::{MapArea}; -use crate::ship::items::{ItemManager, ClientItemId}; +use crate::ship::items::{ItemManager, ClientItemId, InventoryItem}; use crate::ship::packet::builder; pub async fn request_exp(id: ClientId, @@ -210,3 +212,62 @@ where Err(ShipError::NotEnoughMeseta(id, client.character.meseta)) } } + +pub async fn use_item(id: ClientId, + player_use_tool: &PlayerUseItem, + 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 inventory_item_used = item_manager.get_inventory_item_by_id(&client.character, ClientItemId(player_use_tool.item_id))?; + let item_used_type = item_manager.player_consumes_tool(entity_gateway, &client.character, inventory_item_used, 1).await?; + + match item_used_type { + ItemDetail::Weapon(_w) => { + // something like when items are used to combine/transform them? + //_ => {} + }, + ItemDetail::Tool(t) => { + match t.tool { + ToolType::PowerMaterial => { + client.character.materials.power += 1; + entity_gateway.save_character(&client.character).await; + }, + ToolType::MindMaterial => { + client.character.materials.mind += 1; + entity_gateway.save_character(&client.character).await; + }, + ToolType::EvadeMaterial => { + client.character.materials.evade += 1; + entity_gateway.save_character(&client.character).await; + }, + ToolType::DefMaterial => { + client.character.materials.def += 1; + entity_gateway.save_character(&client.character).await; + }, + ToolType::LuckMaterial => { + client.character.materials.luck += 1; + entity_gateway.save_character(&client.character).await; + }, + ToolType::HpMaterial => { + client.character.materials.hp += 1; + entity_gateway.save_character(&client.character).await; + }, + ToolType::TpMaterial => { + client.character.materials.tp += 1; + entity_gateway.save_character(&client.character).await; + }, + _ => {} + } + } + _ => {} + } + + Ok(Box::new(None.into_iter())) +} diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 0abc001..3094e3a 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -252,7 +252,7 @@ pub struct ShipServerState { level_table: CharacterLevelTable, name: String, pub rooms: Rooms, - pub item_manager: items::ItemManager, + item_manager: items::ItemManager, quests: quests::QuestList, } @@ -293,6 +293,9 @@ impl ShipServerState { GameMessage::ChargeAttack(charge_attack) => { handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway).await }, + GameMessage::PlayerUseItem(player_use_item) => { + handler::message::use_item(id, player_use_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await + }, _ => { let cmsg = msg.clone(); Ok(Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter() diff --git a/tests/common.rs b/tests/common.rs index 29f33d0..c6a4736 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,3 +1,4 @@ +#[allow(dead_code)] use std::time::SystemTime; use elseware::common::serverstate::{ClientId, ServerState}; diff --git a/tests/test_exp_gain.rs b/tests/test_exp_gain.rs index 57c410a..3b26c05 100644 --- a/tests/test_exp_gain.rs +++ b/tests/test_exp_gain.rs @@ -32,7 +32,7 @@ async fn test_character_gains_exp() { let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap(); (enemy_id, map_enemy_stats.exp) }; - + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp{ client: enemy_id as u8, target: 16, @@ -110,7 +110,7 @@ async fn test_character_levels_up_multiple_times() { let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap(); (enemy_id, map_enemy_stats.exp) }; - + let levelup_pkt = ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp{ client: enemy_id as u8, target: 16, @@ -121,7 +121,7 @@ async fn test_character_levels_up_multiple_times() { })))).await.unwrap().collect::>(); assert!(matches!(levelup_pkt[1].1, SendShipPacket::Message(Message {msg: GameMessage::PlayerLevelUp(PlayerLevelUp {lvl: 8, ..})}))); - + let c1 = ship.clients.get(&ClientId(1)).unwrap(); assert!(exp == c1.character.exp); } @@ -136,7 +136,7 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() { let mut ship = ShipServerState::new(entity_gateway.clone()); log_in_char(&mut ship, ClientId(1), "a1", "a").await; log_in_char(&mut ship, ClientId(2), "a2", "a").await; - + join_lobby(&mut ship, ClientId(1)).await; join_lobby(&mut ship, ClientId(2)).await; @@ -158,7 +158,7 @@ async fn test_one_character_gets_full_exp_and_other_attacker_gets_partial() { let map_enemy_stats = room.monster_stats.get(&map_enemy.monster).unwrap(); (enemy_id, map_enemy_stats.exp) }; - + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::RequestExp(RequestExp{ client: enemy_id as u8, target: 16, diff --git a/tests/test_item_pickup.rs b/tests/test_item_pickup.rs index 5466ee4..acdb9b7 100644 --- a/tests/test_item_pickup.rs +++ b/tests/test_item_pickup.rs @@ -2,7 +2,7 @@ use elseware::common::serverstate::{ClientId, ServerState}; use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::entity::item; use elseware::ship::ship::{ShipServerState, RecvShipPacket}; -use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType}; +use elseware::ship::items::{ClientItemId}; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -79,11 +79,21 @@ async fn test_pick_up_item_stack_of_items_already_in_inventory() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - assert!(p1_inventory.count() == 1); - let inventory_item = p1_inventory.slot(0).unwrap(); - assert!(inventory_item.entity_id == ActiveItemEntityId::Stacked(vec![item::ItemEntityId(1), item::ItemEntityId(2), item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5), item::ItemEntityId(6)])); - assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6)); + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 6); + + let p1_item_ids = p1_items.iter().map(|item| { + item.id + }).collect::>(); + assert!(p1_item_ids == vec![item::ItemEntityId(1), item::ItemEntityId(2), item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5), item::ItemEntityId(6)]); + + let all_items_are_monomates = p1_items.iter().all(|item| { + match item.item { + item::ItemDetail::Tool(tool) => tool.tool == item::tool::ToolType::Monomate, + _ => false + } + }); + assert!(all_items_are_monomates); } #[async_std::test] @@ -136,19 +146,22 @@ async fn test_pick_up_item_stack_of_items_not_already_held() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - assert!(p1_inventory.count() == 1); - let inventory_item = p1_inventory.slot(0).unwrap(); - assert!(inventory_item.entity_id == ActiveItemEntityId::Stacked(vec![item::ItemEntityId(1)])); - assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 1)); + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 1); + + let first_item = p1_items.get(0).unwrap(); + assert!(first_item.id == item::ItemEntityId(1)); + assert!(first_item.item == item::ItemDetail::Tool(item::tool::Tool { + tool: item::tool::ToolType::Monomate, + })); } #[async_std::test] async fn test_pick_up_meseta_when_inventory_full() { let mut entity_gateway = InMemoryGateway::new(); - let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; - let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; for slot in 0..30 { entity_gateway.create_item( @@ -207,13 +220,15 @@ async fn test_pick_up_meseta_when_inventory_full() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - assert!(p1_inventory.count() == 30); + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 30); - let c1 = ship.clients.get(&ClientId(1)).unwrap(); - let c2 = ship.clients.get(&ClientId(2)).unwrap(); - assert!(c1.character.meseta == 23); - assert!(c2.character.meseta == 277); + let characters1 = entity_gateway.get_characters_by_user(&user1).await; + let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap(); + let characters2 = entity_gateway.get_characters_by_user(&user2).await; + let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap(); + assert!(c1.meseta == 23); + assert!(c2.meseta == 277); } #[async_std::test] @@ -299,11 +314,17 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - assert!(p1_inventory.count() == 30); - - let monomates = p1_inventory.slot(29).unwrap(); - assert!(monomates.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 2)); + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 31); + + let monomate1 = p1_items.get(29).unwrap(); + assert!(monomate1.item == item::ItemDetail::Tool(item::tool::Tool { + tool: item::tool::ToolType::Monomate, + })); + let monomate2 = p1_items.get(30).unwrap(); + assert!(monomate2.item == item::ItemDetail::Tool(item::tool::Tool { + tool: item::tool::ToolType::Monomate, + })); } #[async_std::test] @@ -380,17 +401,29 @@ async fn test_can_not_pick_up_item_when_inventory_full() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - assert!(p1_inventory.count() == 30); - let floor_item = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0x210000)).unwrap(); - assert!(floor_item.item_id == ClientItemId(0x210000)); + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 30); + + let p2_items = entity_gateway.get_items_by_character(&char2).await; + assert!(p2_items.len() == 0); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0x210000, + area: 0, + unknown: [0; 3] + })))).await.unwrap().for_each(drop); + + let p2_items = entity_gateway.get_items_by_character(&char2).await; + assert!(p2_items.len() == 1); } #[async_std::test] async fn test_can_not_drop_more_meseta_than_is_held() { let mut entity_gateway = InMemoryGateway::new(); - let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; char1.meseta = 300; entity_gateway.save_character(&char1).await; @@ -419,9 +452,9 @@ async fn test_can_not_drop_more_meseta_than_is_held() { })))).await; assert!(split_attempt.is_err()); - let c1 = ship.clients.get(&ClientId(1)).unwrap(); - assert!(c1.character.meseta == 300); - assert!(ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0xF0000001)).is_err()) + let characters1 = entity_gateway.get_characters_by_user(&user1).await; + let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap(); + assert!(c1.meseta == 300); } #[async_std::test] @@ -483,27 +516,28 @@ async fn test_pick_up_stack_that_would_exceed_stack_limit() { z: 0.0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { client: 0, target: 0, item_id: 0x210000, area: 0, unknown: [0; 3] - })))).await.unwrap().for_each(drop); + })))).await.unwrap().collect::>(); + assert!(packets.len() == 0); + + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 6); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - let monomates = p1_inventory.slot(0).unwrap(); - assert!(monomates.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6)); - let floor_monomates = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0x210000)).unwrap(); - assert!(floor_monomates.item == FloorItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6)); + let p2_items = entity_gateway.get_items_by_character(&char2).await; + assert!(p2_items.len() == 0); } #[async_std::test] async fn test_can_not_pick_up_meseta_when_full() { let mut entity_gateway = InMemoryGateway::new(); - let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; - let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; char1.meseta = 999999; entity_gateway.save_character(&char1).await; @@ -536,29 +570,30 @@ async fn test_can_not_pick_up_meseta_when_full() { amount: 23, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { client: 0, target: 0, item_id: 0xF0000001, area: 0, unknown: [0; 3] - })))).await.unwrap().for_each(drop); - - let c1 = ship.clients.get(&ClientId(1)).unwrap(); - let c2 = ship.clients.get(&ClientId(2)).unwrap(); - assert!(c1.character.meseta == 999999); - assert!(c2.character.meseta == 277); - - let floor_meseta = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0xF0000001)).unwrap(); - assert!(floor_meseta.item == FloorItemType::Meseta(item::Meseta(23))); + })))).await.unwrap().collect::>(); + println!("pkts {:?}", packets); + assert!(packets.len() == 0); + + let characters1 = entity_gateway.get_characters_by_user(&user1).await; + let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap(); + let characters2 = entity_gateway.get_characters_by_user(&user2).await; + let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap(); + assert!(c1.meseta == 999999); + assert!(c2.meseta == 277); } #[async_std::test] async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() { let mut entity_gateway = InMemoryGateway::new(); - let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; - let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await; char1.meseta = 999998; entity_gateway.save_character(&char1).await; @@ -599,13 +634,12 @@ async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let c1 = ship.clients.get(&ClientId(1)).unwrap(); - let c2 = ship.clients.get(&ClientId(2)).unwrap(); - assert!(c1.character.meseta == 999999); - assert!(c2.character.meseta == 277); - - let floor_meseta = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0xF0000001)); - assert!(floor_meseta.is_err()); + let characters1 = entity_gateway.get_characters_by_user(&user1).await; + let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap(); + let characters2 = entity_gateway.get_characters_by_user(&user2).await; + let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap(); + assert!(c1.meseta == 999999); + assert!(c2.meseta == 277); } #[async_std::test] @@ -665,13 +699,35 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() { unknown: [0; 3] })))).await.unwrap().for_each(drop); - let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); - assert!(p1_inventory.count() == 1); - let inventory_item = p1_inventory.slot(0).unwrap(); - assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 3)); + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items.len() == 3); + + let p1_item_ids = p1_items.iter().map(|item| { + item.id + }).collect::>(); + assert!(p1_item_ids == vec![item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5)]); + + let all_items_are_monomates = p1_items.iter().all(|item| { + match item.item { + item::ItemDetail::Tool(tool) => tool.tool == item::tool::ToolType::Monomate, + _ => false + } + }); + assert!(all_items_are_monomates); - let p2_inventory = ship.item_manager.get_character_inventory(&char2).unwrap(); - assert!(p2_inventory.count() == 1); - let inventory_item = p2_inventory.slot(0).unwrap(); - assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 2)); + let p2_items = entity_gateway.get_items_by_character(&char2).await; + assert!(p2_items.len() == 2); + + let p2_item_ids = p2_items.iter().map(|item| { + item.id + }).collect::>(); + assert!(p2_item_ids == vec![item::ItemEntityId(1), item::ItemEntityId(2)]); + + let all_items_are_monomates = p1_items.iter().all(|item| { + match item.item { + item::ItemDetail::Tool(tool) => tool.tool == item::tool::ToolType::Monomate, + _ => false + } + }); + assert!(all_items_are_monomates); } From 79502f03d254b7f8b90354634e9396d692cad7d5 Mon Sep 17 00:00:00 2001 From: jake Date: Mon, 29 Jun 2020 19:40:50 -0600 Subject: [PATCH 2/2] add tests for item use --- tests/test_item_use.rs | 286 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 tests/test_item_use.rs diff --git a/tests/test_item_use.rs b/tests/test_item_use.rs new file mode 100644 index 0000000..a84afef --- /dev/null +++ b/tests/test_item_use.rs @@ -0,0 +1,286 @@ +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::item; +use elseware::ship::ship::{ShipServerState, RecvShipPacket}; +//use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType}; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; + +#[path = "common.rs"] +mod common; +use common::*; + + +#[async_std::test] +async fn test_use_monomate() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + for (slot, tool) in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter().enumerate() { + for _ in 0..2 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }).await; + } + } + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap().for_each(drop); + + let items = entity_gateway.get_items_by_character(&char1).await; + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::Monomate + } + else { + false + } + }).count() == 1); + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::Monofluid + } + else { + false + } + }).count() == 2); +} + +#[async_std::test] +async fn test_use_monomate_twice() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + for (slot, tool) in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter().enumerate() { + for _ in 0..3 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }).await; + } + } + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap().for_each(drop); + + let items = entity_gateway.get_items_by_character(&char1).await; + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::Monomate + } + else { + false + } + }).count() == 1); + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::Monofluid + } + else { + false + } + }).count() == 3); +} + +#[async_std::test] +async fn test_use_last_monomate() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + for (slot, tool) in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].into_iter().enumerate() { + for _ in 0..1 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }).await; + } + } + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap().for_each(drop); + + let items = entity_gateway.get_items_by_character(&char1).await; + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::Monomate + } + else { + false + } + }).count() == 0); + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::Monofluid + } + else { + false + } + }).count() == 1); +} + +#[async_std::test] +async fn test_use_nonstackable_tool() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::MagicStoneIritista, + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 0, + equipped: false, + } + }).await; + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap().for_each(drop); + + let items = entity_gateway.get_items_by_character(&char1).await; + assert!(items.len() == 0); +} + +#[async_std::test] +async fn test_use_materials() { + let mut entity_gateway = InMemoryGateway::new(); + + let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + + for (slot, tool) in vec![item::tool::ToolType::PowerMaterial, item::tool::ToolType::LuckMaterial].into_iter().enumerate() { + for _ in 0..5 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: tool + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }).await; + } + } + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room(&mut ship, ClientId(1), "room", "").await; + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10000, + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10001, + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerUseItem(PlayerUseItem { + client: 0, + target: 0, + item_id: 0x10001, + })))).await.unwrap().for_each(drop); + + let items = entity_gateway.get_items_by_character(&char1).await; + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::PowerMaterial + } + else { + false + } + }).count() == 4); + assert!(items.iter().filter(|item| { + if let item::ItemDetail::Tool(t) = item.item { + t.tool == item::tool::ToolType::LuckMaterial + } + else { + false + } + }).count() == 3); + + let characters = entity_gateway.get_characters_by_user(&user1).await; + let char = characters[0].as_ref().unwrap(); + + assert!(char.materials.power == 1); + assert!(char.materials.luck == 2); +} +