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>
6.7 KiB
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-Befehlquests, 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):
- Auto-Start: jede
autoStart-Quest, die weder aktiv noch abgeschlossen ist, starten. - Für jede aktive Quest die
completionder aktuellen Stufe viaConditions.allprüfen. Erfüllt →onComplete-Effekte der Stufe anwenden,- Ansage (
io.print, z. B. „✓ Objective complete: …"), advance; war es die letzte Stufe → Quest-onCompleteanwenden,complete, Ansage „Quest complete: …".
- 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;showQuestsdruckt viaQuestText.render(io.text), ausgelöst durchQuestsCommand. - GUI: überschreibt
setQuests(Quest-Box neu zeichnen) undshowQuests(no-op, Box ist immer sichtbar). TestIO: Defaults;showQuestsdruckt inoutputs→ 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.fromDtobautQuest(Conditions/Effects via vorhandenetoModelList); keine Resolver-Phase nötig (Flags/Items lazy, Quest-IDs sind Strings).WorldLoaderliestquests.yamloptional (fehlt die Datei → leere Liste), bautMap<String, Quest>und übergibt sie anWorld.Worldbekommt Feldquests+ rückwärtskompatiblen 5-Arg-Konstruktor (delegiert mit leerer Quest-Map), damit bestehendenew 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);
viewOfliefert aktive + abgeschlossene korrekt. - Effects:
START_QUESTstartet 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).