WebPanelBridge 0.1.0: live server status for simpleadmin-web over RCON

This commit is contained in:
Astra 2026-09-25 19:59:44 +01:00
commit d02c8454df
5 changed files with 257 additions and 0 deletions

79
WebPanelBridge.cs Normal file
View file

@ -0,0 +1,79 @@
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.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils;
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. Nothing here changes game state.
public class WebPanelBridge : BasePlugin
{
public override string ModuleName => "WebPanelBridge";
public override string ModuleVersion => "0.1.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 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));
foreach (var player in Utilities.GetPlayers())
{
if (player is not { IsValid: true, IsHLTV: false } || player.Connected != PlayerConnectedState.Connected)
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");
}
}