initial commit
This commit is contained in:
commit
d701598350
67 changed files with 9351 additions and 0 deletions
51
Outnumbered/Handicap.cs
Normal file
51
Outnumbered/Handicap.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Outnumbered.Config;
|
||||
using Outnumbered.Data;
|
||||
using Outnumbered.Domain;
|
||||
|
||||
namespace Outnumbered;
|
||||
|
||||
// The per-player handicap (the balance spine; the HUD shows each player their live deal/take/XP bands). The MATH lives in Outnumbered.Domain.HandicapModel — a single
|
||||
// signed index t in [-1,+1] driving deal/take/XP together so they reach their extremes at the SAME thresholds. These are
|
||||
// zero-math accessors that snapshot the player and call it. The hot damage hook reaches HandicapModel through
|
||||
// CombatResolver (one snapshot); the HUD + progression use these adapters.
|
||||
public sealed partial class OutnumberedPlugin
|
||||
{
|
||||
// The effective handicap for the active mode = base Config.Handicap with the mode's overrides applied, wrapped in a
|
||||
// ResolvedHandicap (config + precomputed guard denominators). Rebuilt at driver-select (Initialize_Driver) and on
|
||||
// !og_reload, so it stays live-tunable AND ComputeT never recomputes the config-invariant terms per hit/tick.
|
||||
private ResolvedHandicap _hcap = new(new HandicapConfig());
|
||||
internal void RebuildEffectiveHandicap() =>
|
||||
_hcap = new ResolvedHandicap(_driver?.Handicap is { } o ? o.ApplyTo(Config.Handicap) : Config.Handicap);
|
||||
|
||||
// The XP-rate bridge (used by GrantXp + the HUD). The deal/take chains go straight through CombatResolver ->
|
||||
// HandicapModel.MDeal/MTake on a snapshot, so there are no MDeal(pd)/MTake(pd) bridge methods here.
|
||||
private double HandicapXpMult(PlayerData pd) => HandicapModel.XpMult(Snapshot(pd), _hcap);
|
||||
|
||||
// One-time structural guard that HandicapOverride stays a faithful mirror of HandicapConfig: every base field needs a
|
||||
// matching nullable override AND ApplyTo must actually wire it. Catches the documented footgun — add a field to both
|
||||
// POCOs but forget the `X ?? b.X` line in ApplyTo, and that field's per-mode override is silently ignored (a tuner sees
|
||||
// "the GunGame/Survival override doesn't work"). Probes ApplyTo functionally via reflection. Structural, not value-
|
||||
// dependent, so it runs once at load (not per !og_reload). All current HandicapConfig fields are bool/int/double.
|
||||
private void Initialize_Handicap()
|
||||
{
|
||||
var baseCfg = new HandicapConfig();
|
||||
foreach (var bp in typeof(HandicapConfig).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (!bp.CanRead || !bp.CanWrite) continue;
|
||||
var op = typeof(HandicapOverride).GetProperty(bp.Name);
|
||||
if (op is null)
|
||||
{
|
||||
Logger.LogError("Outnumbered: HandicapOverride is missing field '{Field}' that HandicapConfig defines (add a nullable mirror + an ApplyTo line).", bp.Name);
|
||||
continue;
|
||||
}
|
||||
object? sentinel = bp.GetValue(baseCfg) switch { bool b => !b, int i => i + 1, double d => d + 1.0, _ => null };
|
||||
if (sentinel is null) continue; // non-bool/int/double field: can't auto-probe ApplyTo wiring here
|
||||
var ov = new HandicapOverride();
|
||||
op.SetValue(ov, sentinel);
|
||||
if (!Equals(bp.GetValue(ov.ApplyTo(baseCfg)), sentinel))
|
||||
Logger.LogError("Outnumbered: HandicapOverride.ApplyTo does not propagate '{Field}' — its per-mode override is silently ignored (add the matching `?? b.` line).", bp.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue