update
This commit is contained in:
152
README.md
152
README.md
@@ -22,146 +22,56 @@ Das Startmenü hat drei Punkte:
|
||||
|
||||
## Mehrspieler: wie es zusammenhängt
|
||||
|
||||
Die Partie läuft nicht mehr beim Host, sondern auf `pirate-dice.marha.de`:
|
||||
Die Partie läuft nicht beim Host, sondern auf `pirate-dice.marha.de`:
|
||||
|
||||
Spieler ──HTTPS──> Matchmaker ──startet──> Raum (godot --headless, UDP-Port)
|
||||
Spieler ──UDP────> Raum
|
||||
Spieler ──wss://443──> ein Serverprozess ──> Raum MQG4 (SubViewport + World3D)
|
||||
└──> Raum 4YPV (SubViewport + World3D)
|
||||
|
||||
* Der **Matchmaker** (`server/matchmaker.py`) vergibt Codes wie `K7QM` und
|
||||
startet je Raum einen eigenen headless Godot-Prozess auf einem Port aus
|
||||
8911–8940. `POST /api/raum` legt an, `GET /api/raum/<code>` schlägt nach.
|
||||
* Ein **Raum** ist Peer 1 und damit die Autorität, sitzt aber nicht mit am
|
||||
Tisch (`local_seat` bleibt −1, kein HUD). Ohne Spieler beendet er sich nach
|
||||
zwei Minuten von selbst, dann ist der Code wieder frei.
|
||||
* Der **Host** ist der erste Spieler im Raum. Geht er, rückt der nächste
|
||||
nach. Nur er darf starten (`Net.request_start`).
|
||||
* **Ein Prozess, ein Port.** Der Server trägt alle Räume gleichzeitig und
|
||||
spricht WebSocket. Traefik reicht das auf 443 durch, es muss kein Port
|
||||
freigegeben werden.
|
||||
* **Ein Raum** ist ein Knoten unter `/root/Main/Raeume/<CODE>`. Darin ein
|
||||
SubViewport mit `own_world_3d` – nur so hat jede Partie ihren eigenen
|
||||
Physikraum, sonst lägen alle Würfel im selben.
|
||||
* Die Knotennamen sind auf Server und Client gleich. Daran hängt das Ziel
|
||||
jedes RPC, deshalb dürfen sie nicht auseinanderlaufen.
|
||||
* Broadcasts (`rpc`) würden alle Räume treffen. Deshalb geht alles gezielt per
|
||||
`rpc_id` an die Peers des eigenen Raums.
|
||||
* Der Server sitzt nicht mit am Tisch: `local_seat` bleibt −1, kein HUD.
|
||||
* Der **Host** ist der erste Spieler im Raum. Geht er, rückt der nächste nach.
|
||||
Nur er stellt die Bots ein und startet.
|
||||
* Der letzte macht das Licht aus: verlässt der letzte Spieler den Raum, wird er
|
||||
samt Code sofort abgeräumt.
|
||||
|
||||
Gemessen je Raum: 51 MB Speicher zusätzlich, ~1 % eines Kerns im Leerlauf,
|
||||
kurze Spitzen beim Würfelwurf.
|
||||
Gemessen mit sechs gleichzeitigen Partien zu acht Plätzen: **3,7 MB je Raum**,
|
||||
im Leerlauf unter 1 % eines Kerns, kurze Spitzen beim Würfelwurf.
|
||||
|
||||
## Lokal entwickeln
|
||||
|
||||
make matchmaker # Matchmaker auf :8020, legt Räume als Prozesse an
|
||||
make dev # Spiel, zeigt auf den lokalen Matchmaker
|
||||
make server # Spielserver auf :8000, trägt alle Räume
|
||||
make dev # Spiel, zeigt auf den lokalen Server
|
||||
|
||||
Ohne `PIRATE_DICE_SERVER` zeigt das Spiel auf `pirate-dice.marha.de`. Einen
|
||||
einzelnen Raum ohne Matchmaker gibt es mit `make server` (Port 8910).
|
||||
Ohne `PIRATE_DICE_SERVER` zeigt das Spiel auf `wss://pirate-dice.marha.de`.
|
||||
|
||||
## Auf dem Server
|
||||
|
||||
make prod # kopiert das Godot-Binary, baut, startet
|
||||
git pull
|
||||
make prod # baut und startet, holt Godot selbst
|
||||
make logs
|
||||
make down
|
||||
|
||||
Die HTTP-Schnittstelle läuft hinter Traefik auf `pirate-dice.marha.de`. Die
|
||||
Raum-Ports 8911–8940/UDP gehen direkt nach aussen – Traefik kann kein UDP.
|
||||
Mehr ist nicht nötig. Der Docker-Build lädt Godot 4.7.2 herunter (Version steht
|
||||
im `Dockerfile`), importiert das Projekt einmal vor und startet den Server.
|
||||
`restart: unless-stopped` bringt ihn nach einem Neustart von selbst wieder hoch.
|
||||
|
||||
Das Spiel startet im Vollbild, **F11** schaltet um. **Esc** öffnet ein Menü mit
|
||||
*Fortsetzen* und *Verlassen*; im Einzelspieler hält es das Spiel samt Zeitlimit
|
||||
wirklich an, im Mehrspieler läuft die Runde weiter – sonst würde ein geöffnetes
|
||||
Menü alle anderen am Tisch blockieren.
|
||||
|
||||
Die Bildaufteilung: Tisch mittig, darauf die letzte Wette als so viele Würfel
|
||||
wie angesagt; aktive Booster oben links, Würfel aller Spieler oben rechts
|
||||
(Name mit den Würfeln darunter) – beide Panels nur so breit wie ihr Inhalt –,
|
||||
und die eigene Ansage als schmale Leiste unten in der Mitte.
|
||||
|
||||
Augenzahlen werden überall als gezeichnete Würfel dargestellt (`DieIcon` in
|
||||
`hud.gd`), unbekannte Werte als Würfel mit Fragezeichen. Beim Aufdecken färbt
|
||||
sich die Wette in der Tischmitte ein: grün die Würfel, die es wirklich gab,
|
||||
rot die fehlenden. Am Tisch trägt jeder Becher ein Schild mit Namen und der letzten Ansage dieses
|
||||
Spielers; Unterlage und Würfelbilder dafür werden beim Start einmal als Textur
|
||||
gezeichnet. Im Panel oben rechts
|
||||
stehen deshalb deine eigenen Würfel offen, gegnerische verdeckt – bis auf die
|
||||
per *Spähen* aufgedeckten und beim Aufdecken am Rundenende.
|
||||
|
||||
Jeder Platz hat eine runde Würfelzone mit umlaufender Bande. Die runde Form
|
||||
ist nicht nur Optik: in den Ecken der früheren rechteckigen Zone verkanteten
|
||||
sich Würfel, im Kreis liegt kein Wurf mehr schief (Messung: 2,0 % schiefe
|
||||
Würfel und 1,3 % Nachhelfer vorher, 0,4 % und 0 % nachher). Beim Aufdecken
|
||||
legen sich die Würfel als Bogen auf einen Kreis um die Zonenmitte, damit sie
|
||||
sich nicht überdecken und das Namensschild frei bleibt.
|
||||
|
||||
Der Tisch wächst mit der Spielerzahl, damit die Würfelzonen auch bei acht
|
||||
Plätzen nicht aneinanderstoßen. Die Kamera schaut mit 15° Neigung fast senkrecht
|
||||
von oben darauf; der eigene Platz liegt unten im Bild. `_frame_camera` in
|
||||
`table.gd` rechnet den Abstand so aus, dass der Tisch genau das freie Band
|
||||
zwischen den HUD-Feldern ausfüllt (`HUD_TOP`/`HUD_BOTTOM`) – bei jeder
|
||||
Spielerzahl und jedem Fensterformat, auch nach dem Ändern der Fenstergröße.
|
||||
|
||||
## Physik und Bildrate
|
||||
|
||||
Zwei Engine-Vorgaben sind bewusst gesetzt, beide in `project.godot`:
|
||||
|
||||
* **`3d/physics_engine="Jolt Physics"`** – GodotPhysics3D hat Kontakt-Toleranzen,
|
||||
die auf meterhohe Objekte ausgelegt sind (`contact_max_separation` 0.05 bei
|
||||
0.075 Würfel-Halbkante). Würfel dieser Größe sanken dadurch in die Tischplatte
|
||||
ein und rollten endlos weiter, sodass jeder Wurf in den Settle-Timeout lief.
|
||||
Mit Jolt liegt die Wurfphase bei rund 0,9 s statt 7 s.
|
||||
* **`renderer/rendering_method="mobile"`** – auf integrierten GPUs kostet
|
||||
Forward+ hier mehr als das Doppelte: 22 statt 47 fps bei 30 Würfeln.
|
||||
|
||||
Ein liegender Würfel wird mit `FREEZE_MODE_STATIC` eingefroren, nicht
|
||||
kinematisch. Ein kinematischer Körper meldet seine Lage weiter an den Knoten
|
||||
zurück: setzt ein Booster wie *Glücksgriff* die Augenzahl über
|
||||
`snap_to_value()`, überschreibt der Physikserver die neue Lage im nächsten
|
||||
Schritt wieder mit der alten, und der Würfel zeigt den falschen Wert.
|
||||
`Die.place()` zieht die Lage zusätzlich sofort im Physikserver nach.
|
||||
|
||||
Ein Würfel ist ein einziges Renderobjekt: Körper und Augen stecken in einem
|
||||
gemeinsamen Mesh mit Vertexfarben, statt 21 Augen als eigene Meshes zu hängen.
|
||||
|
||||
Weil Kamera und Deckenlicht beide fast senkrecht stehen, liegt der Glanzpunkt
|
||||
genau auf der Würfeloberseite. Mit glänzendem Material und starkem Spot klippte
|
||||
die weiße Fläche bei 255 und riss die schwarzen Augen mit hoch – gemessen lag
|
||||
kein einziger Punkt-Pixel unter Luminanz 150. Deshalb ist das Würfelmaterial
|
||||
matt (`roughness` 0.9, `metallic_specular` 0.0) und der Spot schwächer
|
||||
zugunsten von mehr Umgebungslicht; der Kontrast Auge zu Körper liegt damit bei
|
||||
rund 11:1.
|
||||
|
||||
## Regeln
|
||||
|
||||
Klassisches Liar's Dice ohne Joker. Jeder startet mit 5 Würfeln und sieht nur
|
||||
die eigenen.
|
||||
|
||||
1. **Würfeln** – alle Würfel fallen physikalisch in den eigenen Becher.
|
||||
2. **Boost wählen** – jeder bekommt 3 zufällige Booster angeboten und wählt
|
||||
einen; er wirkt nur für die laufende Runde.
|
||||
3. **Ansagen** – reihum wird ein Gebot "Anzahl x Augenzahl" über alle Würfel
|
||||
am Tisch gemacht. Jedes Gebot muss höher sein: mehr Würfel, oder gleich
|
||||
viele bei höherer Augenzahl.
|
||||
4. **Anfechten** – statt zu erhöhen kann man das Vorgebot anzweifeln. Alle
|
||||
Becher werden gehoben und gezählt: Stimmt das Gebot, verliert der
|
||||
Anfechter einen Würfel, sonst der Bieter.
|
||||
|
||||
Für Boosterwahl und Ansage hat man jeweils **30 Sekunden**; die Restzeit steht
|
||||
im Booster-Fenster beziehungsweise neben „Deine Ansage" und wird unter zehn
|
||||
Sekunden rot. Läuft die Zeit ab, entscheidet der Host automatisch – beim Booster
|
||||
zufällig, beim Gebot mit der Bot-Logik. Ohne dieses Limit würde ein abwesender
|
||||
oder getrennter Mitspieler den ganzen Tisch dauerhaft blockieren.
|
||||
|
||||
Wer keine Würfel mehr hat, scheidet aus. Der Letzte gewinnt.
|
||||
|
||||
## Booster
|
||||
|
||||
| Booster | Art | Wirkung |
|
||||
|---|---|---|
|
||||
| Neuwurf | Buff | Alle eigenen Würfel neu werfen |
|
||||
| Feinschliff | Buff | Einen gewählten eigenen Würfel neu werfen |
|
||||
| Glücksgriff | Buff | Ein gewählter Würfel wird zur 6 |
|
||||
| Zwilling | Buff | Ein anderer eigener Würfel nimmt den Wert des gewählten an |
|
||||
| Spähen | Buff | Von jedem Gegner wird ein zufälliger Würfel aufgedeckt |
|
||||
| Rütteln | Debuff | Jeder Gegner wirft einen zufälligen Würfel neu |
|
||||
| Absenken | Debuff | Bei jedem Gegner sinkt ein zufälliger Würfel um 1 |
|
||||
| Nebel | Debuff | Jeder Gegner sieht einen eigenen Würfel nicht mehr |
|
||||
| Störfeuer | Debuff | Gegner müssen diese Runde um mindestens 2 erhöhen |
|
||||
| Maulkorb | Debuff | Der nächste Spieler muss bieten und darf nicht anfechten |
|
||||
Es muss kein Port freigegeben werden: alles läuft als WebSocket über Traefik
|
||||
auf 443.
|
||||
|
||||
## Aufbau
|
||||
|
||||
| Datei | Inhalt |
|
||||
|---|---|
|
||||
| `scripts/net.gd` | Autoload: ENet-Host/Client, Lobby, Spielstart |
|
||||
| `scripts/net.gd` | Autoload: WebSocket-Verbindung, Räume, Lobby, Spielstart |
|
||||
| `scripts/boosters.gd` | Autoload: Booster-Katalog |
|
||||
| `scripts/game.gd` | Spiellogik (host-autoritativ), RPCs, Zustandsabgleich |
|
||||
| `scripts/table.gd` | Aufbau der 3D-Szene: Tisch, Würfelzonen, Becher, Kamera |
|
||||
|
||||
Reference in New Issue
Block a user