initial commit
All checks were successful
CI / build (push) Successful in 32s
CI / release (push) Successful in 32s
CI / lint (push) Successful in 30s

This commit is contained in:
Kamal Tufekcic 2026-07-05 13:28:35 +03:00
commit d701598350
67 changed files with 9351 additions and 0 deletions

View file

@ -0,0 +1,56 @@
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
};
}
}