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

123
Outnumbered/Admin.cs Normal file
View file

@ -0,0 +1,123 @@
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
using Outnumbered.Config;
using Outnumbered.Data;
namespace Outnumbered;
// Admin command suite (spec §13). Gated by @outnumbered/admin — add your SteamID to
// addons/counterstrikesharp/configs/admins.json with flag "@outnumbered/admin" (or "@css/root" for everything).
// Target arg: a name substring, "@me", or "@all". Console (no caller) is always allowed by CSSharp.
public sealed partial class OutnumberedPlugin
{
// PdOf(CCSPlayerController) lives in the resolution seam (Players.cs); the admin suite just uses it.
private static List<CCSPlayerController> ResolveTargets(string pattern, CCSPlayerController? caller)
{
var humans = Humans().ToList();
if (pattern is "@all" or "@a") return humans;
if ((pattern is "@me" or "@self") && caller is { IsValid: true }) return [caller];
return humans.Where(p => p.PlayerName.Contains(pattern, StringComparison.OrdinalIgnoreCase)).ToList();
}
// Apply an action to every matched target's PlayerData, then persist + refresh HP/armor + clantag.
private int ApplyToTargets(string pattern, CCSPlayerController? caller, Action<CCSPlayerController, PlayerData> action)
{
int n = 0;
foreach (var t in ResolveTargets(pattern, caller))
{
var pd = PdOf(t);
if (pd is null) continue;
action(t, pd);
pd.Dirty = true;
DeferReapplyCaps(t); // re-apply caps next frame (slot+SteamID-pinned) so a slot-reuse can't inherit this grant
ApplyClan(t, pd);
n++;
}
return n;
}
[RequiresPermissions("@outnumbered/admin")]
[ConsoleCommand("css_og_givexp", "[admin] grant XP")]
[CommandHelper(2, "<target> <amount>", CommandUsage.CLIENT_AND_SERVER)]
public void Cmd_GiveXp(CCSPlayerController? player, CommandInfo info)
{
if (!long.TryParse(info.GetArg(2), out var amt)) { info.ReplyToCommand("[Outnumbered] amount must be a number."); return; }
int n = ApplyToTargets(info.GetArg(1), player, (c, pd) => GrantXp(pd, amt, c));
info.ReplyToCommand($"[Outnumbered] gave {amt} XP to {n} player(s).");
}
[RequiresPermissions("@outnumbered/admin")]
[ConsoleCommand("css_og_givepoints", "[admin] grant skill points")]
[CommandHelper(2, "<target> <amount>", CommandUsage.CLIENT_AND_SERVER)]
public void Cmd_GivePoints(CCSPlayerController? player, CommandInfo info)
{
if (!int.TryParse(info.GetArg(2), out var amt)) { info.ReplyToCommand("[Outnumbered] amount must be a number."); return; }
int n = ApplyToTargets(info.GetArg(1), player, (_, pd) => pd.Points = Math.Max(0, pd.Points + amt));
info.ReplyToCommand($"[Outnumbered] gave {amt} point(s) to {n} player(s).");
}
[RequiresPermissions("@outnumbered/admin")]
[ConsoleCommand("css_og_setlevel", "[admin] set level")]
[CommandHelper(2, "<target> <level>", CommandUsage.CLIENT_AND_SERVER)]
public void Cmd_SetLevel(CCSPlayerController? player, CommandInfo info)
{
if (!int.TryParse(info.GetArg(2), out var lvl)) { info.ReplyToCommand("[Outnumbered] level must be a number."); return; }
lvl = Math.Clamp(lvl, 1, Config.Progression.LevelCap);
int n = ApplyToTargets(info.GetArg(1), player, (_, pd) => { pd.Level = lvl; pd.Xp = 0; });
info.ReplyToCommand($"[Outnumbered] set level {lvl} on {n} player(s).");
}
[RequiresPermissions("@outnumbered/admin")]
[ConsoleCommand("css_og_setprestige", "[admin] set prestige")]
[CommandHelper(2, "<target> <prestige>", CommandUsage.CLIENT_AND_SERVER)]
public void Cmd_SetPrestige(CCSPlayerController? player, CommandInfo info)
{
if (!int.TryParse(info.GetArg(2), out var pres)) { info.ReplyToCommand("[Outnumbered] prestige must be a number."); return; }
pres = Math.Clamp(pres, 0, Config.Progression.PrestigeCap);
int n = ApplyToTargets(info.GetArg(1), player, (_, pd) => pd.Prestige = pres);
info.ReplyToCommand($"[Outnumbered] set prestige {pres} on {n} player(s).");
}
[RequiresPermissions("@outnumbered/admin")]
[ConsoleCommand("css_og_resetplayer", "[admin] full progression reset")]
[CommandHelper(1, "<target>", CommandUsage.CLIENT_AND_SERVER)]
public void Cmd_ResetPlayer(CCSPlayerController? player, CommandInfo info)
{
int n = ApplyToTargets(info.GetArg(1), player, (_, pd) =>
{
pd.Xp = 0; pd.Level = 1; pd.Points = 1; pd.Prestige = 0; pd.Upgrades.Clear();
});
info.ReplyToCommand($"[Outnumbered] reset {n} player(s) to L1.");
}
[RequiresPermissions("@outnumbered/admin")]
[ConsoleCommand("css_og_reload", "[admin] live-reload outnumbered.json + ranks.json (no session reset)")]
[CommandHelper(0, "", CommandUsage.CLIENT_AND_SERVER)]
public void Cmd_ReloadConfig(CCSPlayerController? player, CommandInfo info)
{
try
{
string path = ConfigPath("outnumbered.json"); // assembly-name-derived (Ranks.cs) — no hardcoded folder literal
if (File.Exists(path))
{
var c = JsonSerializer.Deserialize<OutnumberedConfig>(File.ReadAllText(path),
new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, PropertyNameCaseInsensitive = true });
if (c is not null) Config = c; // sub-sections have new() defaults, so missing keys never null out
}
RebuildEffectiveHandicap(); // re-resolve base Handicap + the active mode's override from the new config
RebuildStatDefs(); // re-resolve the key->StatDef registry from the new Config.Stats
LoadRanksConfig();
RebuildBalancePayload(); // the API's balance verb serves the EFFECTIVE config — must track every reload
info.ReplyToCommand("[Outnumbered] reloaded outnumbered.json + ranks.json.");
}
catch (Exception ex)
{
info.ReplyToCommand("[Outnumbered] reload failed: " + ex.Message);
Logger.LogError(ex, "Outnumbered og_reload failed");
}
}
}