56 lines
3.1 KiB
C#
56 lines
3.1 KiB
C#
using System.Collections.Frozen;
|
|
using CounterStrikeSharp.API;
|
|
using CounterStrikeSharp.API.Core;
|
|
using Outnumbered.Config;
|
|
using Outnumbered.Data;
|
|
using Outnumbered.Domain;
|
|
|
|
namespace Outnumbered;
|
|
|
|
// The single engine site that reads a player into an immutable PlayerSnapshot for the pure Domain math, plus the stat
|
|
// registry (_statDefs) the Domain resolvers consume. Everything pure (handicap / combat / progression / stat resolution)
|
|
// takes a snapshot + _statDefs and never touches a pawn/controller — so the CS2/CSSharp surface lives only here.
|
|
public sealed partial class OutnumberedPlugin
|
|
{
|
|
private const int BaseMaxHp = 100;
|
|
private const int BaseMaxArmor = 100;
|
|
|
|
// key -> StatDef, rebuilt from Config.Stats on Load + every !og_reload. This is the stat registry the Domain resolvers
|
|
// index (StatResolver/CombatResolver take it) — the key->def view of the named Config.Stats fields (which stay, for JSON
|
|
// back-compat). Projected straight from StatRegistry (Stats.cs), so a new stat is one registry row, not a line here.
|
|
// FrozenDictionary: built once per load/reload, read per-hit (OffenseMultiplier alone does 3+ lookups/hit) — frozen
|
|
// has faster reads than Dictionary. Ordinal matches Dictionary<string,_>'s default, so lookups are bit-identical.
|
|
private FrozenDictionary<string, StatDef> _statDefs = FrozenDictionary<string, StatDef>.Empty;
|
|
|
|
private void RebuildStatDefs() =>
|
|
_statDefs = StatRegistry.ToFrozenDictionary(r => r.Key, r => r.Def(Config.Stats), StringComparer.Ordinal);
|
|
|
|
// Build the immutable Domain input for one player. `p` is optional: it supplies the live pawn Health (drives the
|
|
// missing-HP fraction for Berserk) — null is fine for paths that don't read Health (XP rate, where t ignores HP).
|
|
// Upgrades + Cards are held by reference, so building a snapshot per hit/tick is allocation-free.
|
|
internal PlayerSnapshot Snapshot(PlayerData pd, CCSPlayerController? p = null)
|
|
{
|
|
var pawn = p?.PlayerPawn.Value;
|
|
double now = Server.CurrentTime; // read once for the 3 ability checks below (can't advance within this build)
|
|
return new PlayerSnapshot
|
|
{
|
|
Level = pd.Level,
|
|
Prestige = pd.Prestige,
|
|
Xp = pd.Xp,
|
|
Kills = pd.Kills,
|
|
Deaths = pd.Deaths,
|
|
HeadshotKills = pd.HeadshotKills,
|
|
Streak = pd.Streak,
|
|
Health = pawn?.Health ?? 0,
|
|
HandicapProgress = _driver?.HandicapProgress(pd) ?? 0.0,
|
|
HandicapFloor = _driver?.HandicapFloor(pd) ?? -1.0,
|
|
TeamDealMult = _driver?.TeamDealMult() ?? 1.0,
|
|
TeamTakeMult = _driver?.TeamTakeMult() ?? 1.0,
|
|
OverchargeActive = AbilityActive(pd, AbOvercharge, now), // CombatResolver gates these on Abilities.Enabled
|
|
BerserkActive = AbilityActive(pd, AbBerserk, now),
|
|
AdrenalineActive = AbilityActive(pd, AbAdrenaline, now),
|
|
Upgrades = pd.Upgrades,
|
|
Cards = _driver?.CardSource(pd), // survival run (IStatBonusSource); null in TDM/GG
|
|
};
|
|
}
|
|
}
|