This commit is contained in:
Linrador 2025-09-17 14:08:02 +02:00
parent 5cd0409eda
commit 20e18fca62
11 changed files with 104 additions and 28 deletions

View File

@ -51,6 +51,7 @@ public class MetaWebSocketPlugin : BasePlugin
private CancellationTokenSource? _serverCts; private CancellationTokenSource? _serverCts;
private Task? _acceptTask; private Task? _acceptTask;
private volatile bool _serverRunning = false; private volatile bool _serverRunning = false;
private volatile string _phase = "unknown";
private readonly ConcurrentDictionary<int, ClientState> _clients = new(); private readonly ConcurrentDictionary<int, ClientState> _clients = new();
private int _clientSeq = 0; private int _clientSeq = 0;
@ -63,6 +64,14 @@ public class MetaWebSocketPlugin : BasePlugin
public string? CertPassword { get; set; } // optional 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 private sealed class ClientState
{ {
public required TcpClient Tcp; public required TcpClient Tcp;
@ -74,6 +83,17 @@ public class MetaWebSocketPlugin : BasePlugin
// ------------- Helpers ------------- // ------------- Helpers -------------
private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private string BuildMapPayload()
{
return JsonSerializer.Serialize(new {
type = "map",
name = _mapName,
phase = _phase,
t = NowMs()
});
}
private void TrySafeInitialPush() private void TrySafeInitialPush()
{ {
try try
@ -82,9 +102,10 @@ public class MetaWebSocketPlugin : BasePlugin
if (!string.IsNullOrEmpty(mn)) if (!string.IsNullOrEmpty(mn))
{ {
_mapName = mn!; _mapName = mn!;
Broadcast(JsonSerializer.Serialize(new { type = "map", name = _mapName, t = NowMs() })); Broadcast(BuildMapPayload()); // 👈 statt anonymes Json
} }
SendFullPlayerList(); // nur hier oder aus Events heraus aufrufen SendFullPlayerList(); // nur hier oder aus Events heraus aufrufen
Broadcast(JsonSerializer.Serialize(new { type = "phase", phase = _phase, t = NowMs() }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -197,6 +218,18 @@ public class MetaWebSocketPlugin : BasePlugin
RegisterEventHandler<EventPlayerConnectFull>(OnPlayerConnectFull); RegisterEventHandler<EventPlayerConnectFull>(OnPlayerConnectFull);
RegisterEventHandler<EventPlayerDisconnect>(OnPlayerDisconnect); 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(); LoadAndApplyConfig();
if (_enabled) StartWebSocket(); if (_enabled) StartWebSocket();
@ -217,12 +250,15 @@ public class MetaWebSocketPlugin : BasePlugin
private void OnMapStart(string newMap) private void OnMapStart(string newMap)
{ {
_mapName = newMap ?? Server.MapName ?? ""; _mapName = newMap ?? Server.MapName ?? "";
Broadcast(JsonSerializer.Serialize(new { type = "map", name = _mapName, t = NowMs() }));
// Nach Mapstart zusätzlich die Spielerliste pushen SetPhase("warmup"); // 👈 zuerst Phase setzen (sendet auch ein separates phase-Event)
Broadcast(BuildMapPayload()); // 👈 Map + Phase zusammen
SendFullPlayerList(); SendFullPlayerList();
Logger.LogInformation($"[WS] Map gewechselt: '{(_mapName ?? "")}' Meta gesendet."); Logger.LogInformation($"[WS] Map gewechselt: '{(_mapName ?? "")}' Meta gesendet.");
} }
private HookResult OnPlayerConnectFull(EventPlayerConnectFull ev, GameEventInfo info) private HookResult OnPlayerConnectFull(EventPlayerConnectFull ev, GameEventInfo info)
{ {
try try
@ -274,6 +310,49 @@ public class MetaWebSocketPlugin : BasePlugin
return HookResult.Continue; return HookResult.Continue;
} }
private HookResult OnRoundStart(EventRoundStart ev, GameEventInfo info)
{
// Start der Runde == Freezezeit
SetPhase("freezetime");
return HookResult.Continue;
}
private HookResult OnRoundFreezeEnd(EventRoundFreezeEnd ev, GameEventInfo info)
{
// Ende Freeze -> Live
SetPhase("live");
return HookResult.Continue;
}
private HookResult OnRoundEnd(EventRoundEnd ev, GameEventInfo info)
{
SetPhase("over");
return HookResult.Continue;
}
private HookResult OnBombPlanted(EventBombPlanted ev, GameEventInfo info)
{
SetPhase("bomb");
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 ------------- // ------------- Console Commands -------------
[ConsoleCommand("css_meta_enable", "WS(S)-Server aktivieren/deaktivieren: css_meta_enable 1|0")] [ConsoleCommand("css_meta_enable", "WS(S)-Server aktivieren/deaktivieren: css_meta_enable 1|0")]
[CommandHelper(minArgs: 1, usage: "<1|0>")] [CommandHelper(minArgs: 1, usage: "<1|0>")]
@ -385,7 +464,7 @@ public class MetaWebSocketPlugin : BasePlugin
[ConsoleCommand("css_meta_sendmap", "Sendet die aktuelle Karte an alle verbundenen Clients")] [ConsoleCommand("css_meta_sendmap", "Sendet die aktuelle Karte an alle verbundenen Clients")]
public void CmdSendMap(CCSPlayerController? caller, CommandInfo cmd) 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."); cmd.ReplyToCommand($"[WS] Map '{_mapName}' an Clients gesendet.");
} }
@ -552,14 +631,16 @@ public class MetaWebSocketPlugin : BasePlugin
try try
{ {
if (!_clients.ContainsKey(id) || state.Cts.IsCancellationRequested) return; if (!_clients.ContainsKey(id) || state.Cts.IsCancellationRequested) return;
var nowMs = NowMs(); SendTextFrame(state, BuildMapPayload()); // 👈
SendTextFrame(state, JsonSerializer.Serialize(new { type = "map", name = _mapName, t = nowMs })); var buf = BuildPlayersPayload();
var buf = BuildPlayersPayload(); // jetzt safe
SendTextFrame(state, buf); SendTextFrame(state, buf);
// optional zusätzlich:
// SendTextFrame(state, JsonSerializer.Serialize(new { type = "phase", phase = _phase, t = NowMs() }));
} }
catch { /* ignore */ } catch { /* ignore */ }
}); });
await ReceiveLoop(state, serverCt); await ReceiveLoop(state, serverCt);
} }
catch (OperationCanceledException) { } catch (OperationCanceledException) { }

View File

@ -5,18 +5,18 @@
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool> <RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile> <ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot> <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> <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion> <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.11.1</NuGetToolVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> <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\" /> <SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
</ItemGroup> </ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> <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')" /> <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> </ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> <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> </PropertyGroup>
</Project> </Project>

View File

@ -13,10 +13,10 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("CS2WebSocketTelemetryPlugin")] [assembly: System.Reflection.AssemblyCompanyAttribute("CS2WebSocketTelemetryPlugin")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+476da04a6d243eceba7c8b28e101211f978faf12")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5cd0409eda59707069d32b70b5c63a1fd2d8956a")]
[assembly: System.Reflection.AssemblyProductAttribute("CS2WebSocketTelemetryPlugin")] [assembly: System.Reflection.AssemblyProductAttribute("CS2WebSocketTelemetryPlugin")]
[assembly: System.Reflection.AssemblyTitleAttribute("CS2WebSocketTelemetryPlugin")] [assembly: System.Reflection.AssemblyTitleAttribute("CS2WebSocketTelemetryPlugin")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class. // Von der MSBuild WriteCodeFragment-Klasse generiert.

View File

@ -8,8 +8,6 @@ build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules = build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = CS2WebSocketTelemetryPlugin 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.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop = build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 8.0
build_property.EnableCodeStyleSeverity =

View File

@ -2555,31 +2555,29 @@
] ]
}, },
"packageFolders": { "packageFolders": {
"C:\\Users\\Rother\\.nuget\\packages\\": {}, "C:\\Users\\Chris\\.nuget\\packages\\": {},
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
}, },
"project": { "project": {
"version": "1.0.0", "version": "1.0.0",
"restore": { "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", "projectName": "CS2WebSocketTelemetryPlugin",
"projectPath": "C:\\Users\\Rother\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\CS2WebSocketTelemetryPlugin.csproj", "projectPath": "C:\\Users\\Chris\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\CS2WebSocketTelemetryPlugin.csproj",
"packagesPath": "C:\\Users\\Rother\\.nuget\\packages\\", "packagesPath": "C:\\Users\\Chris\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Rother\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\obj\\", "outputPath": "C:\\Users\\Chris\\fork\\ironie-cs2-websocket-plugin\\CS2WebSocketTelemetryPlugin\\obj\\",
"projectStyle": "PackageReference", "projectStyle": "PackageReference",
"fallbackFolders": [ "fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
], ],
"configFilePaths": [ "configFilePaths": [
"C:\\Users\\Rother\\AppData\\Roaming\\NuGet\\NuGet.Config", "C:\\Users\\Chris\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config"
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
], ],
"originalTargetFrameworks": [ "originalTargetFrameworks": [
"net8.0" "net8.0"
], ],
"sources": { "sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"https://api.nuget.org/v3/index.json": {} "https://api.nuget.org/v3/index.json": {}
}, },
"frameworks": { "frameworks": {
@ -2597,8 +2595,7 @@
"enableAudit": "true", "enableAudit": "true",
"auditLevel": "low", "auditLevel": "low",
"auditMode": "direct" "auditMode": "direct"
}, }
"SdkAnalysisLevel": "9.0.300"
}, },
"frameworks": { "frameworks": {
"net8.0": { "net8.0": {
@ -2625,7 +2622,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.304/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.408/PortableRuntimeIdentifierGraph.json"
} }
} }
} }