From 20d4f1470aa31afb1ff724ea5dcd69bfcc1ddea0 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 31 May 2026 22:20:49 +0200 Subject: [PATCH] docs: spec for quests & quest-log (sub-project 3.2) Condition-driven multi-stage quests on top of the foundation: Quest/QuestStage data, QuestLog + QuestEngine (per-turn auto-advance with announcements), START_QUEST effect, QuestView display via a console 'quests' command and a GUI quest-box under the map. quests.yaml optional; World gains a 5-arg back-compat constructor. Win/endings deferred to 3.3. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../specs/2026-05-31-quests-design.md | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Semesterprojekt/docs/superpowers/specs/2026-05-31-quests-design.md diff --git a/Semesterprojekt/docs/superpowers/specs/2026-05-31-quests-design.md b/Semesterprojekt/docs/superpowers/specs/2026-05-31-quests-design.md new file mode 100644 index 0000000..a0a9378 --- /dev/null +++ b/Semesterprojekt/docs/superpowers/specs/2026-05-31-quests-design.md @@ -0,0 +1,163 @@ +# Spec: Quests & Quest-Log (Teilprojekt 3.2) + +Stand: 2026-05-31. Baut auf dem Quest-Fundament (3.1) auf: Flags, Conditions, +Effects sind vorhanden. Liefert echte Quest-Objekte, eine Fortschritts-Engine und +die Anzeige (Konsolen-Befehl + GUI-Quest-Box unter der Karte). + +## 1. Kontext & Ziel + +Das Fundament erlaubt gated progression über Flags. Jetzt: **strukturierte Quests** +mit mehreren Stufen, deren Fortschritt **automatisch aus dem Weltzustand** erkannt +wird (bestätigt: Ansatz A, condition-driven), plus sichtbares **Quest-Log**. + +## 2. Scope + +**In Scope:** +- Quest-Datenmodell (`Quest`, `QuestStage`) + `quests.yaml`. +- Runtime: `QuestLog` (Fortschritt), `QuestEngine` (Tick pro Zug, Auto-Advance, + Ansagen), `START_QUEST`-Effekt. +- Anzeige: `QuestView`-Modell, `GameIO.setQuests/showQuests`, Konsolen-Befehl + `quests`, GUI-Quest-Box **unter der Karte**. +- Loader: `QuestDto`/`QuestStageDto`, `QuestFactory`, `quests.yaml` (optional), + `World.quests`. +- Demo-Quest in der echten Welt. + +**Out of Scope (3.3 / später):** +- Win-Condition / mehrere Enden (3.3) – eine Quest kann aber per `onComplete`-Effekt + ein Flag setzen, das 3.3 nutzt. +- Quest-Verzweigung, Fehlschlag-Zustände, Voraussetzungen (per `startQuest`-Effekt + ausdrückbar). + +## 3. Datenmodell (model) + +```java +public record Quest(String id, String title, boolean autoStart, + List stages, List onComplete) { /* List.copyOf */ } + +public record QuestStage(String objective, List completion, + List onComplete) { /* List.copyOf */ } +``` +`quests.yaml`: +```yaml +- id: restore_power + title: "Bring the Manor to Life" + autoStart: true + stages: + - objective: "Get the power running." + completion: [{ flag: power_on }] + - objective: "Earn the Old Man's brass key." + completion: [{ hasItem: key }] + onComplete: + - { setFlag: manor_secured } +``` + +## 4. Runtime (game) + +### 4.1 QuestLog (in GameContext) + +`QuestLog` (selbst-initialisiertes Feld in `GameContext`, wie `GameState` → kein +Konstruktor-Eingriff). Hält nur Laufzeit-Fortschritt: +- `Map stageIndex` – aktuelle Stufe je *aktiver* Quest. +- `Set completed` – abgeschlossene Quest-IDs. +- API: `start(id)`, `isActive(id)`, `isCompleted(id)`, `stageIndex(id)`, + `advance(id)`, `complete(id)`. + +### 4.2 START_QUEST-Effekt + +`Effect.Type.START_QUEST` (arg = Quest-ID). `Effects.apply` ruft +`ctx.getQuestLog().start(arg)`. `EffectDto` bekommt Feld `startQuest`. Damit können +Dialoge/Schalter/Reaktionen Quests starten. + +### 4.3 QuestEngine.tick(ctx) + +Pro Zug aus der `Game`-Schleife (neben `setHud`/`setMap`): +1. Auto-Start: jede `autoStart`-Quest, die weder aktiv noch abgeschlossen ist, starten. +2. Für jede **aktive** Quest die `completion` der **aktuellen** Stufe via + `Conditions.all` prüfen. Erfüllt → + - `onComplete`-Effekte der Stufe anwenden, + - Ansage (`io.print`, z. B. „✓ Objective complete: …"), + - `advance`; war es die letzte Stufe → Quest-`onComplete` anwenden, `complete`, + Ansage „Quest complete: …". +3. **Kaskade**: Schleife wiederholen, solange sich etwas geändert hat (begrenzt durch + Gesamt-Stufenzahl), damit ein Zug mehrere Stufen auslösen kann. + +`tick` ist idempotent (abgeschlossene Stufen feuern nicht erneut). `QuestEngine.viewOf(ctx)` +baut die `QuestView` aus `World.getQuests()` + `QuestLog`. + +## 5. Anzeige + +### 5.1 Modell (io.text) + +```java +public record QuestEntry(String title, String objective) {} +public record QuestView(List active, List completed) {} +``` + +### 5.2 GameIO (additiv, wie die Karte) + +```java +default void setQuests(QuestView view) { /* no-op */ } // Push pro Zug (GUI) +default void showQuests(QuestView view) { print(QuestText.render(view)); } // on demand (Konsole) +``` +- **Konsole**: nutzt beide Defaults. `setQuests` = no-op; `showQuests` druckt via + `QuestText.render` (io.text), ausgelöst durch `QuestsCommand`. +- **GUI**: überschreibt `setQuests` (Quest-Box neu zeichnen) und `showQuests` (no-op, + Box ist immer sichtbar). +- `TestIO`: Defaults; `showQuests` druckt in `outputs` → testbar. + +### 5.3 Konsolen-Befehl + +`QuestsCommand` (`quests`, `log`, `journal`) → `io.showQuests(QuestEngine.viewOf(ctx))`. +Ausgabe: aktive Quests (Titel + aktuelles Ziel), darunter abgeschlossene (dim, mit ✓). + +### 5.4 GUI-Quest-Box + +Rechter Seitenbereich wird zu einem Panel: **Karte oben, Quest-Box unten** +(`BorderLayout`: Map-Scroll CENTER, Quest-Scroll SOUTH mit fester/anteiliger Höhe). +Die Quest-Box ist ein gestyltes, schreibgeschütztes Textfeld (`JTextPane`): aktive +Quests fett (Titel) + Ziel; abgeschlossene dim mit ✓. Font folgt der Auto-Skalierung +(`applyFonts`). `setQuests` aktualisiert sie pro Zug. + +## 6. Loading + +- `QuestDto(id, title, Boolean autoStart, List stages, List onComplete)`, + `QuestStageDto(objective, List completion, List onComplete)`. +- `QuestFactory.fromDto` baut `Quest` (Conditions/Effects via vorhandene + `toModelList`); keine Resolver-Phase nötig (Flags/Items lazy, Quest-IDs sind Strings). +- `WorldLoader` liest `quests.yaml` **optional** (fehlt die Datei → leere Liste), + baut `Map` und übergibt sie an `World`. +- `World` bekommt Feld `quests` + **rückwärtskompatiblen 5-Arg-Konstruktor** + (delegiert mit leerer Quest-Map), damit bestehende `new World(...)`-Aufrufe in Tests + unverändert kompilieren. + +## 7. Fehlerbehandlung + +| Fall | Verhalten | +|---|---| +| `quests.yaml` fehlt | leere Quest-Map, Spiel läuft ohne Quests | +| `START_QUEST` mit unbekannter Quest-ID | `QuestLog.start` legt Eintrag an; `viewOf` ignoriert IDs ohne Definition (Warnung geloggt) | +| Quest ohne Stufen | gilt sofort als abgeschlossen beim ersten Tick | +| Doppelte Quest-IDs | `WorldLoader.requireUniqueIds` erweitert um Quests | + +## 8. Testing + +- **QuestLog**: start/advance/complete, isActive/isCompleted. +- **QuestEngine**: Auto-Start; Stufe rückt vor, wenn Condition gilt; Ansage; Quest + schließt nach letzter Stufe ab; Kaskade (eine Stufe löst die nächste); `viewOf` + liefert aktive + abgeschlossene korrekt. +- **Effects**: `START_QUEST` startet Quest. +- **EffectDto/QuestDto**: `toModel`/`fromDto`-Mapping. +- **QuestsCommand**: über `TestIO` – Ausgabe enthält aktives Ziel. +- **WorldLoader**: lädt `quests.yaml`; fehlende Datei → leer. +- **GUI**: nicht unit-getestet; manueller Smoke (`exec:java@gui`). + +## 9. Demonstration + +`quests.yaml` mit `restore_power` (autoStart, 2 Stufen): Strom anschalten → +Schlüssel vom alten Mann holen. Sichtbar im Konsolen-`quests`-Befehl und in der +GUI-Box; Ansagen beim Erfüllen. + +## 10. Offene Detailfragen (in Implementierung) + +- Höhe/Anteil der GUI-Quest-Box (ca. 30 % der Seitenleiste, scrollbar). +- Stil der Ansagen (zunächst `HEADING`).