diff --git a/intern/statistik/index.php b/intern/statistik/index.php
index 170337c..3baf4b3 100644
--- a/intern/statistik/index.php
+++ b/intern/statistik/index.php
@@ -172,6 +172,61 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['promo_action'])) {
}
}
+$balanceError = '';
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['balance_action'])) {
+ if (!hash_equals((string) $_SESSION['csrf_token'], (string) ($_POST['csrf_token'] ?? ''))) {
+ $balanceError = 'Die Sitzung ist abgelaufen. Bitte lade die Seite neu und versuche es erneut.';
+ } elseif ((string) $_POST['balance_action'] !== 'save') {
+ $balanceError = 'Unbekannte Balance-Aktion.';
+ } else {
+ $httpKey = trim((string) ($config['nakama_http_key'] ?? ''));
+ $rpcBase = preg_replace('~/private_stats$~', '/set_game_balance', (string) ($config['nakama_rpc_url'] ?? ''));
+ $towerForm = $_POST['towers'] ?? [];
+ $enemyForm = $_POST['enemies'] ?? [];
+ $balance = ['schema' => 1, 'towers' => [], 'enemies' => []];
+ for ($i = 0; $i < 7; $i++) {
+ $row = is_array($towerForm[$i] ?? null) ? $towerForm[$i] : [];
+ $balance['towers'][] = ['cost' => $row['cost'] ?? '', 'damage' => $row['damage'] ?? '', 'range' => $row['range'] ?? '', 'cooldown' => $row['cooldown'] ?? '', 'targetClass' => $row['targetClass'] ?? '', 'bothScale' => $row['bothScale'] ?? ''];
+ }
+ for ($i = 0; $i < 9; $i++) {
+ $row = is_array($enemyForm[$i] ?? null) ? $enemyForm[$i] : [];
+ $balance['enemies'][] = ['hp' => $row['hp'] ?? '', 'speed' => $row['speed'] ?? '', 'reward' => $row['reward'] ?? '', 'breachDamage' => $row['breachDamage'] ?? '', 'shieldResistance' => $row['shieldResistance'] ?? ''];
+ }
+ if ($httpKey === '' || !is_string($rpcBase) || $rpcBase === (string) ($config['nakama_rpc_url'] ?? '')) {
+ $balanceError = 'Die Balance-Verwaltung ist noch nicht mit Nakama verbunden.';
+ } else {
+ $url = $rpcBase . '?http_key=' . rawurlencode($httpKey) . '&unwrap';
+ $response = fetchJson($url, 'POST', json_encode(['config' => $balance], JSON_THROW_ON_ERROR));
+ if (is_array($response) && isset($response['payload']) && is_string($response['payload'])) {
+ $decodedPayload = json_decode($response['payload'], true);
+ if (is_array($decodedPayload)) $response = $decodedPayload;
+ }
+ if (is_array($response) && !empty($response['accepted'])) {
+ $_SESSION['balance_flash'] = 'Balance gespeichert. Es wird beim nächsten Spielstart und in neuen Online-Partien verwendet.';
+ header('Location: ./#balance');
+ exit;
+ }
+ $balanceError = 'Der Server hat die Werte abgelehnt. Prüfe die Eingaben und ob der Runtime-Server aktualisiert wurde.';
+ }
+ }
+}
+$balanceFlash = (string) ($_SESSION['balance_flash'] ?? '');
+unset($_SESSION['balance_flash']);
+$balanceConfig = null;
+$balanceLoadError = '';
+$httpKey = trim((string) ($config['nakama_http_key'] ?? ''));
+if ($httpKey !== '') {
+ $balanceRpc = preg_replace('~/private_stats$~', '/get_game_balance', (string) ($config['nakama_rpc_url'] ?? ''));
+ if (is_string($balanceRpc) && $balanceRpc !== (string) ($config['nakama_rpc_url'] ?? '')) {
+ $balanceResponse = fetchJson($balanceRpc . '?http_key=' . rawurlencode($httpKey) . '&unwrap', 'POST', '{}');
+ if (is_array($balanceResponse) && isset($balanceResponse['payload']) && is_string($balanceResponse['payload'])) {
+ $decodedPayload = json_decode($balanceResponse['payload'], true);
+ if (is_array($decodedPayload)) $balanceResponse = $decodedPayload;
+ }
+ if (is_array($balanceResponse) && is_array($balanceResponse['config'] ?? null)) $balanceConfig = $balanceResponse['config'];
+ else $balanceLoadError = 'Die aktuelle Balance konnte nicht geladen werden. Servermodul möglicherweise noch nicht aktualisiert.';
+ }
+}
$releases = fetchJson((string) $config['gitea_releases_api']) ?? [];
$downloadTotal = 0;
$latestDownloads = 0;
@@ -271,12 +326,27 @@ if ($httpKey !== '') {
Void Bastion – Interne Statistik
= $downloadTotal ?>Downloads insgesamt= $latestDownloads ?>Downloads aktuelles Release= $players ?>Registrierte Piloten= $activeToday ?>Heute aktive Piloten= $platformDownloads['windows'] ?>Windows-Downloads= $platformDownloads['android'] ?>Android-Downloads
= e($latestRelease) ?>
Die Downloadwerte stammen direkt aus den Gitea-Release-Anhängen und werden alle fünf Minuten neu geladen.
Die private Nakama-Statistik ist noch nicht mit dem HTTP-Schlüssel verbunden.
Letzte Spieleraktualisierung: = e($updatedAt) ?>
Diese Seite ist nicht verlinkt, von Suchmaschinen ausgeschlossen und verlangt eine Anmeldung. Spielerzahlen sind zusammengefasst; technische Rundenberichte sind nur hier einsehbar.
Online-Diagnose
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.
= e($diagnosticError) ?>
Noch keine Verbindung zur privaten Online-Diagnose.
Noch keine Berichte aus der neuen Spielversion vorhanden.
| Runde | Seite | Zeitpunkt | Version | Messungen |
|---|
= e($rowRound) ?> | = (int) ($row['slot'] ?? -1) + 1 ?> | = e(date('d.m.Y H:i:s', intdiv((int) ($row['created_at'] ?? 0), 1000))) ?> | = e((string) ($row['version'] ?? '')) ?> | = (int) ($row['record_count'] ?? 0) ?> |
Runde = e($diagnosticRound) ?>
Für diese Kennung liegen noch keine Berichte vor.
Seite = (int) ($report['slot'] ?? -1) + 1 ?> · = e((string) ($report['version'] ?? '')) ?>
| UTC | Spielstände/s | Abstand p95 / max | Tick-Sprünge | Warteschlange verworfen | Bildaussetzer | Gegner / Türme |
|---|
| = e((string) ($record['utc'] ?? '')) ?> | = e((string) ($record['snapshots_per_second'] ?? 0)) ?> | = (int) ($record['gap_p95_ms'] ?? 0) ?> / = (int) ($record['gap_max_ms'] ?? 0) ?> ms | = (int) ($record['tick_jumps_over_1'] ?? 0) ?> | = (int) ($record['queue_dropped'] ?? 0) ?> | = (int) ($record['frame_stalls_over_200_ms'] ?? 0) ?> (max = (int) ($record['frame_max_ms'] ?? 0) ?> ms) | = (int) ($record['enemies'] ?? 0) ?> / = (int) ($record['towers'] ?? 0) ?> |
Einzelereignisse anzeigen
| UTC | Ereignis | Dauer | Server-Tick |
|---|
| = e((string) ($record['utc'] ?? '')) ?> | = e((string) ($record['event'] ?? '')) ?> | = (int) ($record['duration_ms'] ?? 0) ?> ms | = (int) ($record['last_server_tick'] ?? 0) ?> |
Zur Übersicht
+Einheiten-Balancing
Eine gemeinsame serverseitige Konfiguration wird für Offline-Spiele beim nächsten Start geladen und in neuen Online-Partien direkt vom Server verwendet. Bei fehlender Verbindung nutzt das Spiel den letzten lokal gespeicherten Stand. Laufende Partien bleiben unverändert.
+= e($balanceFlash) ?>
+= e($balanceError) ?>
= e($balanceLoadError) ?>
+
+
+
+Das Balancing-Formular wird angezeigt, sobald der Runtime-Server die Balance-Funktionen bereitstellt.
Freischaltcodes erstellen
Codes und Bemerkungen werden ausschließlich in der geschützten Serverablage verwaltet. Der Klartext ist nur in diesem angemeldeten Bereich sichtbar.
Code erstellt:= e((string) $promoFlash['code']) ?>= e((string) $promoFlash['reward']) ?> · offen · maximal = (int) $promoFlash['max_uses'] ?> Einlösungen · = e((string) $promoFlash['note']) ?>
= e($promoDeleteFlash) ?>