feat(quests): Quests als Trigger mit Zaehler

Eine Quest IST ein Trigger mit count = N — kein zweiter Mechanismus.
Gezaehlt wird erst ab start_quest, erledigte bleiben mit done = true
stehen, und die Belohnung darf die naechste starten: das ist die Kette.

- fuenfzehnte Registry, Trigger per serde(flatten) eingebettet
- give_item als Aktion, damit eine Belohnung mehr als Erfahrung zahlt
- QuestLog auf q, Fortschritt im Speicherstand, unbekannte IDs verworfen
- eine Funktion entscheidet fuer alle Traeger, wann ein Trigger feuert

Befund: Die Ordnungszahlen im Aktionskatalog widersprechen sich — vier
Tickets zaehlten je vom eigenen Stand. Sie sind raus, CONTEXT.md fuehrt
die Liste.
This commit is contained in:
2026-08-13 07:43:46 +02:00
parent 090d9ae136
commit 37b5820c5b
12 changed files with 376 additions and 22 deletions

View File

@@ -191,6 +191,7 @@ fn draw(frame: &mut Frame, app: &App) {
}
Screen::Dialog { pick } => draw_dialog(frame, area, app, *pick),
Screen::Shop { pick, selling } => draw_shop(frame, area, app, *pick, *selling),
Screen::QuestLog => draw_quests(frame, area, app),
Screen::Confirm { text, .. } => draw_confirm(frame, area, text),
Screen::Debug { pick } => draw_debug(frame, area, app, *pick),
Screen::GameOver => draw_game_over(frame, area, app),
@@ -398,6 +399,42 @@ fn draw_shop(frame: &mut Frame, area: Rect, app: &App, pick: usize, selling: boo
);
}
/// Laufende oben, erledigte darunter — erledigte bleiben stehen, weil sie es
/// auch im Spielzustand tun (Ticket 08).
fn draw_quests(frame: &mut Frame, area: Rect, app: &App) {
let mut zeilen = Vec::new();
for q in &app.game.quests {
let Some(def) = app.registries.quests.get(&q.quest) else { continue };
let (marke, farbe) = match q.done {
true => ("✓", Color::DarkGray),
false => ("•", Color::Reset),
};
zeilen.push(
Line::from(format!("{marke} {:<28} {}/{}", def.name, q.count.min(def.count), def.count))
.style(Style::new().fg(farbe)),
);
if !def.description.is_empty() {
zeilen.push(
Line::from(format!(" {}", def.description))
.style(Style::new().fg(Color::DarkGray)),
);
}
}
if zeilen.is_empty() {
zeilen.push(
Line::from("Noch nichts angenommen. Sprich mit jemandem.")
.style(Style::new().fg(Color::DarkGray)),
);
}
zeilen.push(Line::from(""));
zeilen.push(Line::from("Esc / q zurück").style(Style::new().fg(Color::DarkGray)));
frame.render_widget(
Paragraph::new(zeilen).block(Block::bordered().title(" Aufträge ")),
area,
);
}
fn draw_confirm(frame: &mut Frame, area: Rect, text: &str) {
let mut zeilen: Vec<Line> = text.lines().map(Line::from).collect();
zeilen.push(Line::from(""));