baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
77
tests/MUnique.OpenMU.Tests/Offline/BuffHandlerTests.cs
Normal file
77
tests/MUnique.OpenMU.Tests/Offline/BuffHandlerTests.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// <copyright file="BuffHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using NUnit.Framework;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="BuffHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class BuffHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="BuffHandler.PerformBuffsAsync"/> returns true immediately
|
||||
/// when no buff skills are configured.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask ReturnsTrueWhenNoBuffsConfiguredAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
BuffSkill0Id = 0,
|
||||
BuffSkill1Id = 0,
|
||||
BuffSkill2Id = 0,
|
||||
};
|
||||
|
||||
var handler = new BuffHandler(player, config);
|
||||
|
||||
// Act
|
||||
var result = await handler.PerformBuffsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="BuffHandler.PerformBuffsAsync"/> returns true when config is null.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask ReturnsTrueWhenConfigNullAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var handler = new BuffHandler(player, null);
|
||||
|
||||
// Act
|
||||
var result = await handler.PerformBuffsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
160
tests/MUnique.OpenMU.Tests/Offline/CombatHandlerTests.cs
Normal file
160
tests/MUnique.OpenMU.Tests/Offline/CombatHandlerTests.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
// <copyright file="CombatHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using Moq;
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
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.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MonsterDefinition = MUnique.OpenMU.Persistence.BasicModel.MonsterDefinition;
|
||||
using MonsterAttribute = MUnique.OpenMU.Persistence.BasicModel.MonsterAttribute;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="CombatHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class CombatHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
private readonly Point _origin = new(100, 100);
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="CombatHandler.PerformAttackAsync"/> moves closer to the target if out of range.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask PerformAttackAsync_MovesCloserToTargetAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
player.Position = this._origin;
|
||||
|
||||
var monster = await this.CreateMonsterAsync(new Point(105, 105)).ConfigureAwait(false);
|
||||
await player.CurrentMap!.AddAsync(monster).ConfigureAwait(false);
|
||||
|
||||
var config = new MuHelperSettings { HuntingRange = 10 };
|
||||
var movementHandler = new MovementHandler(player, config, this._origin);
|
||||
|
||||
var handler = new CombatHandler(player, config, movementHandler, this._origin);
|
||||
|
||||
// Act
|
||||
await handler.PerformAttackAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
// Should have initiated a walk closer to the monster
|
||||
Assert.That(player.IsWalking, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="CombatHandler.PerformDrainLifeRecoveryAsync"/> uses Drain Life when HP is low.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask PerformDrainLifeRecoveryAsync_UsesDrainLifeWhenLowHpAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
player.Position = this._origin;
|
||||
player.Attributes![Stats.CurrentHealth] = 10; // Low HP
|
||||
player.Attributes[Stats.MaximumHealth] = 100;
|
||||
|
||||
// Place monster slightly away from player so GetDirectionTo doesn't return Undefined
|
||||
var monsterPosition = new Point((byte)(this._origin.X + 1), this._origin.Y);
|
||||
var monster = await this.CreateMonsterAsync(monsterPosition).ConfigureAwait(false);
|
||||
await player.CurrentMap!.AddAsync(monster).ConfigureAwait(false);
|
||||
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
UseDrainLife = true,
|
||||
HealThresholdPercent = 50,
|
||||
HuntingRange = 10
|
||||
};
|
||||
|
||||
// Add Drain Life skill to player
|
||||
var drainSkill = new TestSkill
|
||||
{
|
||||
Number = 214,
|
||||
};
|
||||
await player.SkillList!.AddLearnedSkillAsync(drainSkill).ConfigureAwait(false);
|
||||
|
||||
var movementHandler = new MovementHandler(player, config, this._origin);
|
||||
|
||||
var handler = new CombatHandler(player, config, movementHandler, this._origin);
|
||||
|
||||
// Act
|
||||
await handler.PerformDrainLifeRecoveryAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
// We verify that rotation was updated, which happens during ExecuteAttackAsync
|
||||
Assert.That(player.Rotation, Is.Not.EqualTo(default(Direction)));
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask<Monster> CreateMonsterAsync(Point position)
|
||||
{
|
||||
var monsterDefinition = new MonsterDefinition
|
||||
{
|
||||
ObjectKind = NpcObjectKind.Monster,
|
||||
};
|
||||
monsterDefinition.Attributes.Add(new MonsterAttribute { AttributeDefinition = Stats.MaximumHealth, Value = 1000 });
|
||||
monsterDefinition.Attributes.Add(new MonsterAttribute { AttributeDefinition = Stats.DefenseBase, Value = 100 });
|
||||
|
||||
var map = await this._gameContext.GetMapAsync(0).ConfigureAwait(false)!;
|
||||
var spawnArea = new MonsterSpawnArea
|
||||
{
|
||||
MonsterDefinition = monsterDefinition,
|
||||
GameMap = map!.Definition,
|
||||
X1 = position.X,
|
||||
Y1 = position.Y,
|
||||
X2 = position.X,
|
||||
Y2 = position.Y,
|
||||
Quantity = 1,
|
||||
};
|
||||
|
||||
var monster = new Monster(
|
||||
spawnArea,
|
||||
monsterDefinition,
|
||||
map,
|
||||
NullDropGenerator.Instance,
|
||||
new Mock<INpcIntelligence>().Object,
|
||||
this._gameContext.PlugInManager,
|
||||
this._gameContext.PathFinderPool);
|
||||
|
||||
monster.Initialize();
|
||||
monster.Attributes[Stats.CurrentHealth] = 100;
|
||||
|
||||
return monster;
|
||||
}
|
||||
|
||||
private class TestSkill : Skill
|
||||
{
|
||||
public TestSkill()
|
||||
{
|
||||
this.Requirements = new List<AttributeRequirement>();
|
||||
this.ConsumeRequirements = new List<AttributeRequirement>();
|
||||
this.Range = 1;
|
||||
this.SkillType = SkillType.DirectHit;
|
||||
this.DamageType = DamageType.Curse;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
tests/MUnique.OpenMU.Tests/Offline/HealingHandlerTests.cs
Normal file
103
tests/MUnique.OpenMU.Tests/Offline/HealingHandlerTests.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
// <copyright file="HealingHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using Moq;
|
||||
using MUnique.OpenMU.DataModel;
|
||||
using MUnique.OpenMU.DataModel.Configuration.Items;
|
||||
using MUnique.OpenMU.DataModel.Entities;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="HealingHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class HealingHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="HealingHandler.PerformHealthRecoveryAsync"/> does nothing
|
||||
/// when config is null.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNothingWhenConfigNullAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var handler = new HealingHandler(player, null);
|
||||
|
||||
// Act
|
||||
await handler.PerformHealthRecoveryAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="HealingHandler.PerformHealthRecoveryAsync"/> does not consume
|
||||
/// a potion when the player's HP is above the threshold.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNotUsePotionAboveThresholdAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var maxHp = player.Attributes![Stats.MaximumHealth];
|
||||
player.Attributes[Stats.CurrentHealth] = maxHp * 0.9f;
|
||||
|
||||
var potion = this.CreateHealthPotion();
|
||||
await player.Inventory!.AddItemAsync((byte)(InventoryConstants.FirstEquippableItemSlotIndex + 12), potion).ConfigureAwait(false);
|
||||
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
UseHealPotion = true,
|
||||
PotionThresholdPercent = 50,
|
||||
};
|
||||
|
||||
var handler = new HealingHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformHealthRecoveryAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.Inventory?.GetItem(potion.ItemSlot), Is.Not.Null);
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private MUnique.OpenMU.DataModel.Entities.Item CreateHealthPotion()
|
||||
{
|
||||
var definition = new Mock<MUnique.OpenMU.DataModel.Configuration.Items.ItemDefinition>();
|
||||
definition.SetupAllProperties();
|
||||
definition.Setup(d => d.BasePowerUpAttributes).Returns(new List<ItemBasePowerUpDefinition>());
|
||||
definition.Setup(d => d.PossibleItemOptions).Returns(new List<ItemOptionDefinition>());
|
||||
definition.Object.Number = ItemConstants.SmallHealingPotion.Number!.Value;
|
||||
definition.Object.Group = ItemConstants.SmallHealingPotion.Group;
|
||||
|
||||
var item = new Mock<MUnique.OpenMU.DataModel.Entities.Item>();
|
||||
item.SetupAllProperties();
|
||||
item.Setup(i => i.Definition).Returns(definition.Object);
|
||||
item.Setup(i => i.ItemOptions).Returns(new List<ItemOptionLink>());
|
||||
item.Setup(i => i.ItemSetGroups).Returns(new List<ItemOfItemSet>());
|
||||
item.Object.Durability = 1;
|
||||
|
||||
return item.Object;
|
||||
}
|
||||
|
||||
}
|
||||
134
tests/MUnique.OpenMU.Tests/Offline/ItemPickupHandlerTests.cs
Normal file
134
tests/MUnique.OpenMU.Tests/Offline/ItemPickupHandlerTests.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
// <copyright file="ItemPickupHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using Moq;
|
||||
using MUnique.OpenMU.DataModel.Configuration.Items;
|
||||
using MUnique.OpenMU.DataModel.Entities;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="ItemPickupHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ItemPickupHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pickup does nothing when all pickup options are disabled.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNothingWhenAllDisabledAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
PickAllItems = false,
|
||||
PickJewel = false,
|
||||
PickAncient = false,
|
||||
PickZen = false,
|
||||
PickExcellent = false,
|
||||
};
|
||||
|
||||
var handler = new ItemPickupHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PickupItemsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pickup does nothing when config is null.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNothingWhenConfigNullAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var handler = new ItemPickupHandler(player, null);
|
||||
|
||||
// Act
|
||||
await handler.PickupItemsAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Item CreateItem(byte group, short number)
|
||||
{
|
||||
var definition = new Mock<ItemDefinition>();
|
||||
definition.SetupAllProperties();
|
||||
definition.Object.Group = group;
|
||||
definition.Object.Number = number;
|
||||
definition.Object.Width = 1;
|
||||
definition.Object.Height = 1;
|
||||
|
||||
var item = new Mock<Item>();
|
||||
item.SetupAllProperties();
|
||||
item.Setup(i => i.Definition).Returns(definition.Object);
|
||||
item.Setup(i => i.ItemOptions).Returns(new List<ItemOptionLink>());
|
||||
item.Setup(i => i.ItemSetGroups).Returns(new List<ItemOfItemSet>());
|
||||
return item.Object;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that a jewel pickup option only picks up actual jewels.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask PickJewel_PicksUpJewelsOnlyAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
Assert.That(player.CurrentMap, Is.Not.Null);
|
||||
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
PickSelectItems = true,
|
||||
PickJewel = true,
|
||||
ObtainRange = 5,
|
||||
};
|
||||
|
||||
var handler = new ItemPickupHandler(player, config);
|
||||
|
||||
var bless = this.CreateItem(14, 13);
|
||||
var chaos = this.CreateItem(12, 15);
|
||||
var potion = this.CreateItem(14, 0);
|
||||
var eye = this.CreateItem(14, 17);
|
||||
|
||||
var blessDrop = new DroppedItem(bless, player.Position, player.CurrentMap!, player);
|
||||
var chaosDrop = new DroppedItem(chaos, player.Position, player.CurrentMap!, player);
|
||||
var potionDrop = new DroppedItem(potion, player.Position, player.CurrentMap!, player);
|
||||
var eyeDrop = new DroppedItem(eye, player.Position, player.CurrentMap!, player);
|
||||
|
||||
await player.CurrentMap!.AddAsync(blessDrop).ConfigureAwait(false);
|
||||
await player.CurrentMap.AddAsync(chaosDrop).ConfigureAwait(false);
|
||||
await player.CurrentMap.AddAsync(potionDrop).ConfigureAwait(false);
|
||||
await player.CurrentMap.AddAsync(eyeDrop).ConfigureAwait(false);
|
||||
|
||||
// Act
|
||||
await handler.PickupItemsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.CurrentMap.GetObject(blessDrop.Id), Is.Null, "Bless should be picked up");
|
||||
Assert.That(player.CurrentMap.GetObject(chaosDrop.Id), Is.Null, "Chaos should be picked up");
|
||||
Assert.That(player.CurrentMap.GetObject(potionDrop.Id), Is.Not.Null, "Potion should NOT be picked up");
|
||||
Assert.That(player.CurrentMap.GetObject(eyeDrop.Id), Is.Not.Null, "Devil's Eye should NOT be picked up");
|
||||
}
|
||||
}
|
||||
61
tests/MUnique.OpenMU.Tests/Offline/MovementHandlerTests.cs
Normal file
61
tests/MUnique.OpenMU.Tests/Offline/MovementHandlerTests.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
// <copyright file="MovementHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="MovementHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class MovementHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="MovementHandler.RegroupAsync"/> does nothing when within range.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask RegroupAsync_DoesNothingWhenWithinRangeAsync()
|
||||
{
|
||||
// Arrange
|
||||
var origin = new Point(100, 100);
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
player.Position = new Point(100, 101); // Within RegroupDistanceThreshold (1)
|
||||
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
ReturnToOriginalPosition = true,
|
||||
HuntingRange = 5,
|
||||
};
|
||||
|
||||
var handler = new MovementHandler(player, config, origin);
|
||||
|
||||
// Act
|
||||
var result = await handler.RegroupAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(player.IsWalking, Is.False);
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
146
tests/MUnique.OpenMU.Tests/Offline/OfflinePlayerManagerTests.cs
Normal file
146
tests/MUnique.OpenMU.Tests/Offline/OfflinePlayerManagerTests.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
// <copyright file="OfflinePlayerManagerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="OfflinePlayerManager"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class OfflinePlayerManagerTests
|
||||
{
|
||||
private const string TestUserLoginName = "test";
|
||||
private const string TestCharacterName = "testChar";
|
||||
|
||||
private IGameContext _gameContext = null!;
|
||||
private IPersistenceContextProvider _contextProvider = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
this._contextProvider = this._gameContext.PersistenceContextProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="OfflinePlayerManager.StartAsync"/> returns true on success.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask StartAsync_WithValidPlayer_ReturnsTrueAsync()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new OfflinePlayerManager();
|
||||
var realPlayer = await this.CreatePlayerWithPersistedAccountAsync().ConfigureAwait(false);
|
||||
realPlayer.TryAddMoney(1_000_000);
|
||||
realPlayer.Attributes![Stats.Level] = 100;
|
||||
|
||||
// Act
|
||||
var result = await manager.StartAsync(realPlayer, TestUserLoginName).ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(manager.IsActive(TestUserLoginName), Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="OfflinePlayerManager.StartAsync"/> fails if the player has insufficient Zen.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask StartAsync_WithInsufficientZen_ReturnsFalseAsync()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new OfflinePlayerManager();
|
||||
var realPlayer = await this.CreatePlayerWithPersistedAccountAsync().ConfigureAwait(false);
|
||||
realPlayer.Money = 0; // No money
|
||||
realPlayer.Attributes![Stats.Level] = 100;
|
||||
|
||||
// Act
|
||||
var result = await manager.StartAsync(realPlayer, TestUserLoginName).ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
Assert.That(manager.IsActive(TestUserLoginName), Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="OfflinePlayerManager.StopAsync"/> successfully stops a session.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask StopAsync_WhenSessionActive_StopsSuccessfullyAsync()
|
||||
{
|
||||
// Arrange
|
||||
var manager = new OfflinePlayerManager();
|
||||
var realPlayer = await this.CreatePlayerWithPersistedAccountAsync().ConfigureAwait(false);
|
||||
realPlayer.TryAddMoney(1_000_000);
|
||||
realPlayer.Attributes![Stats.Level] = 100;
|
||||
|
||||
var started = await manager.StartAsync(realPlayer, TestUserLoginName).ConfigureAwait(false);
|
||||
Assert.That(started, Is.True);
|
||||
Assert.That(manager.IsActive(TestUserLoginName), Is.True);
|
||||
|
||||
// Act
|
||||
await manager.StopAsync(TestUserLoginName).ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(manager.IsActive(TestUserLoginName), Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a player whose account is stored in the in-memory repository,
|
||||
/// so that <see cref="OfflinePlayer.InitializeAsync"/> can find it.
|
||||
/// </summary>
|
||||
private async ValueTask<Player> CreatePlayerWithPersistedAccountAsync()
|
||||
{
|
||||
var config = this._gameContext.Configuration;
|
||||
|
||||
// Create persisted account + character in the in-memory repository.
|
||||
using (var ctx = this._contextProvider.CreateNewPlayerContext(config))
|
||||
{
|
||||
var account = ctx.CreateNew<MUnique.OpenMU.DataModel.Entities.Account>();
|
||||
account.LoginName = TestUserLoginName;
|
||||
|
||||
var character = ctx.CreateNew<MUnique.OpenMU.DataModel.Entities.Character>();
|
||||
character.Name = TestCharacterName;
|
||||
|
||||
if (config.CharacterClasses.FirstOrDefault() is { } existingClass)
|
||||
{
|
||||
character.CharacterClass = existingClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Build a minimal CharacterClass so OnPlayerEnteredWorldAsync does not throw.
|
||||
var characterClass = ctx.CreateNew<MUnique.OpenMU.DataModel.Configuration.CharacterClass>();
|
||||
characterClass.HomeMap = config.Maps.FirstOrDefault();
|
||||
character.CharacterClass = characterClass;
|
||||
}
|
||||
|
||||
account.Characters.Add(character);
|
||||
await ctx.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var player = await PlayerTestHelper.CreatePlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
player.Account!.LoginName = TestUserLoginName;
|
||||
|
||||
// Ensure the mock character name matches the persisted one.
|
||||
var mockCharacter = player.SelectedCharacter!;
|
||||
mockCharacter.Name = TestCharacterName;
|
||||
mockCharacter.CharacterClass ??= player.Account.UnlockedCharacterClasses.FirstOrDefault();
|
||||
if (mockCharacter.CharacterClass is null && config.CharacterClasses.FirstOrDefault() is { } cc)
|
||||
{
|
||||
mockCharacter.CharacterClass = cc;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// <copyright file="OfflinePlayerMuHelperTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="OfflinePlayerMuHelper"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class OfflinePlayerMuHelperTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the intelligence can be created and started.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask StartsWithoutExceptionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
|
||||
// Act
|
||||
var intelligence = new OfflinePlayerMuHelper(player);
|
||||
intelligence.Start();
|
||||
|
||||
intelligence?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that disposing the intelligence twice does not throw.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DisposeTwiceDoesNotThrowAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var intelligence = new OfflinePlayerMuHelper(player);
|
||||
intelligence.Start();
|
||||
|
||||
// Act & Assert
|
||||
intelligence.Dispose();
|
||||
intelligence.Dispose();
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
63
tests/MUnique.OpenMU.Tests/Offline/OfflinePlayerTests.cs
Normal file
63
tests/MUnique.OpenMU.Tests/Offline/OfflinePlayerTests.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// <copyright file="OfflinePlayerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using MUnique.OpenMU.DataModel.Entities;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="OfflinePlayer"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class OfflinePlayerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the offline player is created and started successfully.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask InitializesSuccessfullyAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.PlayerState.CurrentState, Is.EqualTo(PlayerState.EnteredWorld));
|
||||
Assert.That(player.SelectedCharacter, Is.Not.Null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="OfflinePlayer.StopAsync"/> cleans up resources.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask StopAsync_CleansUpAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
|
||||
// Act
|
||||
await player.StopAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.PlayerState.CurrentState, Is.EqualTo(PlayerState.Finished));
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
117
tests/MUnique.OpenMU.Tests/Offline/PetHandlerTests.cs
Normal file
117
tests/MUnique.OpenMU.Tests/Offline/PetHandlerTests.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
// <copyright file="PetHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using Moq;
|
||||
using MUnique.OpenMU.DataModel;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Pet;
|
||||
using MUnique.OpenMU.GameLogic.PlayerActions.Items;
|
||||
using Item = MUnique.OpenMU.Persistence.BasicModel.Item;
|
||||
using ItemDefinition = MUnique.OpenMU.Persistence.BasicModel.ItemDefinition;
|
||||
using ItemSlotType = MUnique.OpenMU.Persistence.BasicModel.ItemSlotType;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="PetHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class PetHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="PetHandler.InitializeAsync"/> sets the correct Dark Raven behavior.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[TestCase(1, PetBehaviour.AttackRandom)]
|
||||
[TestCase(2, PetBehaviour.AttackWithOwner)]
|
||||
public async ValueTask InitializeAsync_SetsDarkRavenBehaviorAsync(int mode, PetBehaviour expectedBehaviour)
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var petCommandManagerMock = new Mock<IPetCommandManager>();
|
||||
|
||||
var config = new MuHelperSettings
|
||||
{
|
||||
UseDarkRaven = true,
|
||||
DarkRavenMode = (byte)mode,
|
||||
};
|
||||
|
||||
var handler = new PetHandler(player, config, petCommandManagerMock.Object);
|
||||
|
||||
// Act
|
||||
await handler.InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
petCommandManagerMock.Verify(m => m.SetBehaviourAsync(expectedBehaviour, null), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="PetHandler.CheckPetDurabilityAsync"/> sets pet behavior to Idle when durability is 0.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask CheckPetDurabilityAsync_SetsIdleWhenDurabilityIsZeroAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var petCommandManagerMock = new Mock<IPetCommandManager>();
|
||||
|
||||
var petItem = new Item
|
||||
{
|
||||
Definition = new ItemDefinition
|
||||
{
|
||||
ItemSlot = new ItemSlotType(),
|
||||
Width = 1,
|
||||
Height = 1,
|
||||
},
|
||||
Durability = 0,
|
||||
};
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.PetSlot, petItem).ConfigureAwait(false);
|
||||
|
||||
var handler = new PetHandler(player, new MuHelperSettings(), petCommandManagerMock.Object);
|
||||
|
||||
// Act
|
||||
await handler.CheckPetDurabilityAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
petCommandManagerMock.Verify(m => m.SetBehaviourAsync(PetBehaviour.Idle, null), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="PetHandler.StopAsync"/> sets pet behavior to Idle.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask StopAsync_SetsIdleAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var petCommandManagerMock = new Mock<IPetCommandManager>();
|
||||
|
||||
var handler = new PetHandler(player, new MuHelperSettings(), petCommandManagerMock.Object);
|
||||
|
||||
// Act
|
||||
await handler.StopAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
petCommandManagerMock.Verify(m => m.SetBehaviourAsync(PetBehaviour.Idle, null), Times.Once);
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
221
tests/MUnique.OpenMU.Tests/Offline/RepairHandlerTests.cs
Normal file
221
tests/MUnique.OpenMU.Tests/Offline/RepairHandlerTests.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
// <copyright file="RepairHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using MUnique.OpenMU.DataModel;
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
using MUnique.OpenMU.GameServer.RemoteView.MuHelper;
|
||||
using Item = MUnique.OpenMU.Persistence.BasicModel.Item;
|
||||
using ItemDefinition = MUnique.OpenMU.Persistence.BasicModel.ItemDefinition;
|
||||
using ItemSlotType = MUnique.OpenMU.Persistence.BasicModel.ItemSlotType;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="RepairHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class RepairHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Items at or below the 50% durability threshold should be repaired when the player
|
||||
/// has enough Zen. Uses 10% (well below threshold) to confirm the happy path.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask RepairsItemWhenSufficientZenAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 100, currentDurability: 10);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
player.TryAddMoney(1_000_000);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = true };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(item.Durability, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-repair does nothing when disabled in the configuration, regardless of durability.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNothingWhenDisabledAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 100, currentDurability: 10);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
player.TryAddMoney(1_000_000);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = false };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(item.Durability, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-repair does not repair when the player has insufficient Zen.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNotRepairWhenInsufficientZenAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 100, currentDurability: 10);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = true };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(item.Durability, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fully durable items are skipped and no Zen is spent.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask SkipsFullyDurableItemsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 100, currentDurability: 100);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
var initialMoney = 1_000_000;
|
||||
player.TryAddMoney(initialMoney);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = true };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.Money, Is.EqualTo(initialMoney));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An item at exactly 50% durability (the threshold boundary) must be repaired.
|
||||
/// Mirrors the client check: iHealth <= DEFAULT_DURABILITY_THRESHOLD (50).
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask RepairsItemAtExactlyFiftyPercentThresholdAsync()
|
||||
{
|
||||
// Arrange — 50 / 100 = 50% (ceiling-integer: (50*100 + 99) / 100 = 50)
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 100, currentDurability: 50);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
player.TryAddMoney(1_000_000);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = true };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(item.Durability, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An item at 51% durability is above the threshold and must NOT be repaired,
|
||||
/// so no Zen is spent. Mirrors the client check: iHealth <= 50.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask SkipsItemAboveFiftyPercentThresholdAsync()
|
||||
{
|
||||
// Arrange — 51 / 100 = 51% (ceiling-integer: (51*100 + 99) / 100 = 51)
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 100, currentDurability: 51);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
var initialMoney = 1_000_000;
|
||||
player.TryAddMoney(initialMoney);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = true };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert — durability unchanged, no money spent
|
||||
Assert.That(item.Durability, Is.EqualTo(51));
|
||||
Assert.That(player.Money, Is.EqualTo(initialMoney));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the ceiling-integer formula handles non-round max-durability values
|
||||
/// correctly: 13 / 25 = 52% (above threshold → skip), but 12 / 25 = 48% (≤ 50% → repair).
|
||||
/// </summary>
|
||||
[TestCase(13, false, TestName = "NonRoundMax_52pct_Skipped")]
|
||||
[TestCase(12, true, TestName = "NonRoundMax_48pct_Repaired")]
|
||||
public async ValueTask ThresholdWithNonRoundMaxDurabilityAsync(byte currentDurability, bool expectRepair)
|
||||
{
|
||||
// Arrange — max = 25; ceiling(d*100/25) = ceiling(d*4)
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
var item = this.CreateDamagedItem(maxDurability: 25, currentDurability: currentDurability);
|
||||
await player.Inventory!.AddItemAsync(InventoryConstants.ArmorSlot, item).ConfigureAwait(false);
|
||||
player.TryAddMoney(1_000_000);
|
||||
|
||||
var config = new MuHelperSettings { RepairItem = true };
|
||||
var handler = new RepairHandler(player, config);
|
||||
|
||||
// Act
|
||||
await handler.PerformRepairsAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
if (expectRepair)
|
||||
{
|
||||
Assert.That(item.Durability, Is.EqualTo(25));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(item.Durability, Is.EqualTo(currentDurability));
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
return await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Item CreateDamagedItem(byte maxDurability, byte currentDurability)
|
||||
{
|
||||
return new Item
|
||||
{
|
||||
Definition = new ItemDefinition
|
||||
{
|
||||
Durability = maxDurability,
|
||||
ItemSlot = new ItemSlotType(),
|
||||
Width = 1,
|
||||
Height = 1,
|
||||
Value = 1000,
|
||||
},
|
||||
Durability = currentDurability,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// <copyright file="ZenConsumptionHandlerTests.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Tests.Offline;
|
||||
|
||||
using MUnique.OpenMU.GameLogic;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.MuHelper;
|
||||
using MUnique.OpenMU.GameLogic.Offline;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="ZenConsumptionHandler"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ZenConsumptionHandlerTests
|
||||
{
|
||||
private IGameContext _gameContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a fresh game context before each test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this._gameContext = GameContextTestHelper.CreateGameContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that Zen is deducted when the pay interval has passed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DeductsZenWhenIntervalPassedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
player.TryAddMoney(1_000_000);
|
||||
player.Attributes![Stats.Level] = 100;
|
||||
|
||||
var handler = new ZenConsumptionHandler(player);
|
||||
player.StartTimestamp = DateTime.UtcNow.AddMinutes(-2);
|
||||
|
||||
// Act
|
||||
await handler.DeductZenAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.Money, Is.LessThan(1_000_000));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that Zen is not deducted when the pay interval has not passed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask DoesNotDeductZenWhenIntervalNotPassedAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
player.TryAddMoney(1_000_000);
|
||||
|
||||
var handler = new ZenConsumptionHandler(player);
|
||||
|
||||
// Act
|
||||
await handler.DeductZenAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(player.Money, Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that <see cref="ZenConsumptionHandler.DeductZenAsync"/> returns false
|
||||
/// when the player has insufficient Zen.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask ReturnsFalseWhenInsufficientZenAsync()
|
||||
{
|
||||
// Arrange
|
||||
var player = await this.CreateOfflinePlayerAsync().ConfigureAwait(false);
|
||||
player.TryAddMoney(100);
|
||||
player.Attributes![Stats.Level] = 100;
|
||||
|
||||
var handler = new ZenConsumptionHandler(player);
|
||||
player.StartTimestamp = DateTime.UtcNow.AddMinutes(-2);
|
||||
|
||||
// Act
|
||||
var result = await handler.DeductZenAsync().ConfigureAwait(false);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
private async ValueTask<OfflinePlayer> CreateOfflinePlayerAsync()
|
||||
{
|
||||
var player = await PlayerTestHelper.CreateOfflineLevelingPlayerAsync(this._gameContext).ConfigureAwait(false);
|
||||
player.Attributes![Stats.MasterLevel] = 0;
|
||||
return player;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user