123 lines
6.2 KiB
C#
123 lines
6.2 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|