// // 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.AttributeSystem; using MUnique.OpenMU.DataModel; using MUnique.OpenMU.DataModel.Attributes; 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 the wing milestone policy of - which wings a bot has earned /// at which level; the creation and equipping go through the regular persistence context and /// . /// [TestFixture] public class BotWingHandlerTest { private const short FirstTierWingNumber = 2; private const short SecondTierWingNumber = 5; private const short ThirdTierWingNumber = 36; /// The Cape of Lord lives in group 13, unlike all other wings (group 12). private const short CapeNumber = 30; private const byte CapeGroup = 13; /// /// Below the first milestone no wings are due. /// [Test] public async ValueTask PlansNothingBelowFirstMilestoneAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); AddWingDefinition(player, FirstTierWingNumber, Stats.PhysicalBaseDmg); player.Attributes![Stats.Level] = 179; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan, Is.Null); } /// /// At the milestones the earned tier is granted with the agreed item level and option level. /// /// The character level. /// The expected wing number. /// The expected item level of the grant. /// The expected level of the wing option. [TestCase(180, FirstTierWingNumber, 0, 3)] [TestCase(280, SecondTierWingNumber, 9, 4)] [TestCase(400, ThirdTierWingNumber, 15, 4)] public async ValueTask GrantsEarnedTierAtMilestoneAsync(int level, short expectedNumber, byte expectedItemLevel, int expectedOptionLevel) { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); AddWingDefinition(player, FirstTierWingNumber, Stats.PhysicalBaseDmg); AddWingDefinition(player, SecondTierWingNumber, Stats.PhysicalBaseDmg); AddWingDefinition(player, ThirdTierWingNumber, Stats.PhysicalBaseDmg); player.Attributes![Stats.Level] = level; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan, Is.Not.Null); Assert.That(plan!.Value.Definition.Number, Is.EqualTo(expectedNumber)); Assert.That(plan.Value.ItemLevel, Is.EqualTo(expectedItemLevel)); Assert.That(plan.Value.OptionLevel, Is.EqualTo(expectedOptionLevel)); } /// /// A bot re-levelling through the lower milestones after a reset keeps its better wings. /// [Test] public async ValueTask NeverDowngradesWornWingsAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); AddWingDefinition(player, FirstTierWingNumber, Stats.PhysicalBaseDmg); var secondTier = AddWingDefinition(player, SecondTierWingNumber, Stats.PhysicalBaseDmg); await WearWingsAsync(player, secondTier, 9).ConfigureAwait(false); player.Attributes![Stats.Level] = 200; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan, Is.Null); } /// /// Wearing the earned tier already: nothing to do. /// [Test] public async ValueTask PlansNothingWhenEarnedTierIsWornAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); var secondTier = AddWingDefinition(player, SecondTierWingNumber, Stats.PhysicalBaseDmg); await WearWingsAsync(player, secondTier, 9).ConfigureAwait(false); player.Attributes![Stats.Level] = 300; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan, Is.Null); } /// /// A bot which did not evolve into its master class yet is not qualified for the third tier /// wings and falls back to the best qualified tier. /// [Test] public async ValueTask FallsBackWhenThirdTierIsNotQualifiedAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); AddWingDefinition(player, SecondTierWingNumber, Stats.PhysicalBaseDmg); var thirdTier = AddWingDefinition(player, ThirdTierWingNumber, Stats.PhysicalBaseDmg); thirdTier.QualifiedCharacters.Clear(); player.Attributes![Stats.Level] = 400; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan, Is.Not.Null); Assert.That(plan!.Value.Definition.Number, Is.EqualTo(SecondTierWingNumber)); Assert.That(plan.Value.Tier, Is.EqualTo(2)); } /// /// The capes are the only pre-master wing of their classes: granted at the first milestone at +0 /// and re-granted as a fresh +9 cape at the second one. /// /// The item level of the worn cape. /// Whether a new cape is expected. [TestCase(0, true)] [TestCase(9, false)] public async ValueTask RegrantsCapeAtSecondMilestoneAsync(byte wornCapeLevel, bool expectsGrant) { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); var cape = AddWingDefinition(player, CapeNumber, Stats.PhysicalBaseDmg, CapeGroup); await WearWingsAsync(player, cape, wornCapeLevel).ConfigureAwait(false); player.Attributes![Stats.Level] = 280; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan.HasValue, Is.EqualTo(expectsGrant)); if (expectsGrant) { Assert.That(plan!.Value.Definition, Is.SameAs(cape)); Assert.That(plan.Value.ItemLevel, Is.EqualTo(9)); } } /// /// When a class qualifies for more than one pair (the Magic Gladiator may wear both Wings of /// Heaven and Satan), the pair whose option matches the fighting style wins. /// /// The bot's base energy (base strength is 28). /// The expected wing number. [TestCase(200, 1)] [TestCase(0, 2)] public async ValueTask PrefersWingsMatchingFightingStyleAsync(int baseEnergy, short expectedNumber) { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); AddWingDefinition(player, 1, Stats.WizardryBaseDmg); AddWingDefinition(player, 2, Stats.PhysicalBaseDmg); player.Attributes![Stats.BaseEnergy] = baseEnergy; player.Attributes[Stats.Level] = 180; var plan = BotWingHandler.PlanNextGrant(player); Assert.That(plan, Is.Not.Null); Assert.That(plan!.Value.Definition.Number, Is.EqualTo(expectedNumber)); } private static ItemDefinition AddWingDefinition(Player player, short number, AttributeDefinition optionTarget, byte group = 12) { var definitionMock = new Mock(); definitionMock.SetupAllProperties(); definitionMock.Setup(d => d.QualifiedCharacters).Returns(new List()); definitionMock.Setup(d => d.PossibleItemOptions).Returns(new List()); var slotType = new Mock(); slotType.Setup(s => s.ItemSlots).Returns(new List { InventoryConstants.WingsSlot }); definitionMock.Setup(d => d.ItemSlot).Returns(slotType.Object); var definition = definitionMock.Object; definition.Group = group; definition.Number = number; definition.Width = 5; definition.Height = 3; definition.Durability = 200; definition.MaximumItemLevel = 15; definition.QualifiedCharacters.Add(player.SelectedCharacter!.CharacterClass!); var optionDefinitionMock = new Mock(); optionDefinitionMock.SetupAllProperties(); optionDefinitionMock.Setup(o => o.PossibleOptions).Returns(new List()); var optionMock = new Mock(); optionMock.SetupAllProperties(); var powerUpMock = new Mock(); powerUpMock.SetupAllProperties(); powerUpMock.Object.TargetAttribute = optionTarget; optionMock.Object.OptionType = ItemOptionTypes.Option; optionMock.Object.PowerUpDefinition = powerUpMock.Object; optionDefinitionMock.Object.PossibleOptions.Add(optionMock.Object); definition.PossibleItemOptions.Add(optionDefinitionMock.Object); player.GameContext.Configuration.Items.Add(definition); return definition; } private static async ValueTask WearWingsAsync(Player player, ItemDefinition definition, byte level) { 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.Level = level; item.Durability = definition.Durability; await player.Inventory!.AddItemAsync(InventoryConstants.WingsSlot, item).ConfigureAwait(false); return item; } }