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

6.7 KiB
Raw Blame History

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)

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:

- 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)

public record QuestEntry(String title, String objective) {}
public record QuestView(List<QuestEntry> active, List<String> completed) {}

5.2 GameIO (additiv, wie die Karte)

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