docs: ticket 14 (kampfformeln) aufloesen, nachtraege an 01 07 08 11, registry-liste und initiative ins glossar
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -162,6 +162,25 @@ nicht auswertbar — die Funktion wüsste nicht, gegen wen geschlagen wird. Der
|
||||
Dazu kommt `events: Vec<Event>` 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<AbilityId, u32>` — 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
|
||||
|
||||
@@ -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<Event>` und werden nach der laufenden Aktion in einer Schleife
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<AbilityId, u32>` 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<AbilityId, u32>` 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.
|
||||
|
||||
@@ -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<CreatureRef>` plus `events: Vec<Event>` 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<Screen>` 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<Screen>` 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
|
||||
|
||||
|
||||
34
CONTEXT.md
34
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
|
||||
|
||||
Reference in New Issue
Block a user