diff --git a/Admin/Package/Screens/MonitoringTestScreen.php b/Admin/Package/Screens/MonitoringTestScreen.php
index 5a74fd3..15fd3e7 100644
--- a/Admin/Package/Screens/MonitoringTestScreen.php
+++ b/Admin/Package/Screens/MonitoringTestScreen.php
@@ -96,6 +96,7 @@ public function layout(): array
'auto' => __('monitoring.admin.protocol_auto'),
'source' => 'Source Engine',
'goldsrc' => 'GoldSource (CS 1.6)',
+ 'xash3d' => 'Xash3D FWGS Engine',
'minecraft' => 'Minecraft',
'samp' => 'SA-MP',
'gta5' => 'GTA V (FiveM/RageMP)',
@@ -175,6 +176,7 @@ protected function runTest(): void
$protocolMap = [
'source' => '730',
'goldsrc' => '10',
+ 'xash3d' => 'xash3d',
'minecraft' => 'minecraft',
'samp' => 'samp',
'gta5' => 'gta5',
diff --git a/Resources/views/admin/test-result.blade.php b/Resources/views/admin/test-result.blade.php
index 9fb0f96..9ec9ef3 100644
--- a/Resources/views/admin/test-result.blade.php
+++ b/Resources/views/admin/test-result.blade.php
@@ -162,13 +162,19 @@
@foreach (array_slice($playersData, 0, 20) as $player)
+ @php
+ // Normalise keys — formatPlayersData stores Name/Score/Time (capital),
+ // but some drivers return name/score/time (lowercase)
+ $playerName = $player['Name'] ?? $player['name'] ?? null;
+ $playerScore = $player['Score'] ?? $player['score'] ?? null;
+ $playerTime = $player['Time'] ?? $player['time'] ?? null;
+ @endphp
- {{ $player['name'] ?? 'Unknown' }}
- @if (isset($player['score']))
- {{ $player['score'] }}
- @endif
- @if (isset($player['time']))
- {{ gmdate('H:i:s', $player['time'] ?? 0) }}
+
+ {{ $playerName ?? 'Unknown' }}@if ($playerScore !== null) ({{ $playerScore }})@endif
+
+ @if ($playerTime !== null)
+ {{ gmdate('H:i:s', (int) $playerTime) }}
@endif
@endforeach
@@ -483,17 +489,28 @@
.mtest__players--simple .mtest__player {
border-left: none;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
.mtest__player--simple {
padding: 0.5rem 1rem;
}
+.mtest__player-stats-simple {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ flex-shrink: 0;
+}
+
.mtest__player-score,
.mtest__player-time {
font-size: 0.8125rem;
color: var(--text-400);
font-family: var(--font-mono, monospace);
+ text-align: right;
}
.mtest__more {
diff --git a/Services/MapImageService.php b/Services/MapImageService.php
index e2dffae..573e8d6 100644
--- a/Services/MapImageService.php
+++ b/Services/MapImageService.php
@@ -45,7 +45,13 @@ public function getMapPreview(?ServerStatus $status): string
protected function getMapImagePath(ServerStatus $status): string
{
- $mod = preg_replace('/[^a-zA-Z0-9_\-]/', '', $status->server->mod);
+ $mod = $status->server->mod;
+
+ if ($mod === 'xash3d') {
+ $mod = in_array($status->game, ['cstrike', 'czero'], true) ? '10' : null;
+ }
+
+ $mod = preg_replace('/[^a-zA-Z0-9_\-]/', '', $mod ?? '');
$map = preg_replace('/[^a-zA-Z0-9_\-]/', '', $status->map ?? '-');
return 'assets/img/maps/' . $mod . '/' . $map . '.webp';
diff --git a/Services/MonitoringPingService.php b/Services/MonitoringPingService.php
index 39ade26..99ac730 100644
--- a/Services/MonitoringPingService.php
+++ b/Services/MonitoringPingService.php
@@ -19,7 +19,7 @@ class MonitoringPingService
private const PING_TIMEOUT = 2;
/** @var string[] Game mods that have no TCP listener — must use UDP probe */
- private const UDP_ONLY_MODS = ['samp', 'minecraft_bedrock'];
+ private const UDP_ONLY_MODS = ['samp', 'minecraft_bedrock', 'xash3d'];
/** @var string[] Game mods queried via HTTP */
private const HTTP_MODS = ['gta5', 'fivem', 'redm'];
@@ -182,6 +182,11 @@ private function getUdpProbePacket(string $ip, int $port, string $mod): string
return $header . 'i';
}
+ // Xash3D FWGS — "info 49" out-of-band query
+ if ($mod === 'xash3d') {
+ return "\xFF\xFF\xFF\xFFinfo 49";
+ }
+
// Minecraft Bedrock (RakNet Unconnected Ping)
$packet = chr(0x01);
$packet .= pack('J', (int) (microtime(true) * 1000));