neon-sprawl/server/NeonSprawl.Server.Tests/Game/Combat/CombatOperationsTests.cs

268 lines
9.3 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.AbilityInput;
using NeonSprawl.Server.Game.Combat;
using NeonSprawl.Server.Game.Targeting;
using NeonSprawl.Server.Tests;
using Xunit;
namespace NeonSprawl.Server.Tests.Game.Combat;
public sealed class CombatOperationsTests
{
private static AbilityDefinitionRegistry CreatePrototypeRegistry()
{
var rows = new Dictionary<string, AbilityDefRow>(StringComparer.Ordinal)
{
[PrototypeAbilityRegistry.PrototypeBurst] = new AbilityDefRow(
PrototypeAbilityRegistry.PrototypeBurst,
"Prototype Burst",
40,
5.0,
"attack"),
[PrototypeAbilityRegistry.PrototypeDash] = new AbilityDefRow(
PrototypeAbilityRegistry.PrototypeDash,
"Prototype Dash",
0,
4.0,
"movement"),
[PrototypeAbilityRegistry.PrototypeGuard] = new AbilityDefRow(
PrototypeAbilityRegistry.PrototypeGuard,
"Prototype Guard",
0,
6.0,
"utility"),
[PrototypeAbilityRegistry.PrototypePulse] = new AbilityDefRow(
PrototypeAbilityRegistry.PrototypePulse,
"Prototype Pulse",
25,
3.0,
"attack"),
};
var catalog = new AbilityDefinitionCatalog("/tmp/abilities", rows, catalogJsonFileCount: 1);
return new AbilityDefinitionRegistry(catalog);
}
[Fact]
public void TryResolve_WithPulseOnFreshAlpha_ShouldDealCatalogDamage()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.True(result.Success);
Assert.Null(result.ReasonCode);
Assert.Equal(25, result.DamageDealt);
Assert.Equal(75, result.TargetRemainingHp);
Assert.False(result.TargetDefeated);
}
[Fact]
public void TryResolve_WithFourPulses_ShouldDefeatTargetOnFourthResolve()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
for (var i = 0; i < 3; i++)
{
_ = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
}
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.True(result.Success);
Assert.Equal(0, result.TargetRemainingHp);
Assert.True(result.TargetDefeated);
}
[Fact]
public void TryResolve_OnDefeatedTarget_ShouldDenyWithTargetDefeated()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
for (var i = 0; i < 4; i++)
{
_ = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
}
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
store.TryGet(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);
// Assert
Assert.False(result.Success);
Assert.Equal(CombatReasonCodes.TargetDefeated, result.ReasonCode);
Assert.Equal(0, result.DamageDealt);
Assert.Equal(0, result.TargetRemainingHp);
Assert.False(result.TargetDefeated);
Assert.Equal(0, snapshot.CurrentHp);
Assert.True(snapshot.Defeated);
}
[Fact]
public void TryResolve_WithGuardOnLiveTarget_ShouldSucceedWithZeroDamage()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypeGuard,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
store.TryGet(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);
// Assert
Assert.True(result.Success);
Assert.Null(result.ReasonCode);
Assert.Equal(0, result.DamageDealt);
Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, result.TargetRemainingHp);
Assert.False(result.TargetDefeated);
Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.CurrentHp);
}
[Fact]
public void TryResolve_WithGuardOnDefeatedTarget_ShouldDenyWithTargetDefeated()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
_ = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 100, out _);
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypeGuard,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.False(result.Success);
Assert.Equal(CombatReasonCodes.TargetDefeated, result.ReasonCode);
Assert.Equal(0, result.DamageDealt);
Assert.Equal(0, result.TargetRemainingHp);
Assert.False(result.TargetDefeated);
}
[Fact]
public void TryResolve_WithDashOnLiveTarget_ShouldSucceedWithZeroDamage()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypeDash,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.True(result.Success);
Assert.Null(result.ReasonCode);
Assert.Equal(0, result.DamageDealt);
Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, result.TargetRemainingHp);
Assert.False(result.TargetDefeated);
}
[Fact]
public void TryResolve_WithUnknownAbility_ShouldDenyWithUnknownAbility()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
// Act
var result = CombatOperations.TryResolve(
"not_a_real_ability",
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.False(result.Success);
Assert.Equal(CombatReasonCodes.UnknownAbility, result.ReasonCode);
Assert.Equal(0, result.DamageDealt);
Assert.Null(result.TargetRemainingHp);
}
[Fact]
public void TryResolve_WithUnknownTarget_ShouldDenyWithUnknownTarget()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
"not_a_target",
registry,
store);
// Assert
Assert.False(result.Success);
Assert.Equal(CombatReasonCodes.UnknownTarget, result.ReasonCode);
Assert.Equal(0, result.DamageDealt);
Assert.Null(result.TargetRemainingHp);
}
[Fact]
public void TryResolve_WithBurstOnFreshAlpha_ShouldDealFortyDamage()
{
// Arrange
var registry = CreatePrototypeRegistry();
var store = new InMemoryCombatEntityHealthStore();
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypeBurst,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.True(result.Success);
Assert.Equal(40, result.DamageDealt);
Assert.Equal(60, result.TargetRemainingHp);
Assert.False(result.TargetDefeated);
}
[Fact]
public async Task Host_ShouldResolveCombatDependenciesFromDi_WhenStartupSucceeds()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
using var client = factory.CreateClient();
_ = await client.GetAsync("/health");
var registry = factory.Services.GetRequiredService<IAbilityDefinitionRegistry>();
var store = factory.Services.GetRequiredService<ICombatEntityHealthStore>();
// Act
var result = CombatOperations.TryResolve(
PrototypeAbilityRegistry.PrototypePulse,
PrototypeTargetRegistry.PrototypeTargetAlphaId,
registry,
store);
// Assert
Assert.True(result.Success);
Assert.Equal(25, result.DamageDealt);
Assert.Equal(75, result.TargetRemainingHp);
}
}