// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Tests; using Moq; using MUnique.OpenMU.DataModel; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.DataModel.Configuration.Items; using MUnique.OpenMU.DataModel.Entities; using MUnique.OpenMU.GameLogic; using MUnique.OpenMU.GameLogic.Attributes; using MUnique.OpenMU.GameLogic.Bots; /// /// Tests which gear a bot considers an upgrade (, also /// the pickup filter of the offline ) /// and the hand rule it shares with the engine (). /// [TestFixture] public class BotEquipmentHandlerTest { private const byte StaffGroup = 5; private const byte ShieldGroup = 6; private const byte ArmorGroup = 8; /// /// A better weapon of the bot's own fighting style is an upgrade worth picking up. The test player's /// class is energy-based, so its style is the staff (see ). /// [Test] public async ValueTask BetterWeaponIsAnUpgradeAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, StaffGroup, 1, dropLevel: 10), InventoryConstants.LeftHandSlot).ConfigureAwait(false); var better = CreateItem(CreateDefinition(player, StaffGroup, 2, dropLevel: 40)); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, better), Is.True); } /// /// A two-handed weapon needs the other hand: while a shield is worn, it is only worth it if it beats /// the weapon AND the shield it displaces. Without counting the shield, the bot took its gear off, /// had the equip refused by the engine (a two-hander needs the hand free), put the old weapon back on /// and started over - hundreds of swaps an hour, unarmed half of the time. /// [Test] public async ValueTask TwoHandedWeaponIsNoUpgradeWhenItLosesToWeaponAndShieldAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, StaffGroup, 1, dropLevel: 50), InventoryConstants.LeftHandSlot).ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, ShieldGroup, 1, dropLevel: 40, slot: InventoryConstants.RightHandSlot), InventoryConstants.RightHandSlot).ConfigureAwait(false); var twoHanded = CreateItem(CreateDefinition(player, StaffGroup, 3, dropLevel: 60, width: 2)); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, twoHanded), Is.False); } /// /// A two-handed weapon which beats the worn weapon and shield together is worth the swap - the bot /// frees the hand for it, like a player would. /// [Test] public async ValueTask TwoHandedWeaponIsAnUpgradeWhenItBeatsWeaponAndShieldAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, StaffGroup, 1, dropLevel: 50), InventoryConstants.LeftHandSlot).ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, ShieldGroup, 1, dropLevel: 40, slot: InventoryConstants.RightHandSlot), InventoryConstants.RightHandSlot).ConfigureAwait(false); var twoHanded = CreateItem(CreateDefinition(player, StaffGroup, 3, dropLevel: 120, width: 2)); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, twoHanded), Is.True); } /// /// Without a shield in the way, the same two-handed weapon is a welcome upgrade. /// [Test] public async ValueTask TwoHandedWeaponIsAnUpgradeWithFreeOffHandAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, StaffGroup, 1, dropLevel: 10), InventoryConstants.LeftHandSlot).ConfigureAwait(false); var twoHanded = CreateItem(CreateDefinition(player, StaffGroup, 3, dropLevel: 60, width: 2)); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, twoHanded), Is.True); } /// /// A weapon never goes into the free off-hand: a bot dual-wielding the junk weapons it happens to be /// qualified for is neither useful nor a sight a real character offers. /// [Test] public async ValueTask JunkWeaponIsNoUpgradeForTheFreeOffHandAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); await WearAsync(player, CreateDefinition(player, StaffGroup, 1, dropLevel: 50), InventoryConstants.LeftHandSlot).ConfigureAwait(false); var junk = CreateItem(CreateDefinition(player, StaffGroup, 2, dropLevel: 5)); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, junk), Is.False); } /// /// Gear the bot's class cannot wear is no upgrade, however good it is. /// [Test] public async ValueTask UnqualifiedGearIsNoUpgradeAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); var definition = CreateDefinition(player, ArmorGroup, 1, dropLevel: 80, slot: InventoryConstants.ArmorSlot); definition.QualifiedCharacters.Clear(); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, CreateItem(definition)), Is.False); } /// /// An empty armor slot takes any qualified piece - that is what makes a naked bot dress itself. /// [Test] public async ValueTask ArmorForAnEmptySlotIsAnUpgradeAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); var armor = CreateItem(CreateDefinition(player, ArmorGroup, 1, dropLevel: 20, slot: InventoryConstants.ArmorSlot)); Assert.That(BotEquipmentHandler.IsUpgradeFor(player, armor), Is.True); } private static ItemDefinition CreateDefinition(Player player, byte group, short number, byte dropLevel, byte width = 1, int? slot = null) { var definitionMock = new Mock(); definitionMock.SetupAllProperties(); definitionMock.Setup(d => d.QualifiedCharacters).Returns(new List()); definitionMock.Setup(d => d.PossibleItemOptions).Returns(new List()); definitionMock.Setup(d => d.BasePowerUpAttributes).Returns(new List()); definitionMock.Setup(d => d.Requirements).Returns(new List()); var slotTypeMock = new Mock(); var targetSlot = slot ?? InventoryConstants.LeftHandSlot; var slots = targetSlot == InventoryConstants.LeftHandSlot && group <= ShieldGroup && width < 2 ? new List { InventoryConstants.LeftHandSlot, InventoryConstants.RightHandSlot } : new List { targetSlot }; slotTypeMock.Setup(s => s.ItemSlots).Returns(slots); definitionMock.Setup(d => d.ItemSlot).Returns(slotTypeMock.Object); var definition = definitionMock.Object; definition.Group = group; definition.Number = number; definition.Width = width; definition.Height = 2; definition.Durability = 100; definition.DropLevel = dropLevel; definition.QualifiedCharacters.Add(player.SelectedCharacter!.CharacterClass!); player.GameContext.Configuration.Items.Add(definition); return definition; } private static Item CreateItem(ItemDefinition definition) { var itemMock = new Mock(); itemMock.SetupAllProperties(); itemMock.Setup(i => i.ItemOptions).Returns(new List()); itemMock.Setup(i => i.ItemSetGroups).Returns(new List()); var item = itemMock.Object; item.Definition = definition; item.Durability = definition.Durability; return item; } private static async ValueTask WearAsync(Player player, ItemDefinition definition, byte slot) { var item = CreateItem(definition); await player.Inventory!.AddItemAsync(slot, item).ConfigureAwait(false); return item; } }