Używamy niezbędnych plików cookies (logowanie, bezpieczeństwo). Za Twoją zgodą włączamy też
analitykę: Google Analytics (statystyki odwiedzin) oraz Microsoft Clarity (statystyki i nagrania
zachowania na stronie, np. kliknięcia i przewijanie). Możesz odmówić i dalej korzystać ze strony.
Zgodę zmienisz w każdej chwili w Polityce prywatności i cookies.
🛠️ Szybkie wdrożenie Watchdog v2.0 na serwerze CS2
1. Dodaj lub zaimportuj serwer
Kliknij Importuj z panelu (aby pobrać swoje serwery CS2 z katalogu GamesVar) lub użyj przycisku + Dodaj serwer. System utworzy dla każdej maszyny indywidualny klucz bezpieczeństwa.
2. Wgraj wtyczkę GamesVarWatchdog.dll
Pobierz archiwum GamesVarWatchdog.zip i wypakuj je na serwerze do katalogu: addons/counterstrikesharp/plugins/GamesVarWatchdog/
3. Pobierz gotowy plik konfiguracyjny JSON
Na kafelku swojego serwera powyżej kliknij zielony przycisk 📥 Pobierz gotowy .JSON i wgraj pobrany plik bezpośrednio do: addons/counterstrikesharp/configs/plugins/GamesVarWatchdog/GamesVarWatchdog.json
4. Załaduj plugin i przetestuj komendą
Wpisz w konsoli serwera RCON/VPS: css_plugins load GamesVarWatchdog.
Aby wymusić natychmiastowy test i sprawdzić odczytaną wersję gry, wpisz w konsoli serwera: gv_watchdog.
❓ Baza Wiedzy & FAQ — Monitoring Counter-Strike 2
Szczegóły techniczne architektury Watchdog v2.0, odporności na hibernację oraz detekcji aktualizacji Valve
Jak Watchdog CS2 odróżnia awarię CounterStrikeSharp od wyłączenia całego serwera?▼
System weryfikuje serwer dwutorowo. Wtyczka C# nadaje co 120 sekund raport HTTP z wnętrza silnika gry. Jeśli przez 240 sekund puls nie dotrze do API, skaner na naszym VPS wysyła dwukrotne zapytanie UDP (protokół Source A2S_INFO) bezpośrednio pod adres IP:Port serwera:
• Awaria CounterStrikeSharp: Serwer gry odpowiada na pakiety UDP (proces CS2 żyje), ale wtyczki przestały nadawać puls HTTP (np. po nocnej aktualizacji Valve, która złamała gamedata Metamod/CSS).
• Serwer CS2 Offline: Maszyna nie odpowiada ani po HTTP, ani na pakiety UDP (proces gry uległ zawieszeniu lub serwer VPS jest wyłączony).
Co dzieje się w nocy, gdy na serwerze jest 0 graczy i CS2 przechodzi w stan hibernacji?▼
Domyślnie pusty serwer Counter-Strike 2 usypia główną pętlę gry (OnTick), co zamraża standardowe timery oparte o AddTimer w CounterStrikeSharp. Nasza wtyczka w wersji v2.0 Hibernation-Proof buforuje stan serwera w pamięci i wysyła puls za pomocą niezależnego wątku systemowego System.Threading.Timer. Dzięki temu monitoring działa nieprzerwanie 24/7 — nawet przy 0/20 graczy bez generowania fałszywych alarmów.
Skąd wiem, że mój serwer CS2 wymaga aktualizacji po łatce Valve?▼
Wtyczka automatycznie odczytuje plik steam.inf (parametr PatchVersion) przy każdym starcie mapy i przesyła wersję silnika do GamesVar. Nasze API porównuje wersję Twojego serwera z najnowszą wersją CS2 wykrytą w całej sieci. Gdy Twój serwer zostanie w tyle, obok jego nazwy pojawi się żółte ostrzeżenie ⚠️ Wymagana aktualizacja CS2.
Jak sprawdzić w konsoli serwera, czy wtyczka komunikuje się z GamesVar?▼
Wpisz w konsoli serwera komendę gv_watchdog lub css_watchdog_status. Wtyczka wyświetli zbuforowaną nazwę serwera, adres IP:Port, aktualną mapę, odczytaną wersję CS2 i CSS, czas ostatniego udanego połączenia oraz natychmiast wyśle testowy pakiet do API.
Dodaj serwer CS2 do monitoringu
Potwierdzenie
📦 Kod źródłowy wtyczki GamesVarWatchdog v2.0 (.NET 10)
PLIK: GamesVarWatchdog.cs (v2.0.0 — Odporny na hibernację)
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Timers;
namespace GamesVarWatchdog;
public class WatchdogConfig : BasePluginConfig
{
[JsonPropertyName("ApiUrl")]
public string ApiUrl { get; set; } = "https://gamesvar.com/clans/api_watchdog.php";
[JsonPropertyName("ApiToken")]
public string ApiToken { get; set; } = "TUTAJ_WKLEJ_TOKEN_Z_GAMESVAR";
[JsonPropertyName("ServerIp")]
public string ServerIp { get; set; } = "";
[JsonPropertyName("IntervalSeconds")]
public int IntervalSeconds { get; set; } = 120;
[JsonPropertyName("DebugLogs")]
public bool DebugLogs { get; set; } = false;
}
[MinimumApiVersion(80)]
public class GamesVarWatchdog : BasePlugin, IPluginConfig<WatchdogConfig>
{
public override string ModuleName => "GamesVar Server Watchdog";
public override string ModuleVersion => "2.0.0";
public override string ModuleAuthor => "GamesVar.com";
public override string ModuleDescription => "Wysyla puls stanu serwera CS2 do monitoringu GamesVar (odporny na hibernacje)";
public WatchdogConfig Config { get; set; } = new();
private static readonly HttpClient _httpClient = CreateHttpClient();
private static readonly object _consoleLock = new();
private CounterStrikeSharp.API.Modules.Timers.Timer? _engineSyncTimer;
private System.Threading.Timer? _bgHeartbeatTimer;
private int _isSending = 0;
private volatile string _cachedMapName = "unknown";
private volatile string _cachedServerName = "Serwer CS2";
private volatile string _cachedServerIp = "";
private volatile int _cachedServerPort = 27015;
private volatile int _cachedMaxPlayers = 20;
private volatile int _cachedPlayersCount = 0;
private volatile string _cachedCssVersion = "1.0.0";
private volatile string _cachedCs2Version = "";
private DateTime _lastSuccessUtc = DateTime.MinValue;
private string _lastApiStatus = "Brak wyslanego pulsu";
private static HttpClient CreateHttpClient()
{
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(12) };
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("GamesVarWatchdog", "2.0"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
public void OnConfigParsed(WatchdogConfig config)
{
config.IntervalSeconds = Math.Clamp(config.IntervalSeconds, 60, 300);
config.ApiUrl = string.IsNullOrWhiteSpace(config.ApiUrl) ? "https://gamesvar.com/clans/api_watchdog.php" : config.ApiUrl.Trim();
config.ApiToken = (config.ApiToken ?? "").Trim();
config.ServerIp = (config.ServerIp ?? "").Trim();
Config = config;
}
public override void Load(bool hotReload)
{
DetectStaticVersions();
_engineSyncTimer?.Kill();
_engineSyncTimer = AddTimer(30.0f, SyncEngineStateOnMainThread, TimerFlags.REPEAT);
RegisterListener<Listeners.OnMapStart>(mapName =>
{
if (!string.IsNullOrWhiteSpace(mapName)) _cachedMapName = mapName.Trim();
DetectCs2PatchVersion();
_engineSyncTimer?.Kill();
_engineSyncTimer = AddTimer(30.0f, SyncEngineStateOnMainThread, TimerFlags.REPEAT);
Server.NextFrame(() => { SyncEngineStateOnMainThread(); DispatchHeartbeatFromCache(); });
});
RegisterListener<Listeners.OnClientConnected>(slot => Server.NextFrame(SyncEngineStateOnMainThread));
RegisterListener<Listeners.OnClientDisconnectPost>(slot => SyncEngineStateOnMainThread());
RegisterEventHandler<EventPlayerConnectFull>((@event, info) => { Server.NextFrame(SyncEngineStateOnMainThread); return HookResult.Continue; });
RegisterEventHandler<EventPlayerDisconnect>((@event, info) => { Server.NextFrame(SyncEngineStateOnMainThread); return HookResult.Continue; });
AddCommand("css_watchdog_status", "Wyswietla status polaczenia z GamesVar Watchdog i wysyla testowy puls", OnWatchdogStatusCommand);
AddCommand("gv_watchdog", "Wyswietla status polaczenia z GamesVar Watchdog i wysyla testowy puls", OnWatchdogStatusCommand);
Server.NextFrame(() => { SyncEngineStateOnMainThread(); DispatchHeartbeatFromCache(); });
_bgHeartbeatTimer?.Dispose();
_bgHeartbeatTimer = new System.Threading.Timer(_ => DispatchHeartbeatFromCache(), null, TimeSpan.FromSeconds(Config.IntervalSeconds), TimeSpan.FromSeconds(Config.IntervalSeconds));
LogToConsole($"Plugin v{ModuleVersion} zaladowany. Interwal: {Config.IntervalSeconds}s (CSS: {_cachedCssVersion}, CS2: {(_cachedCs2Version != "" ? _cachedCs2Version : "auto")}).", ConsoleColor.Cyan);
}
public override void Unload(bool hotReload)
{
_bgHeartbeatTimer?.Dispose();
_bgHeartbeatTimer = null;
_engineSyncTimer?.Kill();
_engineSyncTimer = null;
}
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
private void OnWatchdogStatusCommand(CCSPlayerController? caller, CommandInfo command)
{
SyncEngineStateOnMainThread();
LogToConsole($"Serwer: {_cachedServerName} | Mapa: {_cachedMapName} ({_cachedPlayersCount}/{_cachedMaxPlayers}) | Status API: {_lastApiStatus}", ConsoleColor.Cyan);
DispatchHeartbeatFromCache(forceLogSuccess: true);
}
private void DetectStaticVersions()
{
try
{
Version? v = typeof(BasePlugin).Assembly.GetName().Version;
_cachedCssVersion = v != null ? $"{v.Major}.{v.Minor}.{v.Build}" : ModuleVersion;
}
catch { _cachedCssVersion = ModuleVersion; }
DetectCs2PatchVersion();
}
private void DetectCs2PatchVersion()
{
try
{
string gameDir = Server.GameDirectory;
if (string.IsNullOrWhiteSpace(gameDir)) return;
string[] paths = { Path.Combine(gameDir, "csgo", "steam.inf"), Path.Combine(gameDir, "game", "csgo", "steam.inf"), Path.Combine(gameDir, "steam.inf") };
foreach (string p in paths)
{
if (!File.Exists(p)) continue;
foreach (string line in File.ReadAllLines(p))
{
if (line.StartsWith("PatchVersion=", StringComparison.OrdinalIgnoreCase))
{
string ver = line.Substring("PatchVersion=".Length).Trim();
if (!string.IsNullOrEmpty(ver)) { _cachedCs2Version = ver; return; }
}
}
}
}
catch { }
}
private void SyncEngineStateOnMainThread()
{
try
{
if (!string.IsNullOrWhiteSpace(Server.MapName)) _cachedMapName = Server.MapName.Trim();
if (Server.MaxPlayers > 0) _cachedMaxPlayers = Server.MaxPlayers;
_cachedPlayersCount = Math.Max(0, Utilities.GetPlayers().Count(p => p != null && p.IsValid && !p.IsBot && !p.IsHLTV));
string? hostName = ConVar.Find("hostname")?.StringValue;
if (!string.IsNullOrWhiteSpace(hostName)) _cachedServerName = hostName.Trim();
string? portStr = ConVar.Find("hostport")?.StringValue;
if (!string.IsNullOrWhiteSpace(portStr) && int.TryParse(portStr.Trim(), out int port) && port > 0 && port <= 65535)
_cachedServerPort = port;
if (!string.IsNullOrWhiteSpace(Config.ServerIp)) _cachedServerIp = Config.ServerIp;
}
catch { }
}
private void DispatchHeartbeatFromCache(bool forceLogSuccess = false)
{
if (Interlocked.CompareExchange(ref _isSending, 1, 0) != 0) return;
var payload = new
{
action = "heartbeat",
api_token = Config.ApiToken,
server_name = _cachedServerName,
server_ip = _cachedServerIp,
server_port = _cachedServerPort,
current_map = _cachedMapName,
players_count = _cachedPlayersCount,
max_players = _cachedMaxPlayers,
css_version = _cachedCssVersion,
cs2_version = _cachedCs2Version
};
_ = Task.Run(async () =>
{
try { await SendHeartbeatWithRetryAsync(payload, forceLogSuccess); }
finally { Interlocked.Exchange(ref _isSending, 0); }
});
}
private async Task SendHeartbeatWithRetryAsync(object payload, bool forceLogSuccess)
{
string jsonString = JsonSerializer.Serialize(payload);
for (int attempt = 1; attempt <= 3; attempt++)
{
try
{
using var req = new HttpRequestMessage(HttpMethod.Post, Config.ApiUrl)
{
Content = new StringContent(jsonString, Encoding.UTF8, "application/json")
};
if (!string.IsNullOrEmpty(Config.ApiToken)) req.Headers.TryAddWithoutValidation("X-Watchdog-Token", Config.ApiToken);
using var res = await _httpClient.SendAsync(req);
if (res.IsSuccessStatusCode)
{
_lastSuccessUtc = DateTime.UtcNow;
_lastApiStatus = $"OK (HTTP {(int)res.StatusCode})";
if (forceLogSuccess || Config.DebugLogs)
LogToConsole($"Puls wyslany pomyslnie ({_cachedPlayersCount}/{_cachedMaxPlayers} graczy, mapa: {_cachedMapName}).", ConsoleColor.Green);
return;
}
_lastApiStatus = $"Blad HTTP {(int)res.StatusCode}";
}
catch (Exception ex) { _lastApiStatus = $"Wyjatek: {ex.Message}"; }
if (attempt < 3) await Task.Delay(TimeSpan.FromSeconds(15));
}
}
private static void LogToConsole(string message, ConsoleColor color)
{
lock (_consoleLock)
{
Console.ForegroundColor = color;
Console.WriteLine($"[GamesVar-Watchdog] {message}");
Console.ResetColor();
}
}
}