diff --git a/src/ship/items.rs b/src/ship/items.rs index eb49ae1..c08242a 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -21,16 +21,16 @@ struct RoomItemId(RoomId, u32); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ClientItemId(pub u32); -#[derive(Debug, Clone)] -enum ActiveItemEntityId { +#[derive(Debug, Clone, PartialEq)] +pub enum ActiveItemEntityId { Individual(ItemEntityId), Stacked(Vec), Meseta(Meseta), } -#[derive(Debug, Clone)] -enum HeldItemType { +#[derive(Debug, Clone, PartialEq)] +pub enum HeldItemType { Individual(ItemDetail), Stacked(Tool, usize), } @@ -89,10 +89,10 @@ impl FloorItemType { #[derive(Debug, Clone)] pub struct InventoryItem { - entity_id: ActiveItemEntityId, - item_id: ClientItemId, - item: HeldItemType, - equipped: bool, + pub entity_id: ActiveItemEntityId, + pub item_id: ClientItemId, + pub item: HeldItemType, + pub equipped: bool, } #[derive(Debug, Clone)] @@ -113,6 +113,7 @@ pub struct BankItem { } +#[derive(Debug)] pub struct CharacterInventory<'a>(&'a Vec); impl<'a> CharacterInventory<'a> { @@ -132,6 +133,10 @@ impl<'a> CharacterInventory<'a> { }) } + pub fn slot(&self, slot: usize) -> Option<&'a InventoryItem> { + self.0.get(slot) + } + pub fn count(&self) -> usize { self.0.len() } @@ -325,13 +330,46 @@ impl ItemManager { 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, - }; + 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| { + // TOOD: check stack amount does not exceed limit + 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 { @@ -345,7 +383,6 @@ impl ItemManager { }, }); // TODO: error check }; - inventory.push(inventory_item); } // else something went very wrong TODO: log it }, FloorItemType::Meseta(meseta) => { diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 0c39912..3f6a5de 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -243,7 +243,7 @@ pub struct ShipServerState { level_table: CharacterLevelTable, name: String, rooms: Rooms, - item_manager: items::ItemManager, + pub item_manager: items::ItemManager, quests: quests::QuestList, } diff --git a/tests/test_item_pickup.rs b/tests/test_item_pickup.rs new file mode 100644 index 0000000..182f53f --- /dev/null +++ b/tests/test_item_pickup.rs @@ -0,0 +1,216 @@ +use std::time::SystemTime; + +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::account::{UserAccountEntity, NewUserAccountEntity, NewUserSettingsEntity}; +use elseware::entity::character::{CharacterEntity, NewCharacterEntity}; +//use elseware::entity::item::{NewItemEntity, ItemDetail, ItemLocation}; +use elseware::entity::item; +use elseware::ship::ship::{ShipServerState, RecvShipPacket}; +use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType}; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; +use libpso::packet::login::{Login, Session}; +use libpso::{utf8_to_array, utf8_to_utf16_array}; + + +pub fn new_user_character(entity_gateway: &mut EG, username: &str, password: &str) -> (UserAccountEntity, CharacterEntity) { + let new_user = NewUserAccountEntity { + username: username.into(), + password: bcrypt::hash(password, 5).unwrap(), + guildcard: 1, + team_id: None, + banned: false, + muted_until: SystemTime::now(), + created_at: SystemTime::now(), + flags: 0, + }; + + let user = entity_gateway.create_user(new_user).unwrap(); + let new_settings = NewUserSettingsEntity::new(user.id); + let _settings = entity_gateway.create_user_settings(new_settings).unwrap(); + let new_character = NewCharacterEntity::new(user.id); + let character = entity_gateway.create_character(new_character).unwrap(); + + (user, character) +} + +pub fn log_in_char(ship: &mut ShipServerState, id: ClientId, username: &str, password: &str) { + let username = username.to_string(); + let password = password.to_string(); + ship.handle(id, &RecvShipPacket::Login(Login { + tag: 0, + guildcard: 0, + version: 0, + unknown1: [0; 6], + team: 0, + username: utf8_to_array!(username, 16), + unknown2: [0; 32], + password: utf8_to_array!(password, 16), + unknown3: [0; 40], + hwinfo: [0; 8], + session: Session::new(), + })).unwrap().for_each(drop); +} + +pub fn join_lobby(ship: &mut ShipServerState, id: ClientId) { + ship.handle(id, &RecvShipPacket::CharData(CharData { + _unknown: [0; 0x828] + })).unwrap().for_each(drop); +} + +pub fn create_room(ship: &mut ShipServerState, id: ClientId, name: &str, password: &str) { + ship.handle(id, &RecvShipPacket::CreateRoom(CreateRoom { + unknown: [0; 2], + name: utf8_to_utf16_array!(name, 16), + password: utf8_to_utf16_array!(password, 16), + difficulty: 0, + battle: 0, + challenge: 0, + episode: 1, + single_player: 0, + padding: [0; 3], + })).unwrap().for_each(drop); + ship.handle(id, &RecvShipPacket::DoneBursting(DoneBursting {})).unwrap().for_each(drop); +} + +pub fn join_room(ship: &mut ShipServerState, id: ClientId, room_id: u32) { + ship.handle(id, &RecvShipPacket::MenuSelect(MenuSelect { + menu: ROOM_MENU_ID, + item: room_id, + })).unwrap().for_each(drop); +} + + +#[test] +fn test_pick_up_item_stack_of_items_already_in_inventory() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a"); + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a"); + + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: 0, + equipped: false, + } + }); + + for (slot, tool) in vec![item::tool::ToolType::Monomate, item::tool::ToolType::Monofluid].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: char2.id, + slot: slot, + equipped: false, + } + }); + } + } + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a"); + log_in_char(&mut ship, ClientId(2), "a2", "a"); + + join_lobby(&mut ship, ClientId(1)); + join_lobby(&mut ship, ClientId(2)); + + create_room(&mut ship, ClientId(1), "room", ""); + join_room(&mut ship, ClientId(2), 0); + + ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDropItem(PlayerDropItem { + client: 0, + target: 0, + unknown1: 0, + area: 0, + item_id: 0x210000, + x: 0.0, + y: 0.0, + z: 0.0, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0x210000, + area: 0, + unknown: [0; 3] + })))).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)); +} + +#[test] +fn test_pick_up_item_stack_of_items_not_already_held() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a"); + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a"); + + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate + } + ), + location: item::ItemLocation::Inventory { + character_id: char2.id, + slot: 0, + equipped: false, + } + }); + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a"); + log_in_char(&mut ship, ClientId(2), "a2", "a"); + + join_lobby(&mut ship, ClientId(1)); + join_lobby(&mut ship, ClientId(2)); + + create_room(&mut ship, ClientId(1), "room", ""); + join_room(&mut ship, ClientId(2), 0); + + ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerDropItem(PlayerDropItem { + client: 0, + target: 0, + unknown1: 0, + area: 0, + item_id: 0x210000, + x: 0.0, + y: 0.0, + z: 0.0, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0x210000, + area: 0, + unknown: [0; 3] + })))).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)); +}