diff --git a/CS2WebSocketTelemetryPlugin/CS2WebSocketTelemetryPlugin.cs b/CS2WebSocketTelemetryPlugin/CS2WebSocketTelemetryPlugin.cs index e209b0b..ebc61f5 100644 --- a/CS2WebSocketTelemetryPlugin/CS2WebSocketTelemetryPlugin.cs +++ b/CS2WebSocketTelemetryPlugin/CS2WebSocketTelemetryPlugin.cs @@ -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 _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(OnPlayerConnectFull); RegisterEventHandler(OnPlayerDisconnect); + // Runden-/Bomben-Phase + RegisterEventHandler(OnRoundStart); + RegisterEventHandler(OnRoundFreezeEnd); + RegisterEventHandler(OnRoundEnd); + RegisterEventHandler(OnBombPlanted); + RegisterEventHandler(OnBombDefused); + RegisterEventHandler(OnBombExploded); + + // (optional, falls verfügbar) + // RegisterEventHandler(OnWarmupStart); + // RegisterEventHandler(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) diff --git a/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll b/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll index ed89f4b..52eda62 100644 Binary files a/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll and b/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll differ diff --git a/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb b/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb index fac5a87..5f2c6e9 100644 Binary files a/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb and b/CS2WebSocketTelemetryPlugin/bin/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb differ diff --git a/CS2WebSocketTelemetryPlugin/obj/CS2WebSocketTelemetryPlugin.csproj.nuget.g.props b/CS2WebSocketTelemetryPlugin/obj/CS2WebSocketTelemetryPlugin.csproj.nuget.g.props index 2b4790d..6fb2b23 100644 --- a/CS2WebSocketTelemetryPlugin/obj/CS2WebSocketTelemetryPlugin.csproj.nuget.g.props +++ b/CS2WebSocketTelemetryPlugin/obj/CS2WebSocketTelemetryPlugin.csproj.nuget.g.props @@ -5,18 +5,18 @@ NuGet $(MSBuildThisFileDirectory)project.assets.json $(UserProfile)\.nuget\packages\ - C:\Users\Rother\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + C:\Users\Chris\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.14.0 + 6.11.1 - + - C:\Users\Rother\.nuget\packages\microsoft.dotnet.apicompat.task\8.0.203 + C:\Users\Chris\.nuget\packages\microsoft.dotnet.apicompat.task\8.0.203 \ No newline at end of file diff --git a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.AssemblyInfo.cs b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.AssemblyInfo.cs new file mode 100644 index 0000000..7801f71 --- /dev/null +++ b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +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. + diff --git a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.GeneratedMSBuildEditorConfig.editorconfig b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.GeneratedMSBuildEditorConfig.editorconfig index e69493c..93b1b8b 100644 --- a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.GeneratedMSBuildEditorConfig.editorconfig +++ b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.GeneratedMSBuildEditorConfig.editorconfig @@ -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 = diff --git a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll index ed89f4b..52eda62 100644 Binary files a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll and b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.dll differ diff --git a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb index fac5a87..5f2c6e9 100644 Binary files a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb and b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/CS2WebSocketTelemetryPlugin.pdb differ diff --git a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/ref/CS2WebSocketTelemetryPlugin.dll b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/ref/CS2WebSocketTelemetryPlugin.dll index 43c280b..8f390bc 100644 Binary files a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/ref/CS2WebSocketTelemetryPlugin.dll and b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/ref/CS2WebSocketTelemetryPlugin.dll differ diff --git a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/refint/CS2WebSocketTelemetryPlugin.dll b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/refint/CS2WebSocketTelemetryPlugin.dll index 43c280b..8f390bc 100644 Binary files a/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/refint/CS2WebSocketTelemetryPlugin.dll and b/CS2WebSocketTelemetryPlugin/obj/Debug/net8.0/refint/CS2WebSocketTelemetryPlugin.dll differ diff --git a/CS2WebSocketTelemetryPlugin/obj/project.assets.json b/CS2WebSocketTelemetryPlugin/obj/project.assets.json index 416fe03..e6270f5 100644 --- a/CS2WebSocketTelemetryPlugin/obj/project.assets.json +++ b/CS2WebSocketTelemetryPlugin/obj/project.assets.json @@ -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" } } }