diff --git a/.github/workflows/metamod-update.yaml b/.github/workflows/metamod-update.yaml index 0f891b72..894ffa23 100644 --- a/.github/workflows/metamod-update.yaml +++ b/.github/workflows/metamod-update.yaml @@ -18,9 +18,14 @@ jobs: - name: Check latest release URL run: | + # not every release ships a linux tarball, so take the newest one that does LATEST_URL=$(curl -sf -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/repos/alliedmodders/metamod-source/releases?per_page=20" \ - | jq -r '[.[] | select(.tag_name | startswith("2."))] | .[0].assets[] | select(.name | test("^mmsource-.*-linux\\.tar\\.gz$")) | .browser_download_url') + | jq -r '[.[] | select(.tag_name | startswith("2.")) | .assets[] | select(.name | test("^mmsource-.*-linux\\.tar\\.gz$")) | .browser_download_url] | first // ""') + if [ -z "$LATEST_URL" ]; then + echo "::error::No linux release asset found for metamod-source 2.x" + exit 1 + fi echo "LATEST_URL=$LATEST_URL" >> $GITHUB_ENV - name: Compare URLs @@ -33,10 +38,10 @@ jobs: - name: Update Dockerfile (optional) if: env.CURRENT_URL != env.LATEST_URL run: | - sed -i 's|ENV METAMOD_URL=.*|ENV METAMOD_URL=${{ env.LATEST_URL }}|' apps/counterstrikesharp/Dockerfile + sed -i "s|ENV METAMOD_URL=.*|ENV METAMOD_URL=${LATEST_URL}|" apps/counterstrikesharp/Dockerfile git config user.name github-actions git config user.email github-actions@github.com git add apps/counterstrikesharp/Dockerfile - BUILD_ID=$(echo ${{ env.LATEST_URL }} | grep -oP '(?<=mmsource-)[^/]+(?=-linux)') + BUILD_ID=$(echo "$LATEST_URL" | grep -oP '(?<=mmsource-)[^/]+(?=-linux)') git commit -m "chore: update metamod to version ${BUILD_ID}" git push diff --git a/apps/counterstrikesharp/src/FiveStack.Commands/Status.cs b/apps/counterstrikesharp/src/FiveStack.Commands/Status.cs new file mode 100644 index 00000000..dd96ac5d --- /dev/null +++ b/apps/counterstrikesharp/src/FiveStack.Commands/Status.cs @@ -0,0 +1,47 @@ +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Commands; +using FiveStack.Entities; + +namespace FiveStack; + +public partial class FiveStackPlugin +{ + [ConsoleCommand("fivestack_status", "Reports 5Stack plugin and match state")] + [CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)] + public void FiveStackStatus(CCSPlayerController? player, CommandInfo command) + { + command.ReplyToCommand(BuildStatusReport()); + } + + private string BuildStatusReport() + { + MatchManager? match = _matchService.GetCurrentMatch(); + MatchData? matchData = match?.GetMatchData(); + MatchMap? currentMap = match?.GetCurrentMap(); + + List lines = + [ + "----- 5Stack -----", + $"Plugin Version: {ModuleVersion}", + $"Plugin Runtime: counterstrikesharp", + $"Server ID: {_environmentService.GetServerId() ?? "unassigned"}", + $"API: {_environmentService.GetApiUrl()}", + $"API Socket: {(_environmentService.IsOfflineMode() ? "offline mode" : _matchEvents.IsConnected() ? "connected" : "disconnected")}", + ]; + + if (matchData == null) + { + lines.Add("Match: none"); + } + else + { + lines.Add($"Match: {matchData.id}"); + lines.Add($"Map: {currentMap?.map.name ?? "unknown"}"); + lines.Add($"Map Status: {match!.GetCurrentMapStatus()}"); + lines.Add($"Timeout Active: {_timeoutSystem.IsTimeoutActive()}"); + } + + return string.Join("\n", lines); + } +} diff --git a/apps/counterstrikesharp/src/FiveStack.Services/MatchEvents.cs b/apps/counterstrikesharp/src/FiveStack.Services/MatchEvents.cs index cf966b6e..d2d0533d 100644 --- a/apps/counterstrikesharp/src/FiveStack.Services/MatchEvents.cs +++ b/apps/counterstrikesharp/src/FiveStack.Services/MatchEvents.cs @@ -105,6 +105,11 @@ public class RoundResultSnapshot public RoundResultSnapshot? PendingRoundResult { get; set; } + public bool IsConnected() + { + return _webSocket?.State == WebSocketState.Open; + } + public void ClearPendingRoundResult() { if (PendingRoundResult != null) diff --git a/apps/counterstrikesharp/src/FiveStack.Services/MatchManager.cs b/apps/counterstrikesharp/src/FiveStack.Services/MatchManager.cs index a7962e24..8897e2da 100644 --- a/apps/counterstrikesharp/src/FiveStack.Services/MatchManager.cs +++ b/apps/counterstrikesharp/src/FiveStack.Services/MatchManager.cs @@ -109,6 +109,11 @@ public void Init(MatchData match) return _activeMapId; } + public eMapStatus GetCurrentMapStatus() + { + return _currentMapStatus; + } + public void SyncActiveMapAfterMapStart() { _activeMapId = GetCurrentMap()?.id; diff --git a/apps/swiftly/src/FiveStack.Commands/Status.cs b/apps/swiftly/src/FiveStack.Commands/Status.cs new file mode 100644 index 00000000..d7d6fa07 --- /dev/null +++ b/apps/swiftly/src/FiveStack.Commands/Status.cs @@ -0,0 +1,51 @@ +using FiveStack.Entities; +using SwiftlyS2.Shared.Commands; + +namespace FiveStack; + +public partial class FiveStackPlugin +{ + [Command("fivestack_status", registerRaw: true, permission: "")] + public void FiveStackStatus(ICommandContext context) + { + if (context.IsSentByPlayer) + { + return; + } + + // Reply() (not the logger) is what reaches RCON: SwiftlyS2 routes its + // logger to its own sink, while Reply from console goes through Msg(). + context.Reply(BuildStatusReport()); + } + + private string BuildStatusReport() + { + MatchManager? match = _matchService.GetCurrentMatch(); + MatchData? matchData = match?.GetMatchData(); + MatchMap? currentMap = match?.GetCurrentMap(); + + List lines = + [ + "----- 5Stack -----", + $"Plugin Version: {ModuleVersion}", + $"Plugin Runtime: swiftlys2", + $"Server ID: {_environmentService.GetServerId() ?? "unassigned"}", + $"API: {_environmentService.GetApiUrl()}", + $"API Socket: {(_environmentService.IsOfflineMode() ? "offline mode" : _matchEvents.IsConnected() ? "connected" : "disconnected")}", + ]; + + if (matchData == null) + { + lines.Add("Match: none"); + } + else + { + lines.Add($"Match: {matchData.id}"); + lines.Add($"Map: {currentMap?.map.name ?? "unknown"}"); + lines.Add($"Map Status: {match!.GetCurrentMapStatus()}"); + lines.Add($"Timeout Active: {match.timeoutSystem.IsTimeoutActive()}"); + } + + return string.Join("\n", lines); + } +} diff --git a/apps/swiftly/src/FiveStack.Services/MatchEvents.cs b/apps/swiftly/src/FiveStack.Services/MatchEvents.cs index e280e168..3c2108df 100644 --- a/apps/swiftly/src/FiveStack.Services/MatchEvents.cs +++ b/apps/swiftly/src/FiveStack.Services/MatchEvents.cs @@ -105,6 +105,11 @@ public class RoundResultSnapshot public RoundResultSnapshot? PendingRoundResult { get; set; } + public bool IsConnected() + { + return _webSocket?.State == WebSocketState.Open; + } + public void ClearPendingRoundResult() { if (PendingRoundResult != null) diff --git a/apps/swiftly/src/FiveStack.Services/MatchManager.cs b/apps/swiftly/src/FiveStack.Services/MatchManager.cs index 087bf56c..c219726f 100644 --- a/apps/swiftly/src/FiveStack.Services/MatchManager.cs +++ b/apps/swiftly/src/FiveStack.Services/MatchManager.cs @@ -110,6 +110,11 @@ public void Init(MatchData match) return _activeMapId; } + public eMapStatus GetCurrentMapStatus() + { + return _currentMapStatus; + } + public void SyncActiveMapAfterMapStart() { _activeMapId = GetCurrentMap()?.id;