From 6442e104ab23a21223f2c57fcd2b09bd64213b41 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Wed, 12 Aug 2026 12:11:42 +0200 Subject: [PATCH] docs: ticket 10 (raum-prefab-format) aufloesen, nachtraege an 08 und 14, prefab und etage ins glossar --- .../issues/08-event-trigger-system.md | 14 ++ .../issues/10-raum-prefab-format.md | 169 +++++++++++++++++- .../issues/11-ui-geruest-screen-modell.md | 4 +- .../14-kampfformeln-und-rundenablauf.md | 2 +- .scratch/rustkeep-1.0/map.md | 3 +- CONTEXT.md | 28 ++- 6 files changed, 214 insertions(+), 6 deletions(-) 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 4929615..9869367 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 10: zwölf Events + +`room_entered { prefab_id }` kommt dazu. `floor_entered` deckt nur die Etage ab — „die Tür fällt hinter +dir zu", ein Hinterhalt oder eine Quest „finde die Bibliothek" brauchen den einzelnen Raum. Prefabs +tragen Trigger wie jeder andere Registry-Inhalt, neue Maschinerie kostet es also nicht. + +Das Event trägt zugleich den Kampfbeginn: Ein `room_entered` in einen Raum mit lebenden Gegnern +verschiebt sie nach `Game.enemies`. + +**Beobachtung zur Liste selbst:** Sie wurde hier als „fest und geschlossen" festgelegt, und seither hat +*jedes* Ticket, das echte Spielsituationen beschreibt, einen Eintrag gebraucht — 14 zwei, 10 einen. Das +spricht nicht gegen die Entscheidung (namespaced Mod-Events wären ohne Scripting weiterhin sinnlos), +aber wer 15 oder 16 bearbeitet, sollte mit einem weiteren rechnen. + ### Nachtrag aus Ticket 14: elf Events statt neun `combat_started` und `combat_ended { outcome: Victory | Fled }` kommen dazu. Ohne sie ist „gewinne drei diff --git a/.scratch/rustkeep-1.0/issues/10-raum-prefab-format.md b/.scratch/rustkeep-1.0/issues/10-raum-prefab-format.md index fa2f8ce..a3e118b 100644 --- a/.scratch/rustkeep-1.0/issues/10-raum-prefab-format.md +++ b/.scratch/rustkeep-1.0/issues/10-raum-prefab-format.md @@ -1,7 +1,7 @@ # 10 — Raum-Prefab-Format Type: grilling -Status: open +Status: resolved Blocked by: 03 ## Question @@ -35,3 +35,170 @@ Prefabs sind eine **weitere Registry über dieselbe Ladepipeline**, kein eigener beantwortet die letzte Frage oben halb: Hinzufügen ist der Normalfall, Ersetzen geht über `overrides`. Offen bleibt, ob **Generator-Parameter** (Etagen-Tabellen, Gewichtungen, Pflichträume) ebenfalls Registry-Einträge sind — dann mit derselben Maschine moddbar — oder ein Sonderfall. + +## Answer + +Begriffe im [Glossar](../../../CONTEXT.md). + +### Etagenaufbau: freie Platzierung mit Korridoren + +Prefabs werden nacheinander an zufälliger Position gesetzt, Überlappung (plus ein Feld Rand) +verworfen, nach N Fehlversuchen aufgegeben — der klassische Rogue-Ansatz. + +Verbunden wird **Raum `i` mit Raum `i−1`** in Platzierungsreihenfolge, danach ein paar zufällige +Zusatzkorridore für Schleifen. Korridore als L-Form, erst horizontal, dann vertikal. + +Die Kette statt eines minimalen Spannbaums, weil sie **Erreichbarkeit strukturell garantiert**: Hängt +jeder Raum am vorigen, ist jeder Raum von jedem erreichbar — ohne Flutfüllung und ohne +Reparaturschleife. Das beantwortet die Garantiefrage aus dem Ticketkörper nebenbei mit. Prim wären +zwanzig Zeilen für dasselbe Ergebnis mit hübscheren Korridorlängen. + +**Rotation und Spiegelung sind erlaubt** — acht Varianten je Prefab, und das Drehen eines ASCII-Blocks +ist eine Transposition. Achtmal so viel Inhalt aus derselben Handarbeit. + +Kein Opt-out-Feld: 1.0 hat kein Prefab, dessen Ausrichtung etwas bedeutet. Taucht eines auf, ist +`rotate = false` eine Zeile — vorher wäre es ein Schalter, den niemand stellt. + +### Ein Prefab + +```toml +[[prefabs]] +id = "core:kreuzung" +tags = ["core:normal"] +layout = """ +####+#### +#.......# +#..e...e# ++.......+ +#..l....# +#.......# +####+#### +""" +``` + +| Zeichen | Bedeutung | | Zeichen | Bedeutung | +|---|---|---|---|---| +| `#` | Wand | | `l` | Beute-Platz | +| `.` | Boden | | `>` | Treppe | +| `+` | Tür | | `<` | Eingang | +| `e` | Gegner-Platz | | `n` | NPC-Platz | + +Kein eigener Händler-Marker: **Ein Händler ist ein NPC.** Ein zweites Zeichen für denselben Platz wäre +dieselbe Sorte Vermischung, die Ticket 02 beim `ItemType` abgeräumt hat. + +Marker stehen **im Block**, nicht als Koordinatenliste daneben. Der ganze Sinn von ASCII ist, dass ein +Modder den Raum *sieht*; Koordinaten daneben zu pflegen wäre die schlechtere Hälfte von beidem. + +**Türen sind explizit.** Ohne sie stanzt der Generator Löcher in eine von Hand gezeichnete Wand — wer +Prefabs baut, will bestimmen, wo man hineinkommt, sonst hätte man auch würfeln können. Korridore +dürfen nur an `+` andocken. + +**Ein `e` sagt „hier steht ein Gegner", nicht *welcher*.** Was einzieht, entscheidet die Etage. Sonst +wäre jedes Prefab an eine Schwierigkeitsstufe gebunden und derselbe hübsche Raum ließe sich nicht auf +Etage 1 *und* auf Etage 5 verwenden — genau die Wiederverwendung, für die man Prefabs baut. + +Bei `n` ist es **umgekehrt**: Den NPC nennt das Prefab selbst (`npcs = ["core:blacksmith"]`), nicht +die Etage. NPCs skalieren nicht mit der Tiefe — ein Schmied auf Etage 5 ist derselbe Schmied wie auf +Etage 1, und ein `core:merchant`-Raum, dessen Händler ausgewürfelt würde, wäre kein Händlerraum. Das +Feld selbst gehört dem NPC-Ticket; hier steht nur, auf welcher Seite die Entscheidung liegt. + +### Die Etage + +```toml +[[floors]] +id = "core:floor_3" +number = 3 +size = [80, 40] # Felder +rooms = 9 # Zielzahl inklusive Pflichtraeume +required = ["core:entrance", "core:stairs", "core:merchant"] # Tags +prefabs = [ { tag = "core:normal", weight = 10 }, { tag = "core:treasure", weight = 2 } ] +creatures = [ { id = "core:skeleton", weight = 5 }, { id = "core:ghoul", weight = 2 } ] +loot = "core:floor_3_table" +``` + +Ablauf: **Pflichträume zuerst** auf die leere Fläche, danach gewichtet auffüllen bis `rooms` erreicht +oder die Versuche erschöpft sind. + +Ausgewählt wird durchgehend über **Tags**, auch bei den Pflichträumen. IDs würden bedeuten, dass ein +Mod keinen eigenen Eingangsraum beisteuern kann, ohne die ganze Etagen-Tabelle zu überschreiben. + +Dass Pflichträume zuerst kommen, *ist* die Garantie — auf einer leeren Fläche passen sie praktisch +immer. Optionale Räume, die nicht mehr passen, entfallen einfach; das ist der Normalfall, kein Fehler. +Passt die Flächensumme der Pflicht-Tags **grundsätzlich** nicht in `size`, ist das ein Datenfehler und +beim Laden erkennbar: Warnzeile über die Maschinerie aus 04. Zur Laufzeit dann kein +Wiederholungszähler und kein Rückfallpfad — die Prüfung sitzt dort, wo der Fehler gemacht wurde. + +`core:merchant` steht unter `required` und nicht unter `prefabs`: Ticket 07 hat Backtracking +ausgeschlossen, der Händler muss also garantiert auftauchen statt gewichtet. + +Damit beantwortet sich auch die Frage aus der 03-Vorgabe: **Generator-Parameter sind Daten.** +`prefabs` und `floors` sind zwei gewöhnliche Registries — ein Mod ergänzt Etage 6 oder ersetzt Etage 3 +über `overrides`, ohne dass hier etwas erfunden wird. Die Gesamtzahl steigt damit auf **zehn**. + +### Kampf beginnt beim Betreten eines Raums + +Die Bestellung aus 14, wer `Game.enemies` füllt: **Gegner gehören zum Raum, nicht zur Etage.** Ein +`room_entered` in einen Raum mit lebenden Gegnern verschiebt sie nach `Game.enemies` und startet den +Kampf; nach dem Sieg gilt der Raum als geräumt und ein erneutes Betreten startet nichts mehr. + +Der Grund ist, was dadurch **nicht** gebaut werden muss: Frei umherlaufende Gegner bräuchten Pfadsuche, +Sichtlinien, Verfolgung und ein Bewegungsbudget pro Zug — ein eigenes System, das niemand bestellt hat +und das die Charter mit „Kampf im Menü" auch nicht nahelegt. Die `e`-Plätze aus dem Prefab sind +schlicht die Gegnerliste des Raums. + +### `Floor` — der Innenbau aus Ticket 07 + +```rust +struct Floor { + number: u8, + tiles: Vec, width: u16, height: u16, + rooms: Vec, + player_room: Option, +} + +struct Room { + prefab: PrefabId, // fuer room_entered und Trigger + rect: Rect, + enemies: Vec, + loot: Vec<(Pos, ItemInstance)>, + cleared: bool, +} + +enum Tile { Wall, Floor, Door, Stairs, Entrance } +``` + +Korridore gehören zu **keinem** Raum — sie sind schlicht `Tile::Floor` außerhalb aller `rect`. Dadurch +feuert `room_entered` sauber beim Übergang Korridor → Raum, ohne Sonderfall, und `player_room: None` +ist ein echter Zustand statt eines Fehlers. + +Gegner werden bei Kampfbeginn **verschoben, nicht kopiert** — sonst gäbe es zwei Wahrheiten über +denselben Gegner. + +Ladeprüfung über die 04-Maschinerie: **`layout`-Zeilen müssen gleich lang sein**, sonst ist der Block +kein Rechteck und die Platzierung rechnet mit Müll. Ein Zweizeiler, der eine ganze Klasse von +Modder-Frust abfängt. + +### Siegbedingung — bisher nirgends definiert + +Kein Ticket hatte je festgelegt, was beim Abstieg von der letzten Etage passiert. + +**`Descend` auf der letzten definierten Etage gewinnt den Lauf.** Es gibt keine Zahl „5" im Code: Die +Engine sucht `[[floors]]` mit `number = aktuell + 1` und findet sie keine, ist Schluss. Ein Mod, der +Etage 6 ergänzt, verlängert das Spiel ohne eine Zeile Engine-Code — dieselbe Bewegung wie bei +`core:attack` in Ticket 11: Was wie Engine-Logik aussieht, ist Inhalt. + +### Zwei Nachträge an geschlossene Tickets + +| Ticket | Nachtrag | +|---|---| +| **08** | `room_entered { prefab_id }` als **zwölftes** Event. `floor_entered` deckt nur die Etage ab; „die Tür fällt hinter dir zu", ein Hinterhalt oder eine Quest „finde die Bibliothek" brauchen den einzelnen Raum. Neue Maschinerie kostet es nicht — Prefabs sind gewöhnlicher Registry-Inhalt und dürfen Trigger tragen wie alles andere | +| **14** | `GameOver { outcome: Died \| Won }` statt nur `GameOver`. Der Bildschirm zeigt in beiden Fällen dasselbe — Etage, Zug, Gold, Verlauf —, nur die Überschrift unterscheidet sich. Zwei Screens für zwei Texte wären Verdopplung | + +### Auflagen an andere Tickets + +- **09 Save-Schema**: Layout oder Seed? Ticket 14 hat einen seedbaren Generator geliefert, Regenerieren + *wäre* also möglich — aber der Raumzustand (`cleared`, gefallene Gegner, genommene Beute) lässt sich + nicht regenerieren und muss ohnehin mit. 09 entscheidet, ob sich der Seed dann noch lohnt. +- **16 Loot-Tabellen**: `loot` an der Etage verweist auf eine Tabelle, die `l`-Plätze ziehen daraus. +- **Boss-Design**: Ein Boss ist derzeit ein Gegner in einem Prefab mit `core:boss`-Tag. Phasen fehlen + weiterhin. 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 d14065b..c38bf06 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,7 +120,9 @@ 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 +**Nachtrag aus Ticket 14: `GameOver { outcome: Died | Won }` als elfter Screen**, Vollbild. Das +`outcome` kam aus Ticket 10 dazu — der Abstieg von der letzten definierten Etage gewinnt den Lauf, und +Sieg wie Tod zeigen dieselben Zahlen unter einer anderen Überschrift. 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. 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 21fd5c9..043daac 100644 --- a/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md +++ b/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md @@ -213,7 +213,7 @@ Zeile lang. | **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 | +| **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. *Ticket 10 hat daraus `GameOver { outcome: Died \| Won }` gemacht, weil der Abstieg von der letzten Etage den Lauf gewinnt* | ### Auflagen an andere Tickets diff --git a/.scratch/rustkeep-1.0/map.md b/.scratch/rustkeep-1.0/map.md index e2bf906..8a28be9 100644 --- a/.scratch/rustkeep-1.0/map.md +++ b/.scratch/rustkeep-1.0/map.md @@ -67,6 +67,7 @@ auch über Ticket 13. - [06 — Research: ratatui-App-Architektur](issues/06-research-ratatui-architektur.md) ([#7](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/7)) — Screen-Stack über zentralem `Game`-State statt Elm oder Component-Trait; `game/` bleibt ratatui-frei; Popups per `Clear` auf Sub-`Rect` statt Widget-Stack; blockierendes `event::read()` ohne Tick; Dungeon-Grid ist Eigenbau, es gibt kein Widget dafür; ratatui 0.30.2 mit MSRV 1.88; `KeyEventKind::Press`-Filter unter Windows Pflicht. Voller Bericht in [research/06](research/06-ratatui-architektur.md). - [07 — Game-State-Baum](issues/07-game-state-baum.md) ([#8](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/8)) — `Registries` lebt **neben** `Game`, nicht darin: als Feld kollidierte `&mut game.player` mit `&game.registries` bei jedem Methodenaufruf. Verweise per `String`-ID, keine Indizes (die hängen an der Ladereihenfolge). Ein `Creature` für Held und Gegner, aber **zwei Felder** `player` und `enemies` statt einer `Vec` — Solo-Held heißt, „Gegner greift Gegner an" existiert nicht, und zwei Felder sind ein disjunkter Borrow. Ein `Floor`, kein Backtracking; Händler müssen deshalb auf jeder Etage vorkommen können. **Instanzzustand kommt**: `ItemInstance { def, affixes }`, und **Affixe sind die sechste Registry** — dadurch bleibt der Save eine ID-Liste und der Preis `base_price × Π price_factor`. Stapeln bei gleicher `def` und gleicher `affixes`. `enum CreatureRef`, dazu je ein Eingang `Game::apply` und `Game::attribute` — Letzteres auf `Game`, weil Auren beim Träger wohnen und ein `Creature` seine Attribute allein gar nicht ausrechnen kann. Kein Attribut-Cache. - [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. +- [10 — Raum-Prefab-Format](issues/10-raum-prefab-format.md) ([#11](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/11)) — Freie Platzierung mit L-Korridoren; verbunden wird **Raum `i` mit Raum `i−1`**, weil eine Kette Erreichbarkeit *strukturell* garantiert statt sie nachtraeglich zu pruefen. Prefab als ASCII-Block mit Markern im Block, **Tueren explizit** (sonst stanzt der Generator Loecher in gezeichnete Waende); Rotation und Spiegelung erlaubt, `rotate = false` als Opt-out. **Ein `e` sagt „hier steht ein Gegner", nicht welcher** — das entscheidet die Etage, sonst waere jedes Prefab an eine Schwierigkeitsstufe gebunden. `[[prefabs]]` und `[[floors]]` sind die neunte und zehnte Registry; Auswahl durchgehend ueber **Tags**, Pflichtraeume zuerst platziert (das *ist* die Garantie), Flaechenpruefung beim Laden. **Kampf beginnt bei `room_entered`**, Gegner gehoeren zum Raum — das erspart Pfadsuche, Sichtlinien und Verfolgung. `Floor` mit `rooms`, `player_room: Option` (Korridor ist ein echter Zustand). **Siegbedingung erstmals definiert**: kein `[[floors]]` mit der naechsten Nummer = gewonnen, die Zahl „5" steht nirgends im Code. Nachtraege: `room_entered` an 08 (zwoelf Events), `GameOver { outcome }` an 14. - [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. @@ -74,7 +75,7 @@ auch über Ticket 13. ## Not yet specified - **Balancing** — konkrete Zahlen für Attribute, Schaden, Preise und Erfahrung über fünf Etagen. Wartet auf Kampfformeln (14), Progression (15) und Loot (16); vorher gibt es nichts zu balancieren. -- **Boss-Design** — ob Bosse nur Gegner mit besseren Werten sind oder eigene Mechanik brauchen (Phasen, Skripte). Berührt Stufe (c) des Mod-Supports. +- **Boss-Design** — ein Boss ist derzeit ein Wesen mit `ai`-Liste (14) in einem Prefab mit `core:boss`-Tag (10). Was fehlt, sind **Phasen**: „ab 50 % Leben ein anderes Verhalten" wäre mit der `ai`-Bedingungsliste sogar ausdrückbar, „ab 50 % ändern sich die Werte" nicht. Berührt Stufe (c) des Mod-Supports. - **NPC- und Handelsmodell** — Warenbestand, Dialogdaten. Wartet auf das Event-System (08). Die Preisformel steht bereits in 07; weil 07 Backtracking ausgeschlossen hat, muss ein Händler auf **jeder** Etage erreichbar sein. - **Inventar-UX** — Gewicht oder Kapazität (`carry_capacity` ist seit 01 ein Attribut ohne Regel), Sortierung, Vergleichsansicht. 11 hat nur den Screen und die Commands festgelegt, nicht was darin steht. - **Mod-Verteilung** — durch 03 auf „Ordner ins Profil entpacken" geschrumpft, ohne Versionsprüfung. Offen ist nur noch, ob 1.0 mehr braucht. diff --git a/CONTEXT.md b/CONTEXT.md index 9451a78..9f09583 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -165,6 +165,28 @@ 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. +## Prefab + +Ein von Hand gezeichneter Raum als ASCII-Block, moddbarer namespaced Inhalt. Wände, Boden und Türen +stehen als Zeichen darin, dazu Plätze für Gegner, Beute, Treppe, Eingang und NPCs. + +Ein Gegner-Platz sagt „hier steht ein Gegner", **nicht welcher** — das entscheidet die [Etage](#etage). +Nur so lässt sich derselbe Raum auf Etage 1 und Etage 5 verwenden. Bei NPCs ist es umgekehrt: Die nennt +das Prefab selbst, denn ein Schmied ist auf jeder Etage derselbe Schmied. + +Türen sind ausdrücklich markiert, damit der Generator keine Löcher in eine gezeichnete Wand stanzt. +Prefabs dürfen gedreht und gespiegelt werden. + +## Etage + +Eine Ebene des Dungeons und zugleich ihr Bauplan: Fläche, Raumzahl, Pflichträume, gewichtete +[Prefab](#prefab)- und [Wesen](#wesen)-Auswahl, Beutetabelle. Ausgewählt wird über [Tags](#tag), nie +über IDs — sonst könnte ein Mod keinen eigenen Eingangsraum beisteuern. + +Es existiert immer nur **eine** Etage; beim Abstieg wird die vorige verworfen, es gibt kein +Zurückgehen. Gibt es keine Etage mit der nächsten Nummer, ist der Lauf **gewonnen** — die Zahl der +Etagen steht nirgends im Code. + ## Initiative Ein Zahlenwert am [Wesen](#wesen), einmal beim Betreten eines Kampfes aus `dexterity` plus Wurf @@ -176,8 +198,8 @@ 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 +Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **zehn**, und +das hier ist die kanonische Liste — sie ist über fünf Tickets gewachsen und stand sonst nirgends vollständig: | Registry | TOML-Tabelle | @@ -190,6 +212,8 @@ vollständig: | [Affixe](#affix) | `[[affixes]]` | | [Quests](#quest) | `[[quests]]` | | [Wesen](#wesen) | `[[creatures]]` | +| [Prefabs](#prefab) | `[[prefabs]]` | +| [Etagen](#etage) | `[[floors]]` | Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen.