From d51ebb26a16abc8a0852dc15e598fd7bfa13c935 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Wed, 12 Aug 2026 11:51:25 +0200 Subject: [PATCH] docs: ticket 14 (kampfformeln) aufloesen, nachtraege an 01 07 08 11, registry-liste und initiative ins glossar --- .../issues/01-effekt-vokabular.md | 16 ++ .../rustkeep-1.0/issues/07-game-state-baum.md | 19 ++ .../issues/08-event-trigger-system.md | 14 ++ .../issues/11-ui-geruest-screen-modell.md | 5 + .../14-kampfformeln-und-rundenablauf.md | 182 +++++++++++++++++- .scratch/rustkeep-1.0/map.md | 1 + CONTEXT.md | 34 ++++ 7 files changed, 270 insertions(+), 1 deletion(-) diff --git a/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md b/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md index d119fb1..34cb213 100644 --- a/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md +++ b/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md @@ -116,6 +116,22 @@ color = "cyan" `data/` kennt nur `char` und ein Farbnamen-Enum, `ui/theme.rs` übersetzt in `ratatui::Color` — `game/` und `data/` bleiben damit weiterhin ratatui-frei. +### Nachtrag aus Ticket 14: `damage` skaliert, und `flee` ist die achte Aktion + +Die `damage`-Aktion oben kennt nur `amount = 12`, also einen festen Wert. Ein Waffenangriff soll aber +das `damage`-Attribut des Angreifers verwenden — das war gar nicht ausdrückbar. Die Menge ist deshalb +immer: + +``` +menge = amount + scale * damage-Attribut(Verursacher) # beide optional, Standard 0 +``` + +Eine Formel für alles: Basisangriff `scale = 1.0` · Feuerball `amount = 20` · Wuchtschlag +`scale = 1.5` · Mischformen beides. Keine zwei Modi, kein Sonderfall für `core:attack`. + +Dazu **`flee` als achte Aktion** — Ticket 11 hat Flucht zu einer Fähigkeit gemacht, und die braucht +eine Aktion, die sie ausführt. Der Katalog hat damit acht Einträge. + ### Bewusst nicht entschieden Das **Bedingungsvokabular** für `condition`. Es kommt aus Ticket 08, damit Quests und Modifikatoren diff --git a/.scratch/rustkeep-1.0/issues/07-game-state-baum.md b/.scratch/rustkeep-1.0/issues/07-game-state-baum.md index ecbf98b..c7495fd 100644 --- a/.scratch/rustkeep-1.0/issues/07-game-state-baum.md +++ b/.scratch/rustkeep-1.0/issues/07-game-state-baum.md @@ -162,6 +162,25 @@ nicht auswertbar — die Funktion wüsste nicht, gegen wen geschlagen wird. Der Dazu kommt `events: Vec` als Feld in `Game`: Ticket 08 arbeitet Events als Warteschlange ab, weil synchrones Feuern über `self.quests` iterieren müsste, während `self.apply` erneut läuft. +### Nachtrag aus Ticket 14 + +**`enemies` ist der laufende Kampf, nicht die Etage.** Das stand oben nie ausdrücklich da und wäre +sonst falsch geraten worden: Bei Grid-Erkundung mit Menü-Kampf griffe sonst der Skelettkrieger drei +Räume weiter jeden Zug mit an. Leer bedeutet kein Kampf; gefüllt wird die Liste von der Etage (10). + +Zwei neue Felder am `Creature`: + +- `initiative: i32` — einmal beim Betreten des Kampfes gewürfelt. Bewusst ein Zahlenwert am Wesen und + keine Reihenfolgeliste aus `CreatureRef`: Die wäre eine **zweite** Indexliste mit demselben + Ungültigkeitsproblem beim Tod eines Gegners. +- `cooldowns: HashMap` — Abklingzeiten in Zügen. + +Dazu der RNG-Seed als Feld in `Game`, damit Nachladen nicht neu würfelt. + +Und die Antwort auf die Frage, die 07 offen an 14 gereicht hatte: **Tote Gegner werden am Ende von +`Game::execute` entfernt**, nach dem Leerarbeiten der Event-Warteschlange. Innerhalb eines Zuges +bleiben Indizes stabil. + ### Auflagen an andere Tickets - **08 Event- und Trigger-System**: Form von `QuestProgress`; das Bedingungsvokabular, das 01 bereits diff --git a/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md b/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md index 29574df..4929615 100644 --- a/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md +++ b/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md @@ -102,6 +102,20 @@ Stufe (c) kann niemand ein eigenes Event *auslösen* — ein namespaced Event w Kommt Scripting (Ticket 13), wird `emit_event` eine gewöhnliche Aktion und die Liste bekommt eine `Custom(String)`-Variante. Ein Zusatz, kein Umbau. +### Nachtrag aus Ticket 14: elf Events statt neun + +`combat_started` und `combat_ended { outcome: Victory | Fled }` kommen dazu. Ohne sie ist „gewinne drei +Kämpfe" nicht formulierbar, und `combat_ended` **braucht** die Unterscheidung — sonst zählt Weglaufen +als Erfolg. + +Beide mit `source = Player`, ohne `target`. Platzierung im Zugablauf aus 14: `combat_started` bei +Schritt 1 neben `turn_started`, `combat_ended` bei Schritt 5 direkt nach dem `retain` — dann ist die +Gegnerliste bereits leer und ein Trigger sieht den Endzustand statt einer halb aufgeräumten +Zwischenstufe. + +Keine dritte Variante für den Tod des Helden: `creature_died` mit `source = Player` deckt ihn ab, und +danach gibt es ohnehin keinen Trigger mehr, der etwas davon hätte. + ### Warteschlange statt Rekursion Events landen in `Game.events: Vec` und werden nach der laufenden Aktion in einer Schleife diff --git a/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md b/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md index 8eec699..d14065b 100644 --- a/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md +++ b/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md @@ -120,6 +120,11 @@ Trank. `MainMenu` (trägt die Ladewarnungs-Zeile aus 04) · `CharacterCreation` (Registry-Schleife aus 02) · `Explore` · `Combat` · `Inventory` · `CharacterSheet` · `QuestLog` · `Shop` · `Dialog` · `Confirm` +**Nachtrag aus Ticket 14: `GameOver` als elfter Screen**, Vollbild. Die lazy Variante — zurück ins +`MainMenu` mit einer Meldungszeile — wurde bewusst verworfen: Ein Abstiegs-Roguelike lebt davon, dass +der Lauf am Ende *erzählt* wird (Etage, Zug, Gold, Todesursache). Das ist der Moment, für den man fünf +Etagen gespielt hat, und kein anderer Screen kann ihn übernehmen. + Kein `ItemDetail`-Popup: Die Inventarliste zeigt Details in einer Seitenspalte, das ist ein Layout und kein Screen. `Shop` bleibt getrennt von `Dialog`, weil Handel zwei Listen nebeneinander braucht und der Dialog eine Textfläche mit Antwortoptionen. diff --git a/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md b/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md index df26eb0..21fd5c9 100644 --- a/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md +++ b/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md @@ -1,7 +1,7 @@ # 14 — Kampfformeln und Rundenablauf Type: grilling -Status: open +Status: resolved Blocked by: 01 ## Question @@ -41,3 +41,183 @@ Eine Frage fällt zusätzlich an, die 07 nur benennen konnte: **Wann werden tote nicht — eine Aktion, die mitten im Zug einen Gegner tötet, verschiebt die Adressen aller nachfolgenden. Der Rundenablauf muss festlegen, ob am Zugende, am Rundenende oder gar nicht kompaktiert wird (Alternative: Tote bleiben mit einem Flag stehen). + +## Answer + +Begriffe im [Glossar](../../../CONTEXT.md). **Zahlen gehören nicht hierher** — die Map hält Balancing +getrennt; hier stehen Formeln und Ablauf. + +### Rundenablauf + +**Ein Zug = Spielerhandlung plus alle Gegnerreaktionen.** Nicht jede Handlung ein eigener Zug: Bei drei +Gegnern tickte ein Gift-Debuff sonst viermal pro Runde, und ein Tastendruck erzeugte vier Züge — das +bräche die Zusage aus 11, die Ticket 12 gerade gemessen hat. + +Reihenfolge über **Initiative**, einmal beim Betreten des Kampfes aus `dexterity` plus Wurf gewürfelt +und als `i32` **am Wesen** gehalten. Ausdrücklich *nicht* als Reihenfolgeliste aus `CreatureRef`: Die +wäre eine zweite Indexliste, die beim Tod eines Gegners genauso ungültig wird wie die erste. Ein +Zahlenwert am Wesen verschwindet mit ihm, die Sortierung stimmt weiter. Gleichstand geht an den +Spieler — deterministisch und zu seinen Gunsten. + +`dexterity` speist damit Initiative *und* Ausweichen. Absicht, aber 15 sollte es beim Balancing wissen. + +``` +Game::execute(cmd, reg): + 1. turn_started (+ combat_started, falls der Kampf gerade beginnt) in die Warteschlange + 2. befristete Modifikatoren ticken, Ausdauer regeneriert, Abklingzeiten sinken + 3. Teilnehmer handeln in Initiativreihenfolge, Spieler-Command an seinem Platz + -> Abbruch, sobald der Held tot ist + 4. nach JEDER einzelnen Handlung die Warteschlange leerarbeiten (bis leer oder 100) + 5. retain auf tote Gegner, danach ggf. combat_ended + 6. turn += 1 +``` + +Schritt 4 nach jeder Handlung statt einmal am Zugende: Sonst schlägt ein Gegner zu, den ein +Dornenpanzer eine Handlung zuvor getötet hat. Schritt 5 erst **nach** dem Leeren: Sonst zeigt ein +Trigger auf `creature_died` auf einen bereits entfernten Gegner. Innerhalb eines Zuges müssen Indizes +stabil bleiben — über Zuggrenzen hinweg hält ohnehin niemand ein `CreatureRef`, die UI liest neu. + +Genau **eine** Handlung pro Zug, kein Ausdauer-Budget. `max_stamina` ist die Ressource für +Fähigkeiten — das beantwortet die Frage „wozu sonst" aus dem Ticketkörper. + +Stirbt der Held mitten im Zug, bricht der Zug ab (laufende Handlung samt Warteschlange noch zu Ende). +Zwei weitere Gegner, die auf eine Leiche einschlagen, erzeugen nur Kampflog und Trigger auf ein Wesen, +das die Runde nicht mehr erlebt. + +### Formeln + +``` +p_treffer = clamp(hit_chance(A, gegen=B) - dexterity(B) * AUSWEICH, 0.05, 0.95) + +roh = attribute(damage, A, gegen=B) # 01-Stapelung, bedingte Modifikatoren inklusive +krit = rand() < clamp(critical_chance(A), 0.0, 1.0) +roh = roh * critical_factor(A) falls krit +r = clamp(resistance(B, typ), -1.0, 0.8) +final = max(1, round(roh * (1.0 - r))) + +p_flucht = clamp(0.5 + (dexterity(Held) - dexterity(schnellster Gegner)) * FLUCHT, 0.1, 0.9) +``` + +**Die Frage aus dem Ticketkörper löst sich auf.** Ob `critical_factor` den Rohschaden oder den +geminderten multipliziert, ist mathematisch belanglos: `roh × kf × (1−r)` ist dasselbe wie +`roh × (1−r) × kf`. Festzulegen war nur, dass beide multiplikativ *bleiben*. + +Drei Klemmbereiche, jeder mit Grund: + +- **Resistenz bei `0.8`** — bei `1.0` wäre ein Wesen immun, und Immunität ist keine Zahl, sondern ein + Spielabbruch. Nach unten bis `-1.0`, damit Verwundbarkeit ausdrückbar bleibt („Untote nehmen + doppelten heiligen Schaden"). +- **`max(1, …)`** — ein Treffer macht nie null, sonst gibt es Kämpfe, die rechnerisch nie enden. +- **`0.05 / 0.95` beim Treffen** — sonst wird ein hochgezüchteter Verteidiger unangreifbar und ein + hochgezüchteter Angreifer unfehlbar. Beides Sackgassen, die man erst auf Etage 5 bemerkt. + +Keine Schadensstreuung für 1.0: Der Krit liefert die Varianz bereits, Streuung wäre ein zweiter +Zufallsknopf ohne eigene Entscheidung dahinter. + +### Zufall + +Eigener xorshift64* — acht Zeilen statt einer Dependency. Der Seed liegt als Feld in `Game` und damit +**im Speicherstand**. + +Das ist keine Implementierungsfrage, sondern eine Spielentscheidung: **Nachladen würfelt nicht neu.** +Derselbe Angriff führt nach dem Laden zum selben Ergebnis, Save-Scumming läuft ins Leere. Ein +Abstiegs-Roguelike lebt von Entscheidungen mit Konsequenzen. + +### Fähigkeiten — die Bestellung aus Ticket 02 + +```toml +[[abilities]] +id = "core:power_strike" +name = "Wuchtschlag" +symbol = "!" +color = "red" +cost = 15 # Ausdauer +cooldown = 3 # Zuege +condition = [ … ] # optional, Grammatik aus 08 +actions = [ { action = "damage", scale = 1.5, damage_type = "blunt", target = "target" } ] +``` + +**Nur Ausdauer, kein zweiter Ressourcenpool.** Mana wäre ein zweites Attribut, eine zweite +Regeneration, eine zweite Anzeige und eine zweite Sperrregel. Die Decke: Ein Mod, der Mana will, +bräuchte ein Engine-Attribut — ein Nachtrag an 01, kein Problem, das ein Modder selbst lösen kann. + +**Abklingzeit in Zügen**, als `cooldowns: HashMap` am `Creature`. Sie als befristete +Modifikatoren zu tarnen wäre clever und genau die Sorte Cleverness, die man um drei Uhr nachts +entziffert. + +**Gesperrt heißt**: Ausdauer < `cost`, oder `cooldown > 0`, oder `condition` unerfüllt. Auslösen ergibt +dann `Outcome::Rejected("Nicht genug Ausdauer")` aus Ticket 12 und **kostet keinen Zug**. Die Regel +steht im Spielkern, nicht in der UI — sonst kennt ein Script sie nicht. + +**Ausdauer regeneriert pro Zug** um einen festen Betrag. Das gibt `Wait` aus Ticket 11 seinen Zweck und +macht Ausdauer zu einer Entscheidung statt zu einem Countdown. + +Eine misslungene Flucht ist übrigens `TurnPassed`, keine Zurückweisung: Der Versuch hat stattgefunden, +die Gegner haben gehandelt. `Rejected` bleibt dem Fall vorbehalten, dass gar nichts passiert ist. + +### Gegner — die achte Registry + +Beim Aufschreiben der KI fiel eine Lücke auf, die seit 02 offen lag: **Wesen stehen in keiner +Registry.** 02 hatte fünf, 07 ergänzte Affixe, 08 Quests — woher Gegnerdefinitionen kommen, hat nie +jemand gesagt, obwohl 10 sie in Räume setzen und 16 Beute an sie hängen muss. + +```toml +[[creatures]] +id = "core:skeleton" +name = "Skelett" +symbol = "s" +color = "white" +tags = ["core:undead"] +base = { max_health = 30, damage = 6, hit_chance = 0.7, dexterity = 3 } +abilities = ["core:attack", "core:bone_throw"] +ai = [ + { condition = [ { check = "attribute", who = "self", attribute = "health", below = 0.3 } ], + ability = "core:flee" }, + { ability = "core:attack" }, # ohne condition = greift immer +] +``` + +`ai` ist eine **geordnete Liste**, der erste passende *und verfügbare* Eintrag gewinnt — dieselbe +Bedingungsgrammatik wie überall sonst, kein neues Vokabular. Passt nichts, **setzt der Gegner aus**; +einer, der die Runde blockiert, wäre schlimmer als einer, der nichts tut. + +Eine `ai`-Liste **ohne bedingungslosen letzten Eintrag** gibt eine Ladewarnung über die Maschinerie aus +04. Das ist fast immer ein Versehen, und zur Laufzeit erkennt man es nur daran, dass ein Gegner +gelegentlich stillsteht — die teuerste Sorte Fehler. + +Boss-Phasen sind damit *nicht* gelöst, aber vorbereitet: Die Liste trägt sie, was fehlt, ist der +Phasenbegriff. + +### Balance als Daten — ausdrückliche Ausnahme zu Ticket 03 + +Fünf Stellknöpfe entstehen hier: `AUSWEICH`, `FLUCHT`, Ausdauer-Regeneration, Resistenz-Deckel und +Treffer-Klemmbereich. Sie liegen als `[balance]`-Tabelle in TOML und verschmelzen **feldweise** über +die Ladereihenfolge. + +Das widerspricht der Ganz-oder-gar-nicht-Regel aus 03, und zwar bewusst: Ein Mod, der nur `AUSWEICH` +dreht, müsste sonst alle fünf Werte abschreiben, und ein sechster Knopf in der nächsten Engine-Version +bräche ihn stillschweigend. + +Die Ausnahme bleibt eng, weil ihre Begründung eng ist: **`[balance]` hat ein festes, der Engine +bekanntes Schema.** Genau das fehlte bei Inhalt mit offenen Feldern — Modifikator- und Aktionslisten +bräuchten einen Merge-Algorithmus, und das war 03s Argument gegen feldweises Patchen. Hier greift es +nicht. + +Standardwerte liegen in der Engine, nicht in `core`. Dann ist ein Mod, der einen Knopf dreht, eine +Zeile lang. + +### Vier Nachträge an geschlossene Tickets + +| Ticket | Nachtrag | +|---|---| +| **01** | `damage`-Aktion: `menge = amount + scale * damage-Attribut(Verursacher)`, beide Felder optional (Standard 0). Deckt Basisangriff (`scale = 1.0`), Feuerball (`amount = 20`) und Wuchtschlag (`scale = 1.5`) mit *einer* Formel. Dazu `flee` als **achte Aktion** | +| **07** | `initiative: i32` und `cooldowns: HashMap` am `Creature`; RNG-Seed in `Game`; **`enemies` ist der laufende Kampf**, nicht die Etage | +| **08** | `combat_started` und `combat_ended { outcome: Victory \| Fled }` — die Eventliste wächst auf **elf**. Der Tod des Helden braucht keine dritte Variante, `creature_died` mit `source = Player` deckt ihn ab | +| **11** | **`GameOver`** als elfter Screen, Vollbild. Bewusst nicht die lazy Variante mit einer Meldungszeile im Hauptmenü: Ein Abstiegs-Roguelike lebt davon, dass der Lauf am Ende erzählt wird — Etage, Zug, Gold, Todesursache | + +### Auflagen an andere Tickets + +- **10 Raum-Prefab-Format**: füllt `Game.enemies`; Kampfbeginn ist ein Prefab-Ereignis. +- **15 Progression**: was bei Sieg passiert (EXP), und die Zahlen hinter allen Formeln inklusive der + fünf `[balance]`-Knöpfe. +- **16 Loot-Tabellen**: Beute hängt an der `creatures`-Registry, die hier entstanden ist. diff --git a/.scratch/rustkeep-1.0/map.md b/.scratch/rustkeep-1.0/map.md index 63dc146..e2bf906 100644 --- a/.scratch/rustkeep-1.0/map.md +++ b/.scratch/rustkeep-1.0/map.md @@ -69,6 +69,7 @@ auch über Ticket 13. - [08 — Event- und Trigger-System](issues/08-event-trigger-system.md) ([#9](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/9)) — **Der Trigger ist die vierte Wirkungsform** neben Modifikator, Aktion und Fähigkeit: `{ on, condition, actions }` darf überall stehen, wo `modifiers` und `actions` stehen — sonst entstünde dieselbe Maschine ein zweites Mal in 14 als `on_hit`. Eine Bedingungsgrammatik für Trigger *und* Modifikatoren: getaggtes Enum, Liste ist UND, `not` je Eintrag, **kein ODER** (zwei Einträge oder ein gemeinsames Tag). Neun feste Events ohne Namensraum, jedes mit `source`/`target`; `attack_hit` und `damage_taken` bleiben getrennt, weil der eine beim Angreifer und der andere beim Getroffenen hängt. Im Trigger ist `self` immer der Träger. **Warteschlange statt synchron** — synchrones Feuern wäre ein Borrow-Fehler (über `self.quests` iterieren, während `self.apply` läuft) — mit Zähler bei 100 Events pro Zug statt Zyklengraph. Trigger werden gesucht, nicht indiziert. **Quest = Trigger mit `count`**, siebte Registry, erledigte bleiben markiert stehen. Zwei Nachträge an geschlossene Tickets: `start_quest` als siebte Aktion in 01, und `against: Option` plus `events: Vec` in 07. - [11 — UI-Gerüst und Screen-Modell](issues/11-ui-geruest-screen-modell.md) ([#12](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/12)) — **„Aktion" hiess zwei Dinge**: `Action` bleibt das TOML-Effektvokabular aus 01, `Command` wird das UI-Kommando, Eingang `Game::execute(cmd, reg)` — die eine Stelle, an der ein Zug endet (Modifikatoren ticken, Event-Warteschlange leerarbeiten). Zwölf Commands, **alle kosten einen Zug** (ein `costs_turn`-Flag waere eine Ausnahmeliste); Angriff und Flucht sind keine Varianten, sondern `UseAbility("core:attack")` und `UseAbility("core:flee")` — damit moddbar. Ein gemeinsamer `Vec` fuer Screens und Popups; Screens **geben `Response` zurueck statt selbst zu schieben**, sonst haelt ein Screen ein `&mut` auf die `Vec`, in der er liegt. Ein `Response::Command` schliesst alle Overlays — dasselbe Flag, das schon fuers Rendern existiert. Zehn Screens, `ListState` in der Variante (der Stack erhaelt den Cursor gratis, solange der Screen liegt). Nur `KeyEventKind::Press`: bei `Repeat` bestimmte die Tastaturwiederholrate die Spielzeit; gegen zaehes Laufen hilft ein Renn-Kommando in `game/`. Tastenbelegung fest, Pfeile und `hjkl`. ratatui-Freiheit von `game/` prueft ein `#[test]` statt eines Workspace. Nachtrag an 01: `symbol` traegt das Zeichen und einen Farbnamen selbst — `ui/theme.rs` ist Code und damit fuer Modder unerreichbar. - [12 — Prototyp: ratatui-Durchstich](issues/12-prototyp-ratatui-durchstich.md) ([#13](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/13)) — **Das Modell aus 11 traegt.** Primaerquelle: Branch `prototyp/12-ratatui-durchstich`, `cargo run --example durchstich`. Bestaetigt: `mod game` bleibt ratatui-frei ohne Disziplin, Grid als eigenes `impl Widget` ueber `buf[(x,y)].set_symbol(…)`, `Vec` mit Overlay-Flag fuer Rendern *und* Auto-Schliessen, `Response` statt Stack-Mutation (kein Borrow-Aerger). **Der Windows-Fallstrick ist jetzt eine Zahl**: `Tasten 14 → Press 7 → Zuege 7` — ohne Filter kostete jeder Schritt zwei Zuege und damit doppelte Spielzeit. Zwei Korrekturen: 11 sagte „alle Commands kosten einen Zug", richtig ist **alle ausgefuehrten** (`Outcome { TurnPassed, Rejected(&str) }`, Grund reist mit zurueck); und `Command` muss `Clone` sein, damit `Confirm` es herausgeben kann. Ungetestet blieb `Registries` neben `Game` — der Durchstich laedt keine. +- [14 — Kampfformeln und Rundenablauf](issues/14-kampfformeln-und-rundenablauf.md) ([#15](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/15)) — **Ein Zug = Spielerhandlung plus alle Gegnerreaktionen**, sonst tickte ein Debuff bei drei Gegnern viermal. Initiative als `i32` **am Wesen** statt als Reihenfolgeliste (die waere eine zweite Indexsammlung mit demselben Ungueltigkeitsproblem); tote Gegner erst am Ende von `execute`, nach dem Leeren der Warteschlange. Trefferformel und Schadenskette mit drei Klemmbereichen — `0.8` bei Resistenz, weil `1.0` Immunitaet und damit ein Spielabbruch waere. **Die Krit-Reihenfolge-Frage loest sich auf**: multiplikativ ist kommutativ. Eigener xorshift, **Seed im Save** — Nachladen wuerfelt nicht neu. Faehigkeiten kosten Ausdauer, Abklingzeit in Zuegen, gesperrt ⇒ `Rejected` ohne Zug. **Achte Registry: Wesen** — die Luecke lag seit 02 offen; `ai` als geordnete Bedingungsliste. `[balance]` verschmilzt **feldweise**, ausdrueckliche Ausnahme zu 03 (festes Schema). Vier Nachtraege: `scale` und `flee` an 01, drei Felder an 07, zwei Events an 08, `GameOver` an 11. ## Not yet specified diff --git a/CONTEXT.md b/CONTEXT.md index 423bdbb..9451a78 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -11,6 +11,9 @@ Held oder Gegner — im Code derselbe Typ. Träger von [Attributen](#attribut), Ein Wesen ist dabei nicht in sich abgeschlossen: Eine Aura wohnt beim Träger und wird erst beim Auslesen des Ziels eingesammelt. +Gegner werden in der [Registry](#registry) `[[creatures]]` definiert — mit Grundwerten, Tags, +Fähigkeiten und einer geordneten `ai`-Liste, deren erster passender und verfügbarer Eintrag gewinnt. + ## Attribut Eine benannte, veränderbare Größe an einem [Wesen](#wesen). Der Katalog ist fest und wird von der Engine @@ -162,6 +165,37 @@ keine Zeit. Es gibt keine getrennte Kampfrunden-Uhr: Befristete [Modifikatoren](#modifikator) ticken auch beim Erkunden, damit „ich warte den Debuff draußen ab" eine Entscheidung mit Kosten bleibt. +## Initiative + +Ein Zahlenwert am [Wesen](#wesen), einmal beim Betreten eines Kampfes aus `dexterity` plus Wurf +gewürfelt. Er bestimmt die Reihenfolge innerhalb eines [Zuges](#zug); bei Gleichstand handelt der Held +zuerst. + +Initiative ist bewusst eine Zahl *am Wesen* und keine Reihenfolgeliste — eine Liste wäre eine zweite +Sammlung von Verweisen, die beim Tod eines Gegners ungültig würde. + +## Registry + +Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **acht**, und +das hier ist die kanonische Liste — sie ist über vier Tickets gewachsen und stand sonst nirgends +vollständig: + +| Registry | TOML-Tabelle | +|---|---| +| [Gegenstände](#gegenstand) | `[[items]]` | +| [Slots](#slot) | `[[slots]]` | +| [Charakteroption-Kategorien](#charakteroption) | `[[categories]]` | +| [Charakteroptionen](#charakteroption) | `[[options]]` | +| [Fähigkeiten](#fähigkeit) | `[[abilities]]` | +| [Affixe](#affix) | `[[affixes]]` | +| [Quests](#quest) | `[[quests]]` | +| [Wesen](#wesen) | `[[creatures]]` | + +Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen. + +Die Einstellungstabelle `[balance]` ist **keine** Registry: ein einzelner Block mit festem Schema, der +feldweise verschmilzt, statt als ganzer Eintrag ersetzt zu werden. + ## Mod Ein Ordner mit einer `manifest.toml` und beliebig vielen weiteren TOML-Dateien. Das Manifest gibt dem