- Add CSP, frame and referrer headers for served static files - Enforce WebSocket origin check to prevent cross-site hijacking - Trust X-Forwarded-For only when the peer is from a private proxy network - Limit concurrent connections (500 total / 20 per IP) and rooms (300) - Add ALLOWED_ORIGINS env var for additional WebSocket origins - Document reverse proxy setup (NPM/NPMplus) in README - Add scripts/security-test.mjs to verify origin and limit behavior
171 lines
7.6 KiB
Markdown
171 lines
7.6 KiB
Markdown
# 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
|
||
```
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
---
|
||
|
||
## Türme
|
||
|
||
| Turm | Kosten | Upgrade 2/3 | Besonderheit |
|
||
| --- | --- | --- | --- |
|
||
| 🏹 Bogenturm | 50 🪙 | 60 / 110 🪙 | Günstig, schnelle Einzelschüsse, trifft Luft |
|
||
| 🧨 Kanone | 110 🪙 | 100 / 180 🪙 | Flächenschaden, trifft **keine** Flieger |
|
||
| ❄️ Eisturm | 80 🪙 | 70 / 130 🪙 | Verlangsamt alle Gegner im Umkreis um bis zu 65 % |
|
||
| ⚡ Teslaturm | 130 🪙 | 120 / 200 🪙 | Kettenblitz springt auf bis zu 5 Gegner über |
|
||
| 💫 Laserturm | 160 🪙 | 150 / 260 🪙 | Soforttreffer, maximale Reichweite & Durchschlag |
|
||
|
||
- **Verkauf:** 70 % der Gesamtinvestition (Kauf + Upgrades) werden erstattet.
|
||
- **Zielmodi:** Erster / Letzter / Stärkster / Nächster.
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
- **1–5** – 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
|