neon-sprawl/server/NeonSprawl.Server/Game/Combat/CombatOperations.cs

66 lines
2.2 KiB
C#

namespace NeonSprawl.Server.Game.Combat;
/// <summary>
/// Orchestrates deterministic combat resolution: ability catalog damage + prototype target HP (NEO-81).
/// Cast HTTP wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-82).
/// </summary>
public static class CombatOperations
{
/// <summary>
/// Resolves one ability against one prototype combat target using catalog damage and the health store.
/// Defeated targets deny before damage application; zero-damage abilities succeed without mutating HP.
/// </summary>
public static CombatResult TryResolve(
string abilityId,
string targetId,
IAbilityDefinitionRegistry abilityRegistry,
ICombatEntityHealthStore healthStore)
{
if (!abilityRegistry.TryNormalizeKnown(abilityId, out var normalizedAbility) ||
!abilityRegistry.TryGetDefinition(normalizedAbility, out var definition))
{
return Deny(CombatReasonCodes.UnknownAbility);
}
if (!healthStore.TryGet(targetId, out var snapshot))
{
return Deny(CombatReasonCodes.UnknownTarget);
}
if (snapshot.Defeated)
{
return Deny(CombatReasonCodes.TargetDefeated, snapshot.CurrentHp);
}
if (definition.BaseDamage == 0)
{
return new CombatResult(
Success: true,
ReasonCode: null,
DamageDealt: 0,
TargetRemainingHp: snapshot.CurrentHp,
TargetDefeated: snapshot.Defeated);
}
if (!healthStore.TryApplyDamage(targetId, definition.BaseDamage, out var after))
{
return Deny(CombatReasonCodes.UnknownTarget);
}
return new CombatResult(
Success: true,
ReasonCode: null,
DamageDealt: definition.BaseDamage,
TargetRemainingHp: after.CurrentHp,
TargetDefeated: after.Defeated);
}
private static CombatResult Deny(string reasonCode, int? targetRemainingHp = null) =>
new(
Success: false,
ReasonCode: reasonCode,
DamageDealt: 0,
TargetRemainingHp: targetRemainingHp,
TargetDefeated: false);
}