// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Tests; 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.NPC; using MUnique.OpenMU.GameLogic.Resets; /// /// Tests for . /// [TestFixture] public class ResetCharacterActionTest { /// /// Verifies that reset is rejected when required items are missing, without charging zen. /// [Test] public async Task NotEnoughItemsRejectsResetAndKeepsZenAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); player.Attributes![Stats.Level] = 400; player.Money = 1_000; var requiredItem = new ItemDefinition { Name = "Jewel of Creation", Group = 14, Number = 22, Width = 1, Height = 1 }; var configuration = this.CreateConfiguration(requiredItem); player.GameContext.FeaturePlugIns.AddPlugIn(new ResetFeaturePlugIn { Configuration = configuration }, true); await AddRequiredItemsAsync(player, requiredItem, 1).ConfigureAwait(false); var action = new ResetCharacterAction(player, await CreateResetNpcAsync(player).ConfigureAwait(false)); await action.ResetCharacterAsync().ConfigureAwait(false); Assert.That((int)player.Attributes[Stats.Resets], Is.EqualTo(0)); Assert.That(player.Money, Is.EqualTo(1_000)); Assert.That(player.Inventory!.Items.Count(i => i.Definition == requiredItem), Is.EqualTo(1)); } /// /// Verifies that reset consumes configured zen and required items when all conditions are met. /// [Test] public async Task EnoughItemsAndZenConsumesCostsOnceAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); player.Attributes![Stats.Level] = 400; player.Money = 1_000; var requiredItem = new ItemDefinition { Name = "Jewel of Creation", Group = 14, Number = 22, Width = 1, Height = 1 }; var configuration = this.CreateConfiguration(requiredItem); player.GameContext.FeaturePlugIns.AddPlugIn(new ResetFeaturePlugIn { Configuration = configuration }, true); await AddRequiredItemsAsync(player, requiredItem, 2).ConfigureAwait(false); Assert.That(player.Level, Is.EqualTo(400)); Assert.That(player.Money, Is.EqualTo(1_000)); Assert.That( player.Inventory!.Items.Count(i => i.Definition is { } definition && definition.Group == requiredItem.Group && definition.Number == requiredItem.Number), Is.EqualTo(2)); var progression = ResetProgressionCalculator.Calculate((int)player.Attributes[Stats.Resets], (int)player.Attributes[Stats.PointsPerReset], configuration); Assert.That(progression.RequiredItemAmount, Is.EqualTo(2)); Assert.That(progression.RequiredZen, Is.EqualTo(500)); var action = new ResetCharacterAction(player, await CreateResetNpcAsync(player).ConfigureAwait(false)); await action.ResetCharacterAsync().ConfigureAwait(false); Assert.That((int)player.Attributes[Stats.Resets], Is.EqualTo(1)); Assert.That(player.Money, Is.EqualTo(500)); Assert.That(player.Inventory!.Items.Count(i => i.Definition == requiredItem), Is.EqualTo(0)); Assert.That(player.SelectedCharacter!.LevelUpPoints, Is.EqualTo(800)); Assert.That((int)player.Attributes[Stats.Level], Is.EqualTo(1)); } /// /// Verifies cumulative point replacement across multiple resets when point tiers are configured. /// [Test] public async Task ReplacePointsUsesCumulativeTierTotalAcrossResetsAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); player.Attributes![Stats.Level] = 400; player.Money = 2_000; var requiredItem = new ItemDefinition { Name = "Jewel of Creation", Group = 14, Number = 22, Width = 1, Height = 1 }; var configuration = this.CreateConfiguration(requiredItem); player.GameContext.FeaturePlugIns.AddPlugIn(new ResetFeaturePlugIn { Configuration = configuration }, true); await AddRequiredItemsAsync(player, requiredItem, 4).ConfigureAwait(false); var action = new ResetCharacterAction(player, await CreateResetNpcAsync(player).ConfigureAwait(false)); await action.ResetCharacterAsync().ConfigureAwait(false); Assert.That((int)player.Attributes[Stats.Resets], Is.EqualTo(1)); Assert.That(player.SelectedCharacter!.LevelUpPoints, Is.EqualTo(800)); Assert.That(player.Money, Is.EqualTo(1_500)); player.Attributes[Stats.Level] = 400; await action.ResetCharacterAsync().ConfigureAwait(false); Assert.That((int)player.Attributes[Stats.Resets], Is.EqualTo(2)); Assert.That(player.SelectedCharacter!.LevelUpPoints, Is.EqualTo(1_600)); Assert.That(player.Money, Is.EqualTo(500)); } /// /// Verifies add mode behavior when replace mode is disabled. /// [Test] public async Task TieredPointsAreAddedWhenReplaceIsDisabledAsync() { var player = await PlayerTestHelper.CreatePlayerAsync().ConfigureAwait(false); player.Attributes![Stats.Level] = 400; player.Money = 1_000; player.SelectedCharacter!.LevelUpPoints = 1_000; var requiredItem = new ItemDefinition { Name = "Jewel of Creation", Group = 14, Number = 22, Width = 1, Height = 1 }; var configuration = this.CreateConfiguration(requiredItem); configuration.ReplacePointsPerReset = false; player.GameContext.FeaturePlugIns.AddPlugIn(new ResetFeaturePlugIn { Configuration = configuration }, true); await AddRequiredItemsAsync(player, requiredItem, 2).ConfigureAwait(false); var action = new ResetCharacterAction(player, await CreateResetNpcAsync(player).ConfigureAwait(false)); await action.ResetCharacterAsync().ConfigureAwait(false); Assert.That((int)player.Attributes[Stats.Resets], Is.EqualTo(1)); Assert.That(player.SelectedCharacter!.LevelUpPoints, Is.EqualTo(1_800)); } private static async ValueTask AddRequiredItemsAsync(Player player, ItemDefinition requiredItem, int count) { for (byte i = 0; i < count; i++) { var item = player.PersistenceContext.CreateNew(); item.Definition = requiredItem; item.Durability = 1; var added = await player.Inventory!.AddItemAsync(item).ConfigureAwait(false); Assert.That(added, Is.True); } } private static async ValueTask CreateResetNpcAsync(Player player) { var spawnArea = new MonsterSpawnArea { X1 = 125, X2 = 125, Y1 = 125, Y2 = 125, }; var definition = new MonsterDefinition { Number = ResetCharacterNpcPlugin.ResetNpcNumber, ObjectKind = NpcObjectKind.PassiveNpc, Designation = "Reset Helper", }; var map = await player.GameContext.GetMapAsync(0).ConfigureAwait(false) ?? throw new InvalidOperationException("Could not resolve map 0 for NPC test setup."); return new NonPlayerCharacter(spawnArea, definition, map); } private ResetConfiguration CreateConfiguration(ItemDefinition requiredItem) { return new ResetConfiguration { RequiredLevel = 400, LevelAfterReset = 1, RequiredMoney = 500, MultiplyRequiredMoneyByResetCount = true, RequiredResetItem = requiredItem, ItemCostTiers = [ new() { MinimumResetCount = 1, RequiredItemAmount = 2 }, ], PointsTiers = [ new() { MinimumResetCount = 1, PointsGranted = 800 }, ], MoveHome = false, LogOut = false, ResetStats = false, ReplacePointsPerReset = true, }; } }