initial commit
This commit is contained in:
commit
d701598350
67 changed files with 9351 additions and 0 deletions
78
Outnumbered/Data/Models.cs
Normal file
78
Outnumbered/Data/Models.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
namespace Outnumbered.Data;
|
||||
|
||||
// DTO for the !top leaderboard.
|
||||
public sealed record TopPlayer(string Name, int Level, int Prestige, long Xp);
|
||||
|
||||
// DTOs for the records leaderboards (survival best wave / fastest Gun Game ladder). Names only — no SteamIDs on the wire.
|
||||
public sealed record TopWave(string Name, int BestWave);
|
||||
public sealed record TopGgTime(string Name, long BestMs);
|
||||
|
||||
// DTO returned by a load.
|
||||
public sealed record LoadedPlayer(
|
||||
string Name, long Xp, int Level, int Prestige, int Points, Dictionary<string, int> Upgrades,
|
||||
string? PrimaryWeapon, string? SecondaryWeapon);
|
||||
|
||||
// DTO handed to a save (absolute values; last-write-wins).
|
||||
public sealed record PersistPlayer(
|
||||
ulong SteamId, string Name, long Xp, int Level, int Prestige, int Points,
|
||||
IReadOnlyDictionary<string, int> Upgrades, string? PrimaryWeapon, string? SecondaryWeapon);
|
||||
|
||||
// Per-match stats (RAM-first: written only on disconnect/shutdown, wiped on round end). Restored on rejoin mid-match.
|
||||
public sealed record MatchState(ulong SteamId, int Kills, int Deaths, int Streak, int HeadshotKills, long GgRunStartedAtMs = 0);
|
||||
|
||||
// In-memory cache row. Mutated ONLY on the main game thread.
|
||||
public sealed class PlayerData
|
||||
{
|
||||
public required ulong SteamId { get; init; }
|
||||
public string Name { get; set; } = "";
|
||||
public long Xp { get; set; } // progress toward the next level (within the current prestige)
|
||||
public int Level { get; set; } = 1;
|
||||
public int Prestige { get; set; }
|
||||
public int Points { get; set; } = 1; // start with one spendable point (spec §2)
|
||||
public Dictionary<string, int> Upgrades { get; } = new();
|
||||
|
||||
// Chosen loadout (null = use the config default). Persisted. Free selection, validated against the allowed lists.
|
||||
public string? PrimaryWeapon { get; set; }
|
||||
public string? SecondaryWeapon { get; set; }
|
||||
|
||||
public bool Dirty { get; set; }
|
||||
|
||||
// Runtime-only: sub-1 XP remainder carried between per-hit grants so fractional damage-XP isn't lost to rounding.
|
||||
public double XpCarry { get; set; }
|
||||
|
||||
// Runtime-only: combat tracking for the handicap (and killstreak abilities).
|
||||
public int Kills { get; set; }
|
||||
public int Deaths { get; set; }
|
||||
public int Streak { get; set; } // current killstreak; resets on death
|
||||
public int HeadshotKills { get; set; } // headshot kills this match — feeds the handicap's HS-rate factor
|
||||
|
||||
// Runtime-only (Gun Game mode): current weapon-ladder rung + kills banked toward clearing it. Persist across
|
||||
// deaths within a match, reset on map start. NOT round-tripped in match_state — a mid-match reconnect
|
||||
// restarts the climb at rung 0.
|
||||
public int GgRung { get; set; }
|
||||
public int GgRungKills { get; set; }
|
||||
|
||||
// Gun Game speedrun clock: wall-clock unix ms of the run's first damage dealt-or-taken (0 = not armed; reset on map
|
||||
// start). Wall clock, not Server.CurrentTime, because it round-trips through match_state and must stay comparable
|
||||
// across a changelevel/restart (the game clock restarts at ~0 there). Unlike GgRung, this DOES round-trip — a
|
||||
// mid-match reconnect restarts the climb but never restarts the clock (the once-per-match anti-abuse rule).
|
||||
public long GgRunStartedAtMs { get; set; }
|
||||
|
||||
// Runtime-only: killstreak ability state, indexed by AbilityRegistry index (0..N-1).
|
||||
// AbilityReadyAt = Server.CurrentTime at which the ability comes off cooldown (survives death).
|
||||
// AbilityActiveUntil = Server.CurrentTime until which the ability's effect is live (cleared on death).
|
||||
// Sized to AbilitySlots (headroom over the current 5) so adding an AbilityRegistry row can't IndexOutOfRange; a
|
||||
// startup guard (Initialize_Abilities) errors loudly if the registry ever exceeds this.
|
||||
public const int AbilitySlots = 8;
|
||||
public double[] AbilityReadyAt { get; } = new double[AbilitySlots];
|
||||
public double[] AbilityActiveUntil { get; } = new double[AbilitySlots];
|
||||
|
||||
public PersistPlayer ToPersist() =>
|
||||
new(SteamId, Name, Xp, Level, Prestige, Points, new Dictionary<string, int>(Upgrades),
|
||||
PrimaryWeapon, SecondaryWeapon);
|
||||
|
||||
public MatchState ToMatchState() => new(SteamId, Kills, Deaths, Streak, HeadshotKills, GgRunStartedAtMs);
|
||||
// GgRunStartedAtMs counts as activity: an armed-but-zero-kill speedrun clock must still reach match_state,
|
||||
// or a disconnect before the first kill silently discards it (this predicate gates every match-state save).
|
||||
public bool HasMatchActivity => Kills > 0 || Deaths > 0 || Streak > 0 || HeadshotKills > 0 || GgRunStartedAtMs > 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue