// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Tests; using Microsoft.Extensions.Logging.Abstractions; using Moq; using MUnique.OpenMU.AttributeSystem; using MUnique.OpenMU.DataModel; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.DataModel.Configuration.Items; using MUnique.OpenMU.DataModel.Entities; using MUnique.OpenMU.GameLogic.Attributes; using MUnique.OpenMU.GameLogic; using MUnique.OpenMU.GameLogic.NPC; using MUnique.OpenMU.GameLogic.PlayerActions.Items; using MUnique.OpenMU.GameLogic.PlayerActions.Trade; using MUnique.OpenMU.GameLogic.Views.Trade; using MUnique.OpenMU.Persistence.InMemory; using MUnique.OpenMU.PlugIns; /// /// Tests for . /// [TestFixture] public class MoveItemActionTests { /// /// Verifies that a complete stack move consumes the source item. /// [Test] public async ValueTask CompleteStackConsumesSourceItemAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var definition = CreateDefinition(1, 1, 10); var source = CreateItem(definition, 3); var target = CreateItem(definition, 2); await player.Inventory!.AddItemAsync(20, source).ConfigureAwait(false); await player.Inventory.AddItemAsync(21, target).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 20, Storages.Inventory, 21, Storages.Inventory).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(20), Is.Null); Assert.That(player.Inventory.GetItem(21), Is.SameAs(target)); Assert.That(target.Durability, Is.EqualTo(5)); Assert.That(player.Inventory.ItemStorage.Items.Count(i => ReferenceEquals(i, source)), Is.EqualTo(0)); Assert.That(player.Inventory.ItemStorage.Items.Count(i => ReferenceEquals(i, target)), Is.EqualTo(1)); } /// /// Verifies that completing a stack and relogging keeps a single persisted item. /// [Test] public async ValueTask CompleteStackAndRelogKeepsSinglePersistedItemAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var selectedCharacter = player.SelectedCharacter!; var definition = CreateDefinition(1, 1, 10); var source = CreateItem(definition, 3); var target = CreateItem(definition, 2); await player.Inventory!.AddItemAsync(20, source).ConfigureAwait(false); await player.Inventory.AddItemAsync(21, target).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 20, Storages.Inventory, 21, Storages.Inventory).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(20), Is.Null); Assert.That(player.Inventory.GetItem(21), Is.Not.Null); Assert.That(player.Inventory.GetItem(21)!.Durability, Is.EqualTo(5)); await player.RemoveFromGameAsync().ConfigureAwait(false); await player.SetSelectedCharacterAsync(selectedCharacter).ConfigureAwait(false); var persistedTarget = player.Inventory!.GetItem(21); Assert.That(persistedTarget, Is.Not.Null); Assert.That(persistedTarget!.Durability, Is.EqualTo(5)); Assert.That(player.Inventory.GetItem(20), Is.Null); Assert.That(player.Inventory.Items.Count(i => i.Definition == definition), Is.EqualTo(1)); } /// /// Verifies that a failed move to an occupied slot keeps the source at its original slot. /// [Test] public async ValueTask FailedMoveToOccupiedSlotKeepsSourceAtOriginalSlotAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var source = CreateItem(CreateDefinition(), 1); var blocker = CreateItem(CreateDefinition(2, 2), 1); await player.Inventory!.AddItemAsync(30, source).ConfigureAwait(false); await player.Inventory.AddItemAsync(20, blocker).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 30, Storages.Inventory, 21, Storages.Inventory).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(30), Is.SameAs(source)); Assert.That(player.Inventory.GetItem(20), Is.SameAs(blocker)); Assert.That(player.Inventory.GetItem(21), Is.Null); } /// /// Verifies that a failed vault to inventory move keeps the item in the vault. /// [Test] public async ValueTask FailedVaultToInventoryMoveKeepsItemInVaultAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var vaultStorage = CreateVaultStorage(); player.Vault = vaultStorage; player.IsVaultLocked = true; var source = CreateItem(CreateDefinition(), 1); await vaultStorage.AddItemAsync(0, source).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 0, Storages.Vault, 20, Storages.Inventory).ConfigureAwait(false); Assert.That(vaultStorage.GetItem(0), Is.SameAs(source)); Assert.That(player.Inventory!.GetItem(20), Is.Null); } /// /// Verifies that a move to a slot outside grid bounds is rejected without mutation. /// [Test] public async ValueTask MoveToSlotOutsideGridBoundsIsRejectedWithoutMutationAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var source = CreateItem(CreateDefinition(2, 1), 1); await player.Inventory!.AddItemAsync(20, source).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 20, Storages.Inventory, 27, Storages.Inventory).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(20), Is.SameAs(source)); Assert.That(player.Inventory.GetItem(27), Is.Null); Assert.That(source.ItemSlot, Is.EqualTo(20)); } /// /// Verifies that a move request in an invalid player state is rejected without mutation. /// [Test] public async ValueTask MoveRequestInInvalidPlayerStateIsRejectedWithoutMutationAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var source = CreateItem(CreateDefinition(), 1); await player.Inventory!.AddItemAsync(20, source).ConfigureAwait(false); Assert.That(await player.PlayerState.TryAdvanceToAsync(PlayerState.CharacterSelection).ConfigureAwait(false), Is.True); var action = new MoveItemAction(); await action.MoveItemAsync(player, 20, Storages.Inventory, 22, Storages.Inventory).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(20), Is.SameAs(source)); Assert.That(player.Inventory.GetItem(22), Is.Null); } /// /// Verifies that a move to trade storage outside of a trade is rejected without mutation. /// [Test] public async ValueTask MoveToTradeStorageOutsideTradeIsRejectedWithoutMutationAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var source = CreateItem(CreateDefinition(), 1); await player.Inventory!.AddItemAsync(20, source).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 20, Storages.Inventory, 0, Storages.Trade).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(20), Is.SameAs(source)); Assert.That(player.TemporaryStorage!.Items, Does.Not.Contain(source)); } /// /// Verifies that a move request when the trade button is pressed is rejected without mutation. /// [Test] public async ValueTask MoveRequestInTradeButtonPressedStateIsRejectedWithoutMutationAsync() { var trader1 = await CreateTestPlayerAsync().ConfigureAwait(false); var trader2 = await CreateTestPlayerAsync().ConfigureAwait(false); var tradeRequestAction = new TradeRequestAction(); var tradeResponseAction = new TradeAcceptAction(); var tradeButtonAction = new TradeButtonAction(); var itemInTrade = CreateItem(CreateDefinition(), 1); var blockedMoveItem = CreateItem(CreateDefinition(), 1); await trader1.Inventory!.AddItemAsync(20, itemInTrade).ConfigureAwait(false); await trader1.Inventory.AddItemAsync(21, blockedMoveItem).ConfigureAwait(false); await tradeRequestAction.RequestTradeAsync(trader1, trader2).ConfigureAwait(false); await tradeResponseAction.HandleTradeAcceptAsync(trader2, true).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(trader1, 20, Storages.Inventory, 0, Storages.Trade).ConfigureAwait(false); await tradeButtonAction.TradeButtonChangedAsync(trader1, TradeButtonState.Checked).ConfigureAwait(false); Assert.That(trader1.PlayerState.CurrentState, Is.EqualTo(PlayerState.TradeButtonPressed)); await action.MoveItemAsync(trader1, 21, Storages.Inventory, 1, Storages.Trade).ConfigureAwait(false); Assert.That(trader1.Inventory.GetItem(21), Is.SameAs(blockedMoveItem)); Assert.That(trader1.TemporaryStorage!.GetItem(1), Is.Null); } /// /// Verifies that logging out and back in after an inventory to vault move keeps a single persisted copy. /// [Test] public async ValueTask LogoutAndRelogAfterInventoryToVaultMoveKeepsSinglePersistedCopyAsync() { var player = await CreateTestPlayerAsync().ConfigureAwait(false); var selectedCharacter = player.SelectedCharacter!; var vaultStorage = CreateVaultStorage(); player.Vault = vaultStorage; player.OpenedNpc = new NonPlayerCharacter(null!, new MonsterDefinition { NpcWindow = NpcWindow.VaultStorage }, null!); Assert.That(await player.PlayerState.TryAdvanceToAsync(PlayerState.NpcDialogOpened).ConfigureAwait(false), Is.True); var movedItem = CreateItem(CreateDefinition(), 1); await player.Inventory!.AddItemAsync(20, movedItem).ConfigureAwait(false); var action = new MoveItemAction(); await action.MoveItemAsync(player, 20, Storages.Inventory, 0, Storages.Vault).ConfigureAwait(false); Assert.That(player.Inventory.GetItem(20), Is.Null); Assert.That(vaultStorage.GetItem(0), Is.SameAs(movedItem)); await player.RemoveFromGameAsync().ConfigureAwait(false); await player.SetSelectedCharacterAsync(selectedCharacter).ConfigureAwait(false); Assert.That(player.Inventory!.Items.Count(i => ReferenceEquals(i, movedItem)), Is.EqualTo(0)); Assert.That(vaultStorage.Items.Count(i => ReferenceEquals(i, movedItem)), Is.EqualTo(1)); } private static Storage CreateVaultStorage() { var itemStorage = new Mock(); itemStorage.Setup(i => i.Items).Returns(new List()); return new Storage(InventoryConstants.WarehouseSize, itemStorage.Object); } private static async ValueTask CreateTestPlayerAsync() { var gameConfig = new Mock(); gameConfig.SetupAllProperties(); gameConfig.Setup(c => c.Maps).Returns(new List()); gameConfig.Setup(c => c.Items).Returns(new List()); gameConfig.Setup(c => c.Skills).Returns(new List()); gameConfig.Setup(c => c.PlugInConfigurations).Returns(new List()); gameConfig.Setup(c => c.CharacterClasses).Returns(new List()); gameConfig.Setup(c => c.Attributes).Returns(new List()); gameConfig.Setup(c => c.GlobalAttributeCombinations).Returns(new List()); gameConfig.Setup(c => c.GlobalBaseAttributeValues).Returns(new List { new(1, Stats.MoneyAmountRate), }); var map = new Mock(); map.SetupAllProperties(); map.Setup(m => m.DropItemGroups).Returns(new List()); map.Setup(m => m.MonsterSpawns).Returns(new List()); map.Object.TerrainData = new byte[ushort.MaxValue + 3]; gameConfig.Object.RecoveryInterval = int.MaxValue; gameConfig.Object.Maps.Add(map.Object); var mapInitializer = new MapInitializer(gameConfig.Object, new NullLogger(), NullDropGenerator.Instance, null); var gameContext = new GameContext( gameConfig.Object, new InMemoryPersistenceContextProvider(), mapInitializer, new NullLoggerFactory(), new PlugInManager(null, new NullLoggerFactory(), null, null), NullDropGenerator.Instance, new ConfigurationChangeMediator()); mapInitializer.PlugInManager = gameContext.PlugInManager; mapInitializer.PathFinderPool = gameContext.PathFinderPool; return await PlayerTestHelper.CreatePlayerAsync(gameContext).ConfigureAwait(false); } private static ItemDefinition CreateDefinition(byte width = 1, byte height = 1, byte durability = 1) { return new ItemDefinition { Width = width, Height = height, Durability = durability, }; } private static Item CreateItem(ItemDefinition definition, double durability, byte level = 0) { var item = new Mock(); item.SetupAllProperties(); item.Setup(i => i.ItemOptions).Returns(new List()); item.Setup(i => i.ItemSetGroups).Returns(new List()); item.Object.Definition = definition; item.Object.Durability = durability; item.Object.Level = level; return item.Object; } }