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