96 lines
4 KiB
C#
96 lines
4 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using CounterStrikeSharp.API;
|
|
using CounterStrikeSharp.API.Core;
|
|
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
|
using CounterStrikeSharp.API.Core.Capabilities;
|
|
using CounterStrikeSharp.API.Modules.Commands;
|
|
using CounterStrikeSharp.API.Modules.Cvars;
|
|
using CounterStrikeSharp.API.Modules.Utils;
|
|
using CS2_SimpleAdminApi;
|
|
|
|
namespace WebPanelBridge;
|
|
|
|
// Reports live match state to simpleadmin-web over RCON. The server console command prints one
|
|
// JSON object per line, each prefixed with "wpb ", so the web panel can pick them out of whatever
|
|
// else lands in the RCON response. Admins hidden with CS2-SimpleAdmin's css_hide are left out, as if
|
|
// they weren't connected. Nothing here changes game state.
|
|
public class WebPanelBridge : BasePlugin
|
|
{
|
|
public override string ModuleName => "WebPanelBridge";
|
|
public override string ModuleVersion => "0.2.0";
|
|
public override string ModuleAuthor => "astra";
|
|
public override string ModuleDescription => "Live server status for simpleadmin-web, over RCON";
|
|
|
|
private const string Prefix = "wpb ";
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
|
};
|
|
|
|
private static readonly PluginCapability<ICS2_SimpleAdminApi> SimpleAdminCapability = new("simpleadmin:api");
|
|
|
|
private record ServerLine(int V, string Hostname, string Map, int MaxPlayers, int ScoreT, int ScoreCt, bool Warmup);
|
|
|
|
private record PlayerLine(int Userid, string Name, string Steamid, int Team, int Kills, int Deaths, uint Ping, bool Bot);
|
|
|
|
[ConsoleCommand("css_webpanel_status", "Prints live server status as JSON lines for simpleadmin-web")]
|
|
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
|
|
public void OnStatus(CCSPlayerController? caller, CommandInfo command)
|
|
{
|
|
int scoreT = 0, scoreCt = 0;
|
|
foreach (var team in Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager"))
|
|
{
|
|
if (team.TeamNum == (byte)CsTeam.Terrorist)
|
|
scoreT = team.Score;
|
|
else if (team.TeamNum == (byte)CsTeam.CounterTerrorist)
|
|
scoreCt = team.Score;
|
|
}
|
|
|
|
var warmup = Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules")
|
|
.FirstOrDefault()?.GameRules?.WarmupPeriod ?? false;
|
|
|
|
var maxPlayers = Server.MaxPlayers;
|
|
var visible = ConVar.Find("sv_visiblemaxplayers")?.GetPrimitiveValue<int>() ?? -1;
|
|
if (visible > 0)
|
|
maxPlayers = visible;
|
|
|
|
var server = new ServerLine(1, ConVar.Find("hostname")?.StringValue ?? "", Server.MapName, maxPlayers,
|
|
scoreT, scoreCt, warmup);
|
|
command.ReplyToCommand(Prefix + JsonSerializer.Serialize(server, JsonOptions));
|
|
|
|
// Fetched on every call, so reloading SimpleAdmin doesn't leave a stale instance here.
|
|
ICS2_SimpleAdminApi? simpleAdmin = null;
|
|
try
|
|
{
|
|
simpleAdmin = SimpleAdminCapability.Get();
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
}
|
|
|
|
foreach (var player in Utilities.GetPlayers())
|
|
{
|
|
if (player is not { IsValid: true, IsHLTV: false } || player.Connected != PlayerConnectedState.Connected)
|
|
continue;
|
|
if (!player.IsBot && simpleAdmin?.IsAdminSilent(player) == true)
|
|
continue;
|
|
|
|
var stats = player.ActionTrackingServices?.MatchStats;
|
|
var line = new PlayerLine(
|
|
player.UserId ?? -1,
|
|
player.PlayerName,
|
|
player.IsBot ? "0" : player.SteamID.ToString(),
|
|
player.TeamNum,
|
|
stats?.Kills ?? 0,
|
|
stats?.Deaths ?? 0,
|
|
player.Ping,
|
|
player.IsBot);
|
|
command.ReplyToCommand(Prefix + JsonSerializer.Serialize(line, JsonOptions));
|
|
}
|
|
|
|
command.ReplyToCommand(Prefix + "end");
|
|
}
|
|
}
|