# 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`).