Merge branch 'main' of https://git.rother-woelki.de/chris/ironie-cs2-websocket-plugin
# Conflicts: # CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.AssemblyInfo.cs
This commit is contained in:
commit
a8e6f612ee
@ -21,6 +21,7 @@ using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WsTelemetry;
|
||||
@ -46,11 +47,16 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
private X509Certificate2? _serverCert;
|
||||
|
||||
private volatile string _mapName = "";
|
||||
private volatile string _serverName = "";
|
||||
|
||||
private int _scoreCT = 0;
|
||||
private int _scoreT = 0;
|
||||
|
||||
private TcpListener? _listener;
|
||||
private CancellationTokenSource? _serverCts;
|
||||
private Task? _acceptTask;
|
||||
private volatile bool _serverRunning = false;
|
||||
private volatile string _phase = "unknown";
|
||||
|
||||
private readonly ConcurrentDictionary<int, ClientState> _clients = new();
|
||||
private int _clientSeq = 0;
|
||||
@ -63,6 +69,14 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
public string? CertPassword { get; set; } // optional
|
||||
}
|
||||
|
||||
private void SetPhase(string p)
|
||||
{
|
||||
var s = (p ?? "unknown").ToLowerInvariant();
|
||||
if (s == _phase) return;
|
||||
_phase = s;
|
||||
Broadcast(JsonSerializer.Serialize(new { type = "phase", phase = _phase, t = NowMs() }));
|
||||
}
|
||||
|
||||
private sealed class ClientState
|
||||
{
|
||||
public required TcpClient Tcp;
|
||||
@ -74,17 +88,44 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
// ------------- Helpers -------------
|
||||
private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
private string BuildMapPayload()
|
||||
{
|
||||
return JsonSerializer.Serialize(new {
|
||||
type = "map",
|
||||
name = _mapName,
|
||||
serverName = _serverName,
|
||||
t = NowMs()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void TrySafeInitialPush()
|
||||
{
|
||||
try
|
||||
{
|
||||
var mn = Server.MapName; // jetzt sicher, weil NextFrame/MapStart
|
||||
if (!string.IsNullOrEmpty(mn))
|
||||
RefreshServerName();
|
||||
|
||||
// Map-Name aus dem Server holen (erst im NextFrame sicher verfügbar)
|
||||
var mn = Server.MapName;
|
||||
if (!string.IsNullOrWhiteSpace(mn))
|
||||
{
|
||||
_mapName = mn!;
|
||||
Broadcast(JsonSerializer.Serialize(new { type = "map", name = _mapName, t = NowMs() }));
|
||||
// Map + (aktuelle) Phase in EINEM Payload
|
||||
Broadcast(BuildMapPayload());
|
||||
}
|
||||
SendFullPlayerList(); // nur hier oder aus Events heraus aufrufen
|
||||
|
||||
// Vollständige Spielerliste
|
||||
SendFullPlayerList();
|
||||
|
||||
// Phase separat noch einmal senden (falls ein Client erst danach connected)
|
||||
Broadcast(JsonSerializer.Serialize(new {
|
||||
type = "phase",
|
||||
phase = _phase,
|
||||
t = NowMs()
|
||||
}));
|
||||
|
||||
// Optional: aktuellen Score mitschicken (falls du BroadcastScore() implementiert hast)
|
||||
BroadcastScore();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -92,6 +133,26 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshServerName()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Standard in Source/CS2
|
||||
var s = ConVar.Find("hostname")?.StringValue;
|
||||
if (!string.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
_serverName = s!;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback (vereinzelt genutzt)
|
||||
s = ConVar.Find("host_name")?.StringValue;
|
||||
if (!string.IsNullOrWhiteSpace(s))
|
||||
_serverName = s!;
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
|
||||
private void LoadAndApplyConfig(bool generateIfMissing = true)
|
||||
{
|
||||
try
|
||||
@ -197,6 +258,18 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
RegisterEventHandler<EventPlayerConnectFull>(OnPlayerConnectFull);
|
||||
RegisterEventHandler<EventPlayerDisconnect>(OnPlayerDisconnect);
|
||||
|
||||
// Runden-/Bomben-Phase
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventRoundFreezeEnd>(OnRoundFreezeEnd);
|
||||
RegisterEventHandler<EventRoundEnd>(OnRoundEnd);
|
||||
RegisterEventHandler<EventBombPlanted>(OnBombPlanted);
|
||||
RegisterEventHandler<EventBombDefused>(OnBombDefused);
|
||||
RegisterEventHandler<EventBombExploded>(OnBombExploded);
|
||||
|
||||
// (optional, falls verfügbar)
|
||||
// RegisterEventHandler<EventWarmupStart>(OnWarmupStart);
|
||||
// RegisterEventHandler<EventWarmupEnd>(OnWarmupEnd);
|
||||
|
||||
LoadAndApplyConfig();
|
||||
|
||||
if (_enabled) StartWebSocket();
|
||||
@ -217,12 +290,21 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
private void OnMapStart(string newMap)
|
||||
{
|
||||
_mapName = newMap ?? Server.MapName ?? "";
|
||||
Broadcast(JsonSerializer.Serialize(new { type = "map", name = _mapName, t = NowMs() }));
|
||||
// Nach Mapstart zusätzlich die Spielerliste pushen
|
||||
|
||||
// Score neu starten
|
||||
_scoreCT = 0;
|
||||
_scoreT = 0;
|
||||
|
||||
RefreshServerName();
|
||||
|
||||
SetPhase("warmup");
|
||||
Broadcast(BuildMapPayload());
|
||||
SendFullPlayerList();
|
||||
BroadcastScore(); // gleich initial 0:0 senden
|
||||
Logger.LogInformation($"[WS] Map gewechselt: '{(_mapName ?? "")}' – Meta gesendet.");
|
||||
}
|
||||
|
||||
|
||||
private HookResult OnPlayerConnectFull(EventPlayerConnectFull ev, GameEventInfo info)
|
||||
{
|
||||
try
|
||||
@ -274,6 +356,75 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart ev, GameEventInfo info)
|
||||
{
|
||||
// Start der Runde == Freezezeit
|
||||
SetPhase("freezetime");
|
||||
BroadcastScore();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundFreezeEnd(EventRoundFreezeEnd ev, GameEventInfo info)
|
||||
{
|
||||
// Ende Freeze -> Live
|
||||
SetPhase("live");
|
||||
BroadcastScore();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundEnd(EventRoundEnd ev, GameEventInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Viele Builds haben ev.Winner (2 = T, 3 = CT).
|
||||
// Falls in deinem Build anders, kannst du unten noch "WinnerTeam" per Reflection abfragen.
|
||||
int? winner = null;
|
||||
|
||||
// direkt zugreifen, wenn Property existiert
|
||||
try { winner = ev.Winner; } catch { /* Property evtl. nicht vorhanden */ }
|
||||
|
||||
// Fallback per Reflection (WinnerTeam / Team / …)
|
||||
if (!winner.HasValue)
|
||||
{
|
||||
winner = TryGetIntProp(ev, "Winner")
|
||||
?? TryGetIntProp(ev, "WinnerTeam")
|
||||
?? TryGetIntProp(ev, "Team");
|
||||
}
|
||||
|
||||
if (winner == 3) _scoreCT++;
|
||||
else if (winner == 2) _scoreT++;
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
|
||||
SetPhase("over");
|
||||
BroadcastScore();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnBombPlanted(EventBombPlanted ev, GameEventInfo info)
|
||||
{
|
||||
SetPhase("bomb");
|
||||
BroadcastScore();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnBombDefused(EventBombDefused ev, GameEventInfo info)
|
||||
{
|
||||
// Bombe ist weg, Runde läuft weiter bis RoundEnd
|
||||
SetPhase("live");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnBombExploded(EventBombExploded ev, GameEventInfo info)
|
||||
{
|
||||
SetPhase("over");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// (optional)
|
||||
// private HookResult OnWarmupStart(EventWarmupStart ev, GameEventInfo info) { SetPhase("warmup"); return HookResult.Continue; }
|
||||
// private HookResult OnWarmupEnd(EventWarmupEnd ev, GameEventInfo info) { SetPhase("freezetime"); return HookResult.Continue; }
|
||||
|
||||
// ------------- Console Commands -------------
|
||||
[ConsoleCommand("css_meta_enable", "WS(S)-Server aktivieren/deaktivieren: css_meta_enable 1|0")]
|
||||
[CommandHelper(minArgs: 1, usage: "<1|0>")]
|
||||
@ -385,7 +536,7 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
[ConsoleCommand("css_meta_sendmap", "Sendet die aktuelle Karte an alle verbundenen Clients")]
|
||||
public void CmdSendMap(CCSPlayerController? caller, CommandInfo cmd)
|
||||
{
|
||||
Broadcast(JsonSerializer.Serialize(new { type = "map", name = _mapName, t = NowMs() }));
|
||||
Broadcast(BuildMapPayload());
|
||||
cmd.ReplyToCommand($"[WS] Map '{_mapName}' an Clients gesendet.");
|
||||
}
|
||||
|
||||
@ -552,14 +703,16 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
try
|
||||
{
|
||||
if (!_clients.ContainsKey(id) || state.Cts.IsCancellationRequested) return;
|
||||
var nowMs = NowMs();
|
||||
SendTextFrame(state, JsonSerializer.Serialize(new { type = "map", name = _mapName, t = nowMs }));
|
||||
var buf = BuildPlayersPayload(); // jetzt safe
|
||||
SendTextFrame(state, BuildMapPayload()); // ✅ Map ohne Phase
|
||||
var buf = BuildPlayersPayload();
|
||||
SendTextFrame(state, buf);
|
||||
// ✅ Phase EINMAL separat für den neu verbundenen Client
|
||||
SendTextFrame(state, JsonSerializer.Serialize(new { type = "phase", phase = _phase, t = NowMs() }));
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
});
|
||||
|
||||
|
||||
await ReceiveLoop(state, serverCt);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
@ -599,6 +752,27 @@ public class MetaWebSocketPlugin : BasePlugin
|
||||
return JsonSerializer.Serialize(new { type = "players", t = NowMs(), players = list });
|
||||
}
|
||||
|
||||
// kleiner Helper für obige Reflection-Zugriffe
|
||||
private static int? TryGetIntProp(object obj, string prop)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pi = obj.GetType().GetProperty(prop, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (pi != null)
|
||||
{
|
||||
var v = pi.GetValue(obj);
|
||||
if (v is int i) return i;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
private void BroadcastScore()
|
||||
{
|
||||
Broadcast(JsonSerializer.Serialize(new { type = "score", ct = _scoreCT, t = _scoreT, tms = NowMs() }));
|
||||
}
|
||||
|
||||
private void Broadcast(string text)
|
||||
{
|
||||
foreach (var kv in _clients)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -5,18 +5,18 @@
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Rother\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Chris\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.11.1</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\Rother\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Users\Chris\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Configuration.UserSecrets.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgMicrosoft_DotNet_ApiCompat_Task Condition=" '$(PkgMicrosoft_DotNet_ApiCompat_Task)' == '' ">C:\Users\Rother\.nuget\packages\microsoft.dotnet.apicompat.task\8.0.203</PkgMicrosoft_DotNet_ApiCompat_Task>
|
||||
<PkgMicrosoft_DotNet_ApiCompat_Task Condition=" '$(PkgMicrosoft_DotNet_ApiCompat_Task)' == '' ">C:\Users\Chris\.nuget\packages\microsoft.dotnet.apicompat.task\8.0.203</PkgMicrosoft_DotNet_ApiCompat_Task>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("CS2WebSocketTelemetryPlugin")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+20e18fca62bdfacbfc20e6e98cbe220dc389b3cb")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("CS2WebSocketTelemetryPlugin")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("CS2WebSocketTelemetryPlugin")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
||||
|
||||
@ -8,8 +8,6 @@ build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = CS2WebSocketTelemetryPlugin
|
||||
build_property.ProjectDir = C:\Users\Rother\fork\ironie-cs2-websocket-plugin\CS2WebSocketTelemetryPlugin\
|
||||
build_property.ProjectDir = C:\Users\Chris\fork\ironie-cs2-websocket-plugin\CS2WebSocketTelemetryPlugin\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2555,31 +2555,29 @@
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\Rother\\.nuget\\packages\\": {},
|
||||
"C:\\Users\\Chris\\.nuget\\packages\\": {},
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\Rother\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\CS2WebSocketTelemetryPlugin.csproj",
|
||||
"projectUniqueName": "C:\\Users\\Chris\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\CS2WebSocketTelemetryPlugin.csproj",
|
||||
"projectName": "CS2WebSocketTelemetryPlugin",
|
||||
"projectPath": "C:\\Users\\Rother\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\CS2WebSocketTelemetryPlugin.csproj",
|
||||
"packagesPath": "C:\\Users\\Rother\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\Rother\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\obj\\",
|
||||
"projectPath": "C:\\Users\\Chris\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\CS2WebSocketTelemetryPlugin.csproj",
|
||||
"packagesPath": "C:\\Users\\Chris\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\Chris\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\Rother\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
"C:\\Users\\Chris\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
@ -2597,8 +2595,7 @@
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.300"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
@ -2625,7 +2622,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.304/PortableRuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.408/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user