TRXTD/README.md
Tronax 69fbb015ab
feat(admin): live admin dashboard with solo presence tracking
Solo games run entirely in the browser, so the server previously had no
idea who was playing right now (it only saw logins and finished games).
This adds lightweight presence reporting and an admin dashboard.

Presence (server):
- POST /api/presence (logged-in users): heartbeat while a solo game runs,
  stores username, map (validated), difficulty, wave, since/lastSeen per
  user; entries expire automatically after 90s without a heartbeat
- POST /api/presence/stop: explicit removal when the player exits

Admin API (ADMIN_USERS env, comma-separated usernames):
- GET /api/admin/overview: live solo players, multiplayer room summaries
  (code, mode, map, players — the server already tracks rooms), and
  global stats (accounts, rounds, crystals in circulation)
- GET /api/admin/users?limit=100: user list with stats, newest login first
- publicUser now carries an admin flag; non-admins get 403

Admin UI (src/components/AdminDashboard.vue, served at /admin):
- Login gate for guests/non-admins (guest profiles are detected via
  isLoggedIn, not just user presence)
- KPI cards, live solo table (player, map, difficulty, wave, duration),
  room table, and account table; auto-refresh every 5 seconds
- App.vue renders the dashboard for /admin instead of the game and runs
  a screen watcher that starts/stops the solo presence heartbeat

Config: ADMIN_USERS documented in docker-compose.yml and README.

Tests: 6 new integration checks (admin flag, presence report/stop,
403 guard, overview contents, user list) — 37/37 green, build clean.
Verified end-to-end in the browser: guest gate, admin login, and a live
second player (map/difficulty/wave) appearing in the dashboard.
2026-08-17 15:07:02 +02:00

191 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TRXTD Tower Defense im Browser
Ein vollständiges Tower-Defense-Spiel, gebaut mit **Vue 3 + TypeScript + Vite**.
Die Spiellogik läuft in reinem TypeScript auf einem HTML-Canvas (60 FPS), Vue kümmert sich um HUD, Menüs und Panels.
Enthält **Solo-Kampagne**, **2-Spieler-Coop** und **1v1-Duell** (Online/Lokal per WebSocket).
## Schnellstart
1. Abhängigkeiten installieren:
```bash
npm install
```
2. Frontend (Vite) und Multiplayer-Server starten:
```bash
# Terminal 1: Dev-Server
npm run dev # → http://localhost:5173
# Terminal 2: Multiplayer-Relay (nur für Coop / 1v1 nötig)
npm run server # → ws://localhost:3001
```
3. Browser öffnen unter `http://localhost:5173`.
---
## 🐳 Docker
Das Spiel gibt es als **All-in-One-Container**: Frontend (statisch) und Multiplayer-Server teilen sich **einen Port** ideal für Self-Hosting.
```bash
# Image bauen (Multi-Stage-Build: Build → schlankes Runtime-Image)
docker build -t trxtd:latest .
# Container starten
docker run -d --name trxtd -p 3001:3001 trxtd:latest
# Oder mit Docker Compose (empfohlen)
docker compose up -d
```
Danach läuft das komplette Spiel inkl. Online-Multiplayer unter **http://localhost:3001**.
**Enthaltene Härtung:**
- Multi-Stage-Build (kein Build-Tooling im Runtime-Image)
- Läuft als unprivilegierter Nutzer (`node`)
- `HEALTHCHECK` über `/health`
- `/assets/` mit immutablen Cache-Headern, SPA-Fallback für alle anderen Routen
- WebSocket-Sicherheit: Payload-Limit (4 KB), Rate-Limiting (30 Aktionen/s), Schema-Validierung, Ping/Pong-Heartbeat
**Nützliche Umgebungsvariablen:**
| Variable | Default | Bedeutung |
| --- | --- | --- |
| `PORT` | `3001` | HTTP- und WebSocket-Port |
| `DIST_DIR` | `./dist` | Ordner mit den statischen Dateien |
| `ALLOWED_ORIGINS` | | Zusätzlich erlaubte WebSocket-Origins (Komma-Liste, z. B. `https://spiel.example.com`) |
**Öffentliches Hosting hinter Reverse Proxy (Nginx Proxy Manager / NPMplus):**
1. Container **nur im internen Netz** erreichbar machen (Port 3001 **nicht** am Router freigeben!), z. B.:
```yaml
# docker-compose.yml — gemeinsames Netzwerk mit dem Proxy
services:
trxtd:
build: .
restart: unless-stopped
expose: ["3001"] # intern, kein ports:-Mapping nach außen
# networks: proxy_net ...
```
2. In NPM/NPMplus einen **Proxy Host** anlegen:
- **Domain:** `spiel.example.com` (DNS-A-Record auf den Server)
- **Scheme:** `http` · **Forward Hostname:** Containername (gemeinsames Docker-Netz) oder Server-LAN-IP · **Port:** `3001`
- **Websockets Support: ✔ aktivieren** (zwingend erforderlich für Multiplayer!)
- Block Common Exploits: ✔ · Cache Assets: ✘ (der Server setzt eigene Cache-Header)
- **SSL-Tab:** Let's-Encrypt-Zertifikat anfordern, *Force SSL* + *HTTP/2* aktivieren
3. Fertig das Spiel (inkl. Coop/1v1 über `wss://`) läuft unter `https://spiel.example.com`.
**Serverseitige Härtung (aktiv):** CSP-/Frame-/Referrer-Header · Origin-Check gegen Cross-Site-WebSocket-Hijacking · `X-Forwarded-For`-Auswertung nur aus privaten Proxy-Netzen · max. 500 WebSocket-Verbindungen total / 20 pro IP · max. 300 Räume · Payload-Limit 4 KB · Rate-Limit 30 Aktionen/s · Schema-Validierung · Ping/Pong-Heartbeat · Raum-Timeout 30 min.
**Manuell ohne Docker im Produktionsmodus starten:**
```bash
npm run build
npm run server # serves dist/ + ws auf :3001
```
### 🛡️ Admin-Dashboard (`/admin`)
Live-Übersicht über den Server: wer gerade **Solo spielt** (Karte, Schwierigkeit, Welle — angemeldete Spieler senden dazu alle 20 s einen Heartbeat), offene **Multiplayer-Räume**, Account-**Statistiken** und die **Nutzerliste**. Aktualisiert sich alle 5 s automatisch.
Freischalten über die Umgebungsvariable `ADMIN_USERS` (Komma-getrennte Benutzernamen):
```yaml
environment:
- ADMIN_USERS=deinaccount
```
Dann als eines dieser Konten unter `https://deine-domain/admin` anmelden. Solo-Spiele laufen zwar lokal im Browser, aber angemeldete Spieler melden ihren Spielstatus an den Server; Gäste bleiben unsichtbar.
---
## Spielmodi
### 1. Solo-Kampagne
Verteidige deine Basis gegen 20 handgetunte Wellen (Leicht: 30 Leben, Normal: 20 Leben, Schwer: 12 Leben). Nach Welle 20 geht es auf Wunsch im **Endlos-Modus** weiter.
### 2. 🤝 2-Spieler-Coop
- **Gemeinsame Basis & gemeinsames Gold** Sprecht euch ab!
- **Eigene Türme:** Deine Türme haben einen **blauen Ring**, die deines Partners einen **orangenen Ring** (jeder kann nur seine eigenen Türme upgraden oder verkaufen).
- Gegner haben 70 % mehr Leben, um der doppelten Feuerkraft standzuhalten.
- Wellen starten automatisch nach Countdown oder per Klick auf „Nächste Welle" (beide können klicken).
### 3. ⚔ 1v1-Duell (Tower-Battles-Prinzip)
- Jeder Spieler verteidigt sein **eigenes Spielfeld**.
- Über ein **Live-Miniaturfenster (PiP)** siehst du in Echtzeit das Spielfeld deines Gegners mit allen Türmen und Gegnern.
- **Rush-Mechanik:** Schicke gegen Gold (`60 🪙 + 8 🪙/Welle`) eine Welle schneller Läufer auf die Basis des Gegners!
- **Siegbedingungen:**
- Basis des Gegners fällt auf 0 Leben → Sofortsieg!
- Nach 20 Wellen gewinnt, wer mehr Leben übrig hat (bei Gleichstand entscheiden die Punkte).
### Multiplayer-Raum-System
- Klicke auf dem Startbildschirm auf **„Coop-Raum erstellen"** oder **„1v1-Raum erstellen"**.
- Gib deinem Mitspieler den **4-stelligen Raum-Code** (z. B. `5SK7`).
- Der Mitspieler tippt seinen Namen + den Code ein und klickt auf **„Beitreten"**.
- Der Host startet das Spiel, sobald beide in der Lobby sind.
- Funktioniert im lokalen Netzwerk (LAN / WLAN) für Freunde über das Internet einfach Port 3001 freigeben oder über ngrok/Tailscale leiten.
---
## 🌟 9-Level-Evolutionssystem
Jeder Turm hat **9 Ausbaustufen**, aufgeteilt in **3 Evolutions-Stufen**:
| Tier | Level | Farbe | Bedeutung |
| --- | --- | --- | --- |
| **Basis** | 13 | 🟡 Gelb | Höherer Grundschaden, Reichweite & Feuerrate |
| **Evolution I** | 46 | 🟠 Orange | Neue Spezialfähigkeit schaltet sich frei |
| **Evolution II** | 79 | 🔵 Blau | Finale Evolutionsstufe mit verheerenden Effekten |
### Die Evolutionen im Detail
| Turm | 🟡 Basis (L13) | 🟠 Evolution I (L46) | 🔵 Evolution II (L79) |
| --- | --- | --- | --- |
| 🏹 **Bogenturm** | Schneller Einzelschuss | **Mehrfachschuss:** Feuert 23 Pfeile gleichzeitig auf verschiedene Ziele | **Giftpfeile + 4-Fach:** Bis zu 4 Pfeile + starker Gift-DoT (28 Schaden/s) |
| 🧨 **Kanone** | Flächenschaden | **Brandgeschosse:** Hinterlässt brennenden DoT an allen getroffenen Gegnern | **Flak-Geschütz:** Trifft ab L7 auch **Flugeinheiten** mit voller Explosionskraft! |
| ❄️ **Eisturm** | Verlangsamender Puls | **Permafrost:** Jeder 3. Puls **friert alle Gegner komplett ein** (Bewegung = 0) | **Zerbrechlichkeit:** Eingefrorene Ziele nehmen **+50 % Schaden aus ALLEN Quellen**! |
| ⚡ **Teslaturm** | Kettenblitz | **Betäubungs-Schock:** Kettenblitze betäuben getroffene Ziele kurzzeitig | **Gewittersturm:** Jeder 4. Schuss schlägt bei **ALLEN Gegnern in Reichweite** gleichzeitig ein! |
| 💫 **Laserturm** | Soforttreffer | **Prisma-Brechung:** Der Laserstrahl bricht und springt auf 23 Nebenziele über | **Durchschlag:** Der Strahl durchdringt ALLES in einer Linie bis zum Rand der Reichweite |
Türme ab Level 4 erhalten im Spiel eine pulsierende **Evolutions-Aura** (orange ab L4, blau ab L7) und die Sterne im Turm-Panel zeigen die jeweilige Tier-Farbe.
---
## Gegner
- **🟢 Kriecher:** Standard-Einheit, gut für frühes Gold
- **🟡 Läufer:** Sehr schnell Bogentürme oder Eisturm nötig
- **⬜ Panzer:** Zäh, kostet **2 Leben** bei Durchbruch
- **🟣 Flieger:** Ignorieren den Weg und fliegen auf halber Höhe geradeaus (nur Bogenturm, Eisturm, Tesla, Laser!)
- **🔴 Boss:** Riesige Lebensleiste, kostet **5 Leben** (Wellen 10, 15, 20 …)
---
## Hindernisse
Bäume (60 🪙) und Felsen (40 🪙) blockieren strategisch wichtige Bauplätze.
- **Klick auf das Hindernis** → „⛏ Entfernen" öffnet sich oben rechts.
- Nach dem Entfernen wird das Feld sofort bebaubar.
---
## Steuerung
- **15** Turm zum Bauen auswählen, Klick auf freies Feld platziert ihn
- **Klick auf Turm** Details, Upgrade, Verkauf, Zielmodus
- **Klick auf Hindernis** Entfernen gegen Gold
- **Leertaste** nächste Welle früh starten (Frühstart-Bonus!) / Pause (nur Solo)
- **U** Upgrade · **V** Verkaufen · **T** Zielmodus wechseln
- **P** Pause (nur Solo) · **M** Ton an/aus · **Esc** Abbrechen
- **Rechte Maustaste** Bauauswahl aufheben
---
## Technische Details
- **Client-Side Simulation:** Determinister Lockstep-Tick-Loop (30 Hz) beide Clients berechnen den exakten Spielverlauf parallel; der Server (`server/server.mjs`) vermittelt nur die Räume und leitet Aktionen weiter (minimaler Traffic, kein Lag bei der Bewegung).
- **Renderer:** Offscreen-gepufferter HTML-Canvas mit dynamischer Vollbild-Skalierung, Retina-DPR-Support, Partikelsystem und Mündungsfeuer.
- **Sound:** Web Audio API mit synthetisierten Klängen (keine externen Audio-Dateien).
- **Automatisierte Tests:**
- `npm run sim` Headless-Bot spielt die Solo-Kampagne zur Balancing-Prüfung durch
- `npx tsx scripts/test-mp.mts` Lockstep-Determinismus-Test (3600 Ticks, byte-identischer Zustand auf beiden Seiten)
- `npx tsx scripts/test-obstacle.mts` Feature-Test: Hindernis-Entfernung