diff --git a/src/ship/items.rs b/src/ship/items.rs index c08242a..87df145 100644 --- a/src/ship/items.rs +++ b/src/ship/items.rs @@ -56,7 +56,7 @@ impl HeldItemType { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum FloorItemType { Individual(ItemDetail), Stacked(Tool, usize), @@ -290,6 +290,35 @@ 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) => { + 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 @@ -304,10 +333,6 @@ impl ItemManager { return Err(ItemManagerError::NoSuchItemId(floor_item.item_id)) } - if inventory.len() >= 30 { - return Err(ItemManagerError::CouldNotAddToInventory(floor_item)); - } - match floor_item.item { FloorItemType::Individual(item) => { let inventory_item = InventoryItem { @@ -342,7 +367,6 @@ impl ItemManager { }) .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) @@ -386,7 +410,7 @@ impl ItemManager { } // else something went very wrong TODO: log it }, FloorItemType::Meseta(meseta) => { - character.meseta += meseta.0; + character.meseta = std::cmp::min(character.meseta + meseta.0, 999999); entity_gateway.save_character(&character); } } diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index d1b2fa8..36be335 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -190,4 +190,4 @@ EG: EntityGateway .collect::>(); // TODO: can EntityGateway be Sync? Ok(Box::new(item_drop_packets.into_iter())) -} \ No newline at end of file +} diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 90edcb7..9749178 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -238,7 +238,7 @@ impl ClientState { pub struct ShipServerState { entity_gateway: EG, - clients: Clients, + pub clients: Clients, client_location: ClientLocation, level_table: CharacterLevelTable, name: String, diff --git a/tests/test_item_pickup.rs b/tests/test_item_pickup.rs index 182f53f..9df6818 100644 --- a/tests/test_item_pickup.rs +++ b/tests/test_item_pickup.rs @@ -7,7 +7,7 @@ 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 elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType}; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -121,7 +121,7 @@ fn test_pick_up_item_stack_of_items_already_in_inventory() { }); } } - + 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"); @@ -214,3 +214,471 @@ fn test_pick_up_item_stack_of_items_not_already_held() { 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)); } + +#[test] +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"); + let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a"); + + for slot in 0..30 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }); + } + + char2.meseta = 300; + entity_gateway.save_character(&char2); + + 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::DropCoordinates(DropCoordinates { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + map_area: 0, + x: 0.0, + z: 0.0, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + amount: 23, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0xF0000001, + area: 0, + unknown: [0; 3] + })))).unwrap().for_each(drop); + + let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap(); + assert!(p1_inventory.count() == 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); +} + +#[test] +fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() { + 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"); + + for slot in 0..29 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }); + } + + 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: 29, + equipped: false, + } + }); + 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() == 30); + + + let monomates = p1_inventory.slot(29).unwrap(); + assert!(monomates.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 2)); +} + +#[test] +fn test_can_not_pick_up_item_when_inventory_full() { + 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"); + + for slot in 0..30 { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + location: item::ItemLocation::Inventory { + character_id: char1.id, + slot: slot, + equipped: false, + } + }); + } + + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + 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() == 30); + let floor_item = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0x210000)).unwrap(); + assert!(floor_item.item_id == ClientItemId(0x210000)); +} + +#[test] +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"); + + char1.meseta = 300; + entity_gateway.save_character(&char1); + + let mut ship = ShipServerState::new(entity_gateway.clone()); + log_in_char(&mut ship, ClientId(1), "a1", "a"); + + join_lobby(&mut ship, ClientId(1)); + + create_room(&mut ship, ClientId(1), "room", ""); + + ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::DropCoordinates(DropCoordinates { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + map_area: 0, + x: 0.0, + z: 0.0, + })))).unwrap().for_each(drop); + + let split_attempt = ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + amount: 301, + })))); + 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()) +} + +#[test] +fn test_pick_up_stack_that_would_exceed_stack_limit() { + 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"); + + for _ in 0..6 { + 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 _ in 0..6 { + 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(); + 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)); +} + +#[test] +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"); + let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a"); + + char1.meseta = 999999; + entity_gateway.save_character(&char1); + char2.meseta = 300; + entity_gateway.save_character(&char2); + + 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::DropCoordinates(DropCoordinates { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + map_area: 0, + x: 0.0, + z: 0.0, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + amount: 23, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0xF0000001, + area: 0, + unknown: [0; 3] + })))).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))); +} + +#[test] +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"); + let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a"); + + char1.meseta = 999998; + entity_gateway.save_character(&char1); + char2.meseta = 300; + entity_gateway.save_character(&char2); + + 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::DropCoordinates(DropCoordinates { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + map_area: 0, + x: 0.0, + z: 0.0, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::Message(Message::new(GameMessage::PlayerSplitItemStack(PlayerSplitItemStack { + client: 0, + target: 0, + item_id: 0xFFFFFFFF, + amount: 23, + })))).unwrap().for_each(drop); + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { + client: 0, + target: 0, + item_id: 0xF0000001, + area: 0, + unknown: [0; 3] + })))).unwrap().for_each(drop); + + let c1 = ship.clients.get(&ClientId(1)).unwrap(); + let c2 = ship.clients.get(&ClientId(2)).unwrap(); + println!("{}", c1.character.meseta); + 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()); +}