Show real player activity and tower usage in private statistics
This commit is contained in:
1 parent
5cc948c72a
commit
5c7175d3dc
1 file changed
+47
-2
@@ -86,6 +86,20 @@ function e(string $value): string
|
|||||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function privateRpc(array $config, string $name, array $payload = []): ?array
|
||||||
|
{
|
||||||
|
$base = (string) ($config['nakama_rpc_url'] ?? '');
|
||||||
|
$key = trim((string) ($config['nakama_http_key'] ?? ''));
|
||||||
|
if ($key === '' || !preg_match('~/private_stats$~', $base)) return null;
|
||||||
|
$url = preg_replace('~/private_stats$~', '/' . $name, $base) . '?http_key=' . rawurlencode($key) . '&unwrap';
|
||||||
|
$response = fetchJson($url, 'POST', json_encode($payload, JSON_THROW_ON_ERROR));
|
||||||
|
if (is_array($response) && isset($response['payload']) && is_string($response['payload'])) {
|
||||||
|
$decoded = json_decode($response['payload'], true);
|
||||||
|
if (is_array($decoded)) return $decoded;
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($_SESSION['authenticated'])):
|
if (empty($_SESSION['authenticated'])):
|
||||||
?>
|
?>
|
||||||
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow,noarchive"><title>GerGames – Interne Statistik</title><style>
|
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow,noarchive"><title>GerGames – Interne Statistik</title><style>
|
||||||
@@ -266,6 +280,25 @@ $today = gmdate('Y-m-d');
|
|||||||
$activeToday = (int) (($playerStats['daily_active'] ?? [])[$today] ?? 0);
|
$activeToday = (int) (($playerStats['daily_active'] ?? [])[$today] ?? 0);
|
||||||
$updatedAt = isset($playerStats['updated_at']) ? date('d.m.Y H:i', intdiv((int) $playerStats['updated_at'], 1000)) : 'Noch keine Daten';
|
$updatedAt = isset($playerStats['updated_at']) ? date('d.m.Y H:i', intdiv((int) $playerStats['updated_at'], 1000)) : 'Noch keine Daten';
|
||||||
|
|
||||||
|
$directoryPage = filter_var($_GET['spieler_seite'] ?? 0, FILTER_VALIDATE_INT);
|
||||||
|
$directoryPage = $directoryPage === false ? 0 : max(0, min(10000, $directoryPage));
|
||||||
|
$playerDirectory = privateRpc($config, 'list_registered_players', ['page' => $directoryPage]);
|
||||||
|
if (is_array($playerDirectory) && isset($playerDirectory['players'], $playerDirectory['total'])) {
|
||||||
|
$players = (int) $playerDirectory['total'];
|
||||||
|
$activeToday = (int) ($playerDirectory['active_today'] ?? 0);
|
||||||
|
} else {
|
||||||
|
$playerDirectory = null;
|
||||||
|
}
|
||||||
|
$selectedPlayerId = strtolower(trim((string) ($_GET['spieler'] ?? '')));
|
||||||
|
if (!preg_match('/^[a-f0-9-]{36}$/D', $selectedPlayerId)) $selectedPlayerId = '';
|
||||||
|
$selectedPlayer = null;
|
||||||
|
if ($selectedPlayerId !== '') {
|
||||||
|
$playerResponse = privateRpc($config, 'get_registered_player', ['user_id' => $selectedPlayerId]);
|
||||||
|
if (is_array($playerResponse) && !empty($playerResponse['found']) && is_array($playerResponse['player'] ?? null)) {
|
||||||
|
$selectedPlayer = $playerResponse['player'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$promoCodes = null;
|
$promoCodes = null;
|
||||||
$promoListError = '';
|
$promoListError = '';
|
||||||
if ($httpKey !== '') {
|
if ($httpKey !== '') {
|
||||||
@@ -327,10 +360,22 @@ if ($httpKey !== '') {
|
|||||||
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow,noarchive"><meta http-equiv="refresh" content="300"><title>Void Bastion – Interne Statistik</title><style>
|
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow,noarchive"><meta http-equiv="refresh" content="300"><title>Void Bastion – Interne Statistik</title><style>
|
||||||
:root{color-scheme:dark;--bg:#06121e;--panel:#0a2030;--line:#26596a;--cyan:#5cecff;--muted:#92aebb;font-family:Inter,system-ui,sans-serif}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 85% 0,#12384b 0,transparent 35%),var(--bg);color:#edfaff;min-height:100vh}.shell{width:min(1120px,calc(100% - 32px));margin:auto;padding:48px 0}header{display:flex;justify-content:space-between;align-items:start;gap:24px;margin-bottom:34px}h1{margin:0;font-size:clamp(2rem,5vw,4rem);letter-spacing:-.04em}p{color:var(--muted)}a{color:var(--cyan)}.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.card{padding:24px;border:1px solid var(--line);background:linear-gradient(145deg,#0d2c3d,#071722)}.card strong{display:block;color:var(--cyan);font-size:clamp(2rem,4vw,3.5rem);line-height:1}.card span{display:block;margin-top:10px;color:#b9ced7}.panel{margin-top:18px;padding:24px;border:1px solid var(--line);background:#081b28}.warning{color:#ffbd7b}.meta{font-size:.9rem}.form-grid{display:grid;grid-template-columns:minmax(200px,1fr) 180px minmax(220px,1fr) 200px;gap:14px;align-items:end}.field label{display:block;margin-bottom:7px;color:#b9ced7}.field select,.field input,.panel button{width:100%;min-height:46px;padding:9px 12px;border:1px solid #316f80;background:#071722;color:#edfaff;font:inherit}.panel button{background:#14576a;color:#fff;font-weight:700;cursor:pointer}.result{padding:14px;border:1px solid #247c66;background:#0a3028;color:#a5ffdc}.result code{display:block;margin:8px 0;font-size:1.3rem;letter-spacing:.12em;overflow-wrap:anywhere;color:#fff}.error{color:#ffac99}.meta{font-size:.9rem}.table-wrap{overflow-x:auto}.codes{width:100%;border-collapse:collapse;margin-top:16px;min-width:780px}.codes th,.codes td{text-align:left;padding:11px;border-bottom:1px solid #214553;vertical-align:top}.codes th{color:#9fd7e7;font-size:.85rem}.codes code{font-size:.95rem;overflow-wrap:anywhere}.status{display:inline-block;padding:4px 9px;border:1px solid #326;white-space:nowrap}.status-open{color:#a5ffdc;border-color:#247c66;background:#0a3028}.status-partial{color:#ffd88b;border-color:#84672f;background:#332911}.status-redeemed{color:#b6c7d1;border-color:#43515a;background:#1b2830}.note-cell{min-width:180px;white-space:pre-wrap;overflow-wrap:anywhere}@media(max-width:800px){.grid{grid-template-columns:1fr 1fr}header{display:block}.form-grid{grid-template-columns:1fr 1fr}}@media(max-width:480px){.grid,.form-grid{grid-template-columns:1fr}}
|
:root{color-scheme:dark;--bg:#06121e;--panel:#0a2030;--line:#26596a;--cyan:#5cecff;--muted:#92aebb;font-family:Inter,system-ui,sans-serif}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 85% 0,#12384b 0,transparent 35%),var(--bg);color:#edfaff;min-height:100vh}.shell{width:min(1120px,calc(100% - 32px));margin:auto;padding:48px 0}header{display:flex;justify-content:space-between;align-items:start;gap:24px;margin-bottom:34px}h1{margin:0;font-size:clamp(2rem,5vw,4rem);letter-spacing:-.04em}p{color:var(--muted)}a{color:var(--cyan)}.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.card{padding:24px;border:1px solid var(--line);background:linear-gradient(145deg,#0d2c3d,#071722)}.card strong{display:block;color:var(--cyan);font-size:clamp(2rem,4vw,3.5rem);line-height:1}.card span{display:block;margin-top:10px;color:#b9ced7}.panel{margin-top:18px;padding:24px;border:1px solid var(--line);background:#081b28}.warning{color:#ffbd7b}.meta{font-size:.9rem}.form-grid{display:grid;grid-template-columns:minmax(200px,1fr) 180px minmax(220px,1fr) 200px;gap:14px;align-items:end}.field label{display:block;margin-bottom:7px;color:#b9ced7}.field select,.field input,.panel button{width:100%;min-height:46px;padding:9px 12px;border:1px solid #316f80;background:#071722;color:#edfaff;font:inherit}.panel button{background:#14576a;color:#fff;font-weight:700;cursor:pointer}.result{padding:14px;border:1px solid #247c66;background:#0a3028;color:#a5ffdc}.result code{display:block;margin:8px 0;font-size:1.3rem;letter-spacing:.12em;overflow-wrap:anywhere;color:#fff}.error{color:#ffac99}.meta{font-size:.9rem}.table-wrap{overflow-x:auto}.codes{width:100%;border-collapse:collapse;margin-top:16px;min-width:780px}.codes th,.codes td{text-align:left;padding:11px;border-bottom:1px solid #214553;vertical-align:top}.codes th{color:#9fd7e7;font-size:.85rem}.codes code{font-size:.95rem;overflow-wrap:anywhere}.status{display:inline-block;padding:4px 9px;border:1px solid #326;white-space:nowrap}.status-open{color:#a5ffdc;border-color:#247c66;background:#0a3028}.status-partial{color:#ffd88b;border-color:#84672f;background:#332911}.status-redeemed{color:#b6c7d1;border-color:#43515a;background:#1b2830}.note-cell{min-width:180px;white-space:pre-wrap;overflow-wrap:anywhere}@media(max-width:800px){.grid{grid-template-columns:1fr 1fr}header{display:block}.form-grid{grid-template-columns:1fr 1fr}}@media(max-width:480px){.grid,.form-grid{grid-template-columns:1fr}}
|
||||||
.delete-form{margin:0}.panel .delete-form button{width:auto;min-height:36px;padding:6px 10px;background:#602c38;border-color:#ad5963;font-size:.9rem;white-space:nowrap}
|
.delete-form{margin:0}.panel .delete-form button{width:auto;min-height:36px;padding:6px 10px;background:#602c38;border-color:#ad5963;font-size:.9rem;white-space:nowrap}
|
||||||
.balance-table{width:100%;border-collapse:collapse;min-width:900px;margin:14px 0}.balance-table th,.balance-table td{text-align:left;padding:8px;border-bottom:1px solid #214553;vertical-align:middle}.balance-table th{color:#9fd7e7;font-size:.85rem}.balance-table input,.balance-table select{width:100%;min-width:80px;min-height:38px;padding:6px;border:1px solid #316f80;background:#071722;color:#edfaff;font:inherit}.unit-mark{display:inline-grid;place-items:center;width:34px;height:34px;border:1px solid #316f80;background:#102b3a;color:#7de7ff;font-weight:800;margin-right:8px;vertical-align:middle}.unit-cell{min-width:180px;white-space:nowrap}.unit-preview{width:56px;height:42px;object-fit:contain;vertical-align:middle;margin-right:8px;border:1px solid #285364;background:linear-gradient(145deg,#102b3a,#071722);border-radius:4px}.unit-preview-flak{filter:hue-rotate(135deg) saturate(1.6)}.unit-preview-admin{display:inline-grid;place-items:center;color:#ffe49a;font-size:1.7rem}.unit-cell .unit-mark{display:none}.boss-info{margin:16px 0 8px;padding:14px 16px;border:1px solid #725d35;background:linear-gradient(110deg,#302713,#10212b);color:#ffdfa0}.boss-info strong{color:#ffe4a9}.balance-save{max-width:360px}
|
.balance-table{width:100%;border-collapse:collapse;min-width:900px;margin:14px 0}.balance-table th,.balance-table td{text-align:left;padding:8px;border-bottom:1px solid #214553;vertical-align:middle}.balance-table th{color:#9fd7e7;font-size:.85rem}.balance-table input,.balance-table select{width:100%;min-width:80px;min-height:38px;padding:6px;border:1px solid #316f80;background:#071722;color:#edfaff;font:inherit}.unit-mark{display:inline-grid;place-items:center;width:34px;height:34px;border:1px solid #316f80;background:#102b3a;color:#7de7ff;font-weight:800;margin-right:8px;vertical-align:middle}.unit-cell{min-width:180px;white-space:nowrap}.unit-preview{width:56px;height:42px;object-fit:contain;vertical-align:middle;margin-right:8px;border:1px solid #285364;background:linear-gradient(145deg,#102b3a,#071722);border-radius:4px}.unit-preview-flak{filter:hue-rotate(135deg) saturate(1.6)}.unit-preview-admin{display:inline-grid;place-items:center;color:#ffe49a;font-size:1.7rem}.unit-cell .unit-mark{display:none}.boss-info{margin:16px 0 8px;padding:14px 16px;border:1px solid #725d35;background:linear-gradient(110deg,#302713,#10212b);color:#ffdfa0}.boss-info strong{color:#ffe4a9}.balance-save{max-width:360px}.player-table{min-width:720px}.player-table td{vertical-align:middle}.player-name{font-weight:700}.player-meta{font-size:.85rem;color:var(--muted)}.player-pages{display:flex;justify-content:space-between;gap:12px;align-items:center;margin-top:14px}.player-pages a{padding:8px 14px;border:1px solid var(--line);text-decoration:none}.player-highlight{border-color:#c79545;background:linear-gradient(135deg,#2b2518,#102b34)}
|
||||||
</style></head><body><main class="shell"><header><div><p>GERGAMES · NICHT ÖFFENTLICH</p><h1>Void Bastion Statistik</h1></div><a href="?logout=1">Abmelden</a></header>
|
</style></head><body><main class="shell"><header><div><p>GERGAMES · NICHT ÖFFENTLICH</p><h1>Void Bastion Statistik</h1></div><a href="?logout=1">Abmelden</a></header>
|
||||||
<section class="grid"><article class="card"><strong><?= $downloadTotal ?></strong><span>Downloads insgesamt</span></article><article class="card"><strong><?= $latestDownloads ?></strong><span>Downloads aktuelles Release</span></article><article class="card"><strong><?= $players ?></strong><span>Registrierte Piloten</span></article><article class="card"><strong><?= $activeToday ?></strong><span>Heute aktive Piloten</span></article><article class="card"><strong><?= $platformDownloads['windows'] ?></strong><span>Windows-Downloads</span></article><article class="card"><strong><?= $platformDownloads['android'] ?></strong><span>Android-Downloads</span></article></section>
|
<section class="grid"><article class="card"><strong><?= $downloadTotal ?></strong><span>Downloads insgesamt</span></article><article class="card"><strong><?= $latestDownloads ?></strong><span>Downloads aktuelles Release</span></article><article class="card"><strong><?= $players ?></strong><span>Registrierte Piloten</span></article><article class="card"><strong><?= $activeToday ?></strong><span>Heute aktive Piloten</span></article><article class="card"><strong><?= $platformDownloads['windows'] ?></strong><span>Windows-Downloads</span></article><article class="card"><strong><?= $platformDownloads['android'] ?></strong><span>Android-Downloads</span></article></section>
|
||||||
<section class="panel"><h2><?= e($latestRelease) ?></h2><p>Die Downloadwerte stammen direkt aus den Gitea-Release-Anhängen und werden alle fünf Minuten neu geladen.</p><?php if ($playerStats === null): ?><p class="warning">Die private Nakama-Statistik ist noch nicht mit dem HTTP-Schlüssel verbunden.</p><?php else: ?><p>Letzte Spieleraktualisierung: <?= e($updatedAt) ?></p><?php endif; ?><p class="meta">Diese Seite ist nicht verlinkt, von Suchmaschinen ausgeschlossen und verlangt eine Anmeldung. Spielerzahlen sind zusammengefasst; technische Rundenberichte sind nur hier einsehbar.</p></section>
|
<section class="panel"><h2><?= e($latestRelease) ?></h2><p>Die Downloadwerte stammen direkt aus den Gitea-Release-Anhängen und werden alle fünf Minuten neu geladen.</p><?php if ($playerStats === null): ?><p class="warning">Die private Nakama-Statistik ist noch nicht mit dem HTTP-Schlüssel verbunden.</p><?php else: ?><p>Letzte Spieleraktualisierung: <?= e($updatedAt) ?></p><?php endif; ?><p class="meta">Diese Seite ist nicht verlinkt, von Suchmaschinen ausgeschlossen und verlangt eine Anmeldung. Spielernamen, Aktivität und technische Rundenberichte sind nur hier einsehbar.</p></section>
|
||||||
|
<section class="panel" id="spieler"><h2>Registrierte Spieler</h2><p>Spielerkonten und ihre letzte Aktivität. Automatisch erzeugte interne Testkonten werden ausgeblendet. „Zuletzt gespielt“ und Turmbauten werden ab der neuen Spielversion erfasst; frühere Runden lassen sich nicht rückwirkend zählen.</p>
|
||||||
|
<?php if ($playerDirectory === null): ?><p class="warning">Die Spielerliste konnte gerade nicht geladen werden.</p><?php else: ?>
|
||||||
|
<div class="table-wrap"><table class="codes player-table"><thead><tr><th>Spieler</th><th>Registriert</th><th>Zuletzt gespielt</th><th>Zuletzt online</th><th>Turmbauten</th></tr></thead><tbody>
|
||||||
|
<?php foreach ($playerDirectory['players'] as $player): $id = (string) ($player['user_id'] ?? ''); if (!preg_match('/^[a-f0-9-]{36}$/D', $id)) continue; ?><tr><td><a class="player-name" href="?spieler=<?= e($id) ?>&spieler_seite=<?= $directoryPage ?>#spieler-detail"><?= e((string) ($player['name'] ?? 'PILOT')) ?></a></td><td><?= (int) ($player['registered_at'] ?? 0) > 0 ? e(date('d.m.Y H:i', intdiv((int) $player['registered_at'], 1000))) : '–' ?></td><td><?= (int) ($player['last_played'] ?? 0) > 0 ? e(date('d.m.Y H:i', intdiv((int) $player['last_played'], 1000))) : 'Noch nicht erfasst' ?></td><td><?= (int) ($player['last_seen'] ?? 0) > 0 ? e(date('d.m.Y H:i', intdiv((int) $player['last_seen'], 1000))) : '–' ?></td><td><?= (int) ($player['tower_build_total'] ?? 0) ?></td></tr><?php endforeach; ?>
|
||||||
|
</tbody></table></div><div class="player-pages"><span><?= (int) $playerDirectory['total'] ?> Spieler · Seite <?= $directoryPage + 1 ?></span><span><?php if ($directoryPage > 0): ?><a href="?spieler_seite=<?= $directoryPage - 1 ?>#spieler">← Zurück</a><?php endif; ?> <?php if (($directoryPage + 1) * 50 < (int) $playerDirectory['total']): ?><a href="?spieler_seite=<?= $directoryPage + 1 ?>#spieler">Weiter →</a><?php endif; ?></span></div>
|
||||||
|
<?php endif; ?></section>
|
||||||
|
<?php if ($selectedPlayerId !== ''): ?><section class="panel" id="spieler-detail"><h2><?= $selectedPlayer !== null ? e((string) ($selectedPlayer['name'] ?? 'Spieler')) : 'Spieler nicht gefunden' ?></h2>
|
||||||
|
<?php if ($selectedPlayer !== null): $towerNames = ['Kinetik','Laser','Rakete','Plasma','EMP-Puls','Railgun','Flak']; $towerPreviews = ['kinetik.png','laser.png','rakete.png','plasma.png','emp-puls.png','railgun.png','flak.png']; $builds = is_array($selectedPlayer['tower_builds'] ?? null) ? $selectedPlayer['tower_builds'] : []; $towerRows = []; foreach ($towerNames as $index => $name) $towerRows[] = ['name' => $name, 'preview' => $towerPreviews[$index], 'count' => (int) ($builds[$index] ?? 0)]; usort($towerRows, static fn(array $a, array $b): int => $b['count'] <=> $a['count']); ?>
|
||||||
|
<p>Registriert: <?= e(date('d.m.Y H:i', intdiv((int) ($selectedPlayer['registered_at'] ?? 0), 1000))) ?> · Zuletzt gespielt: <?= (int) ($selectedPlayer['last_played'] ?? 0) > 0 ? e(date('d.m.Y H:i', intdiv((int) $selectedPlayer['last_played'], 1000))) : 'Noch nicht erfasst' ?> · Insgesamt <?= (int) ($selectedPlayer['tower_build_total'] ?? 0) ?> Türme gebaut</p>
|
||||||
|
<div class="table-wrap"><table class="codes"><thead><tr><th>Turm</th><th>Gebaut</th></tr></thead><tbody><?php foreach ($towerRows as $rank => $tower): ?><tr class="<?= $rank === 0 && $tower['count'] > 0 ? 'player-highlight' : '' ?>"><td><img class="unit-preview" src="../../assets/balancing/<?= e($tower['preview']) ?>" alt="" aria-hidden="true" loading="lazy"><?= e($tower['name']) ?><?= $rank === 0 && $tower['count'] > 0 ? ' · meistgebaut' : '' ?></td><td><?= $tower['count'] ?></td></tr><?php endforeach; ?></tbody></table></div>
|
||||||
|
<p class="player-meta">Gezählt werden bestätigte Bauten im Online-Koop sowie lokal gebaute Türme, sobald das Spiel sie übertragen konnte. KI- und Admin-Türme zählen nicht mit.</p>
|
||||||
|
<?php endif; ?><p><a href="?spieler_seite=<?= $directoryPage ?>#spieler">← Zur Spielerliste</a></p></section><?php endif; ?>
|
||||||
<section class="panel" id="diagnose"><h2>Online-Diagnose</h2><p>Die beiden PCs senden ihre technischen Messwerte am Ende einer Online-Partie automatisch mit derselben Rundenkennung. Berichte sind maximal 14 Tage relevant; Namen und Raumcodes werden hier nicht gespeichert.</p>
|
<section class="panel" id="diagnose"><h2>Online-Diagnose</h2><p>Die beiden PCs senden ihre technischen Messwerte am Ende einer Online-Partie automatisch mit derselben Rundenkennung. Berichte sind maximal 14 Tage relevant; Namen und Raumcodes werden hier nicht gespeichert.</p>
|
||||||
<?php if ($diagnosticError !== ''): ?><p class="warning"><?= e($diagnosticError) ?></p><?php elseif ($diagnosticRows === null): ?><p class="warning">Noch keine Verbindung zur privaten Online-Diagnose.</p><?php elseif (!$diagnosticRows): ?><p>Noch keine Berichte aus der neuen Spielversion vorhanden.</p><?php else: ?><div class="table-wrap"><table class="codes"><thead><tr><th>Runde</th><th>Seite</th><th>Zeitpunkt</th><th>Version</th><th>Messungen</th></tr></thead><tbody><?php foreach ($diagnosticRows as $row): ?><?php $rowRound = (string) ($row['round_id'] ?? ''); if (!preg_match('/^[a-f0-9]{16}$/D', $rowRound)) continue; ?><tr><td><a href="?round_id=<?= e($rowRound) ?>#diagnose"><code><?= e($rowRound) ?></code></a></td><td><?= (int) ($row['slot'] ?? -1) + 1 ?></td><td><?= e(date('d.m.Y H:i:s', intdiv((int) ($row['created_at'] ?? 0), 1000))) ?></td><td><?= e((string) ($row['version'] ?? '')) ?></td><td><?= (int) ($row['record_count'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div><?php endif; ?>
|
<?php if ($diagnosticError !== ''): ?><p class="warning"><?= e($diagnosticError) ?></p><?php elseif ($diagnosticRows === null): ?><p class="warning">Noch keine Verbindung zur privaten Online-Diagnose.</p><?php elseif (!$diagnosticRows): ?><p>Noch keine Berichte aus der neuen Spielversion vorhanden.</p><?php else: ?><div class="table-wrap"><table class="codes"><thead><tr><th>Runde</th><th>Seite</th><th>Zeitpunkt</th><th>Version</th><th>Messungen</th></tr></thead><tbody><?php foreach ($diagnosticRows as $row): ?><?php $rowRound = (string) ($row['round_id'] ?? ''); if (!preg_match('/^[a-f0-9]{16}$/D', $rowRound)) continue; ?><tr><td><a href="?round_id=<?= e($rowRound) ?>#diagnose"><code><?= e($rowRound) ?></code></a></td><td><?= (int) ($row['slot'] ?? -1) + 1 ?></td><td><?= e(date('d.m.Y H:i:s', intdiv((int) ($row['created_at'] ?? 0), 1000))) ?></td><td><?= e((string) ($row['version'] ?? '')) ?></td><td><?= (int) ($row['record_count'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div><?php endif; ?>
|
||||||
<?php if ($diagnosticRound !== ''): ?><h3>Runde <?= e($diagnosticRound) ?></h3><?php if (!$diagnosticReports): ?><p>Für diese Kennung liegen noch keine Berichte vor.</p><?php else: ?><?php foreach ($diagnosticReports as $report): ?><h4>Seite <?= (int) ($report['slot'] ?? -1) + 1 ?> · <?= e((string) ($report['version'] ?? '')) ?></h4><div class="table-wrap"><table class="codes"><thead><tr><th>UTC</th><th>Spielstände/s</th><th>Abstand p95 / max</th><th>Tick-Sprünge</th><th>Warteschlange verworfen</th><th>Bildaussetzer</th><th>Gegner / Türme</th></tr></thead><tbody><?php foreach (($report['records'] ?? []) as $record): if (($record['event'] ?? '') !== 'window') continue; ?><tr><td><?= e((string) ($record['utc'] ?? '')) ?></td><td><?= e((string) ($record['snapshots_per_second'] ?? 0)) ?></td><td><?= (int) ($record['gap_p95_ms'] ?? 0) ?> / <?= (int) ($record['gap_max_ms'] ?? 0) ?> ms</td><td><?= (int) ($record['tick_jumps_over_1'] ?? 0) ?></td><td><?= (int) ($record['queue_dropped'] ?? 0) ?></td><td><?= (int) ($record['frame_stalls_over_200_ms'] ?? 0) ?> (max <?= (int) ($record['frame_max_ms'] ?? 0) ?> ms)</td><td><?= (int) ($record['enemies'] ?? 0) ?> / <?= (int) ($record['towers'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div><details><summary>Einzelereignisse anzeigen</summary><div class="table-wrap"><table class="codes"><thead><tr><th>UTC</th><th>Ereignis</th><th>Dauer</th><th>Server-Tick</th></tr></thead><tbody><?php foreach (($report['records'] ?? []) as $record): if (($record['event'] ?? '') === 'window') continue; ?><tr><td><?= e((string) ($record['utc'] ?? '')) ?></td><td><?= e((string) ($record['event'] ?? '')) ?></td><td><?= (int) ($record['duration_ms'] ?? 0) ?> ms</td><td><?= (int) ($record['last_server_tick'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div></details><?php endforeach; ?><?php endif; ?><p><a href="./#diagnose">Zur Übersicht</a></p><?php endif; ?></section>
|
<?php if ($diagnosticRound !== ''): ?><h3>Runde <?= e($diagnosticRound) ?></h3><?php if (!$diagnosticReports): ?><p>Für diese Kennung liegen noch keine Berichte vor.</p><?php else: ?><?php foreach ($diagnosticReports as $report): ?><h4>Seite <?= (int) ($report['slot'] ?? -1) + 1 ?> · <?= e((string) ($report['version'] ?? '')) ?></h4><div class="table-wrap"><table class="codes"><thead><tr><th>UTC</th><th>Spielstände/s</th><th>Abstand p95 / max</th><th>Tick-Sprünge</th><th>Warteschlange verworfen</th><th>Bildaussetzer</th><th>Gegner / Türme</th></tr></thead><tbody><?php foreach (($report['records'] ?? []) as $record): if (($record['event'] ?? '') !== 'window') continue; ?><tr><td><?= e((string) ($record['utc'] ?? '')) ?></td><td><?= e((string) ($record['snapshots_per_second'] ?? 0)) ?></td><td><?= (int) ($record['gap_p95_ms'] ?? 0) ?> / <?= (int) ($record['gap_max_ms'] ?? 0) ?> ms</td><td><?= (int) ($record['tick_jumps_over_1'] ?? 0) ?></td><td><?= (int) ($record['queue_dropped'] ?? 0) ?></td><td><?= (int) ($record['frame_stalls_over_200_ms'] ?? 0) ?> (max <?= (int) ($record['frame_max_ms'] ?? 0) ?> ms)</td><td><?= (int) ($record['enemies'] ?? 0) ?> / <?= (int) ($record['towers'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div><details><summary>Einzelereignisse anzeigen</summary><div class="table-wrap"><table class="codes"><thead><tr><th>UTC</th><th>Ereignis</th><th>Dauer</th><th>Server-Tick</th></tr></thead><tbody><?php foreach (($report['records'] ?? []) as $record): if (($record['event'] ?? '') === 'window') continue; ?><tr><td><?= e((string) ($record['utc'] ?? '')) ?></td><td><?= e((string) ($record['event'] ?? '')) ?></td><td><?= (int) ($record['duration_ms'] ?? 0) ?> ms</td><td><?= (int) ($record['last_server_tick'] ?? 0) ?></td></tr><?php endforeach; ?></tbody></table></div></details><?php endforeach; ?><?php endif; ?><p><a href="./#diagnose">Zur Übersicht</a></p><?php endif; ?></section>
|
||||||
|
|||||||
Reference in new issue
Block a user