Files
Jander_Semester2/Semesterprojekt/docs/superpowers/specs/2026-05-31-quests-design.md
Jean-Luc Makiola 20d4f1470a 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) <noreply@anthropic.com>
2026-05-31 22:20:49 +02:00

164 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<QuestStage> stages, List<Effect> onComplete) { /* List.copyOf */ }
public record QuestStage(String objective, List<Condition> completion,
List<Effect> 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<String, Integer> stageIndex` aktuelle Stufe je *aktiver* Quest.
- `Set<String> 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<QuestEntry> active, List<String> 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<QuestStageDto> stages, List<EffectDto> onComplete)`,
`QuestStageDto(objective, List<ConditionDto> completion, List<EffectDto> 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<String, Quest>` 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`).