41 lines
2.2 KiB
C#
41 lines
2.2 KiB
C#
using System.Collections.Frozen;
|
|
using Outnumbered.Config;
|
|
|
|
namespace Outnumbered.Domain;
|
|
|
|
// Resolves a player's effective stat values from their invested levels + run-card bonuses. Takes a stat-def lookup
|
|
// (key -> StatDef) rather than the named-field config, so it's already shaped for the stat registry — the engine builds
|
|
// the dictionary once and passes it. `Eff` = permanent only; `EffRun` = permanent + survival card; `CardMag` = the
|
|
// effect-card magnitude (a non-stat key, cards only).
|
|
public static class StatResolver
|
|
{
|
|
private static readonly StatDef Zero = new(0, 0);
|
|
|
|
private static int LevelOf(in PlayerSnapshot s, string key) =>
|
|
s.Upgrades is not null && s.Upgrades.TryGetValue(key, out var l) ? l : 0;
|
|
|
|
public static double Eff(in PlayerSnapshot s, string key, FrozenDictionary<string, StatDef> defs)
|
|
{
|
|
var d = defs.TryGetValue(key, out var def) ? def : Zero;
|
|
return d.Base + LevelOf(s, key) * d.PerLevel;
|
|
}
|
|
|
|
public static double EffRun(in PlayerSnapshot s, string key, FrozenDictionary<string, StatDef> defs) =>
|
|
Eff(s, key, defs) + (s.Cards?.Bonus(key) ?? 0.0);
|
|
|
|
public static double CardMag(in PlayerSnapshot s, string key) => s.Cards?.Bonus(key) ?? 0.0;
|
|
|
|
public static int MaxHp(in PlayerSnapshot s, FrozenDictionary<string, StatDef> defs, int baseMax) =>
|
|
baseMax + (int)EffRun(s, StatKeys.MaxHp, defs);
|
|
|
|
public static int MaxArmor(in PlayerSnapshot s, FrozenDictionary<string, StatDef> defs, int baseMax) =>
|
|
baseMax + (int)EffRun(s, StatKeys.MaxArmor, defs);
|
|
|
|
// 0 at full HP, 1 near death — drives Berserk (ability + passive card). Health<=0 (a snapshot built with no live
|
|
// pawn, or a downed pawn) yields 0, NOT 1: missing-HP scaling only applies to a live attacker, so "no pawn" must
|
|
// mean "no bonus", not "max bonus" (guards the pd-less Snapshot path where Health defaults to 0).
|
|
public static double MissingHpFraction(in PlayerSnapshot s, int maxHp) =>
|
|
// Health>=1 (guarded) and maxHp>=1 => 1 - Health/maxHp < 1, so the upper clamp can't bind; only the lower
|
|
// (overheal -> negative) is live. Math.Max(0, x) is bit-identical to Math.Clamp(x, 0, 1) here.
|
|
maxHp <= 0 || s.Health <= 0 ? 0.0 : Math.Max(0.0, 1.0 - s.Health / (double)maxHp);
|
|
}
|