08 — Event- und Trigger-System #9

Closed
opened 2026-08-11 13:13:27 +00:00 by RDietermann · 1 comment
Owner

Teil der Map #1 · Type: wayfinder:grilling · Blocked by: #8

Welche Ereignisse meldet das Spiel, und wie haengen Quests und spaeter Scripts daran?

Volle Fragestellung (kanonisch): .scratch/rustkeep-1.0/issues/08-event-trigger-system.md

Teil der Map #1 · **Type: `wayfinder:grilling`** · **Blocked by: #8** Welche Ereignisse meldet das Spiel, und wie haengen Quests und spaeter Scripts daran? Volle Fragestellung (kanonisch): `.scratch/rustkeep-1.0/issues/08-event-trigger-system.md`
RDietermann added the wayfinder:grilling label 2026-08-11 13:13:27 +00:00
Author
Owner

Aufgeloest in d5a6feb. Kanonische Fassung: .scratch/rustkeep-1.0/issues/08-event-trigger-system.md

Der Trigger ist die vierte Wirkungsform

{ on = "attack_hit", condition = [ ... ], actions = [ ... ] }

Modifikator rechnet um, Aktion wirkt einmal, Faehigkeit bietet an — der Trigger sagt wann. Er darf ueberall stehen, wo modifiers und actions stehen: Gegenstaende, Affixe, Charakteroptionen, Quests, spaeter Scripts.

Das kostet nichts (Aktionsliste plus zwei Felder, Aktionen sind seit #2 definiert). Die Alternative waere nicht weniger Code, sondern dieselbe Maschine ein zweites Mal — in #15 unter dem Namen on_hit. Ohne Trigger waere "Flammendes Schwert: 20% Chance Brennen" nicht ausdrueckbar.

Bedingungen — eine Grammatik fuer alles

condition = [
  { check = "has_tag",   who = "target", value = "core:undead" },
  { check = "attribute", who = "self",   attribute = "level", at_least = 3 },
  { check = "has_item",  who = "self",   item = "core:key", count = 1, not = true },
]

Getaggtes Enum, Liste ist implizit UND, jeder Eintrag ueber not negierbar. Kein ODER, kein Verschachteln — ein rekursiver Ausdrucksbaum braeuchte eine Klammer-Syntax, die in TOML niemand freiwillig schreibt. Decke offen benannt: "(untot ODER daemonisch) UND Level >= 3" wird zu zwei Eintraegen dupliziert, oder der Modder haengt beiden ein Tag core:unheilig an.

Neun Events, feste Liste

turn_started · attack_hit · damage_taken · creature_died · item_picked_up · item_used · floor_entered · npc_talked · quest_completed

Jedes traegt source: CreatureRef und target: Option<CreatureRef> plus eigene Nutzlast. Kein player_died — das ist creature_died mit source = Player. attack_hit und damage_taken bleiben getrennt: das eine feuert beim Angreifer (flammendes Schwert), das andere beim Getroffenen (Dornenpanzer).

self ist der Traeger des Triggers, target die andere Seite des Events. Damit ist eine Quest kein Sonderfall, sondern ein Trigger mit festem Traeger.

Keine Mod-Events fuer 1.0 — ohne Scripting kann niemand eines ausloesen, es waere Vokabular ohne Sender. Kommt Stufe (c) ueber #14, wird emit_event eine gewoehnliche Aktion und die Liste bekommt eine Custom(String)-Variante.

Warteschlange statt Rekursion

Events landen in Game.events: Vec<Event> und werden nach der laufenden Aktion leergearbeitet. Zur Haelfte ein Rust-Fakt: Game::apply haelt &mut self, synchrones Feuern hiesse ueber self.quests iterieren waehrend self.apply erneut laeuft — Borrow-Fehler, kein Stilproblem. Zur anderen Haelfte die Rekursionsfrage: eine Belohnung, die ein Event ausloest, haengt es hinten an.

Gegen Zyklen ein Zaehler: maximal 100 Events pro Zug, dann Abbruch mit Warnzeile. Kein Zyklengraph. Trigger werden bei jedem Event gesucht, nicht indiziert — ein Index muesste bei jedem An- und Ablegen gepflegt werden, also genau das Invalidierungsproblem, das #8 vermieden hat.

Quests

Eine Quest ist ein Trigger mit count = N und damit die siebte Registry:

[[quests]]
id = "core:rattenplage"
on = "creature_died"
count = 5
condition = [ { check = "has_tag", who = "target", value = "core:rat" } ]
actions = [ { action = "give_item", item = "core:gold", count = 50, target = "self" } ]

"Besitze 100 Gold" braucht keine zweite Mechanik: on = "item_picked_up", has_item >= 100, count = 1. QuestProgress { quest, count, done } — erledigte bleiben mit done = true stehen, sonst kann eine Kette auf quest_completed nicht zwischen "lief nie" und "gerade fertig" unterscheiden.

Zwei Nachtraege an geschlossene Tickets

  • #2 (01): start_quest kommt als siebte Aktion dazu. Ohne Aktivierung waere "geh in den Keller" erfuellt, bevor jemand die Quest erwaehnt hat, und ein Mod mit 40 Quests liesse 40 Zaehler pro Event mitlaufen.
  • #8 (07): Game::attribute(who, attr, against: Option<CreatureRef>, reg). Ohne den Gegner ist "+5 Schaden gegen Untote" beim Auslesen von damage nicht auswertbar. Als Trigger mit Zusatzschaden zu loesen waere nicht dasselbe Ergebnis — der zweite Schadensfall liefe getrennt durch Resistenz und Krit. Dazu events: Vec<Event> als neues Feld.

Auflagen

  • #11 (10 Prefabs): Raumbetreten als Trigger-Ort — floor_entered deckt nur die Etage ab
  • #14 (13 Script-Engine): emit_event und namespaced Events, sobald Stufe (c) steht
  • #15 (14 Kampf): wo genau attack_hit und damage_taken im Zug feuern
**Aufgeloest** in d5a6feb. Kanonische Fassung: `.scratch/rustkeep-1.0/issues/08-event-trigger-system.md` ### Der Trigger ist die vierte Wirkungsform ```toml { on = "attack_hit", condition = [ ... ], actions = [ ... ] } ``` Modifikator rechnet um, Aktion wirkt einmal, Faehigkeit bietet an — der **Trigger sagt wann**. Er darf ueberall stehen, wo `modifiers` und `actions` stehen: Gegenstaende, Affixe, Charakteroptionen, Quests, spaeter Scripts. Das kostet nichts (Aktionsliste plus zwei Felder, Aktionen sind seit #2 definiert). Die Alternative waere nicht weniger Code, sondern dieselbe Maschine ein zweites Mal — in #15 unter dem Namen `on_hit`. Ohne Trigger waere "Flammendes Schwert: 20% Chance Brennen" nicht ausdrueckbar. ### Bedingungen — eine Grammatik fuer alles ```toml condition = [ { check = "has_tag", who = "target", value = "core:undead" }, { check = "attribute", who = "self", attribute = "level", at_least = 3 }, { check = "has_item", who = "self", item = "core:key", count = 1, not = true }, ] ``` Getaggtes Enum, Liste ist implizit **UND**, jeder Eintrag ueber `not` negierbar. **Kein ODER, kein Verschachteln** — ein rekursiver Ausdrucksbaum braeuchte eine Klammer-Syntax, die in TOML niemand freiwillig schreibt. Decke offen benannt: "(untot ODER daemonisch) UND Level >= 3" wird zu zwei Eintraegen dupliziert, oder der Modder haengt beiden ein Tag `core:unheilig` an. ### Neun Events, feste Liste `turn_started` · `attack_hit` · `damage_taken` · `creature_died` · `item_picked_up` · `item_used` · `floor_entered` · `npc_talked` · `quest_completed` Jedes traegt `source: CreatureRef` und `target: Option<CreatureRef>` plus eigene Nutzlast. Kein `player_died` — das ist `creature_died` mit `source = Player`. `attack_hit` und `damage_taken` bleiben getrennt: das eine feuert beim Angreifer (flammendes Schwert), das andere beim Getroffenen (Dornenpanzer). **`self` ist der Traeger des Triggers**, `target` die andere Seite des Events. Damit ist eine Quest kein Sonderfall, sondern ein Trigger mit festem Traeger. **Keine Mod-Events fuer 1.0** — ohne Scripting kann niemand eines *ausloesen*, es waere Vokabular ohne Sender. Kommt Stufe (c) ueber #14, wird `emit_event` eine gewoehnliche Aktion und die Liste bekommt eine `Custom(String)`-Variante. ### Warteschlange statt Rekursion Events landen in `Game.events: Vec<Event>` und werden nach der laufenden Aktion leergearbeitet. Zur Haelfte ein Rust-Fakt: `Game::apply` haelt `&mut self`, synchrones Feuern hiesse ueber `self.quests` iterieren waehrend `self.apply` erneut laeuft — Borrow-Fehler, kein Stilproblem. Zur anderen Haelfte die Rekursionsfrage: eine Belohnung, die ein Event ausloest, haengt es hinten an. Gegen Zyklen ein Zaehler: **maximal 100 Events pro Zug**, dann Abbruch mit Warnzeile. Kein Zyklengraph. Trigger werden bei jedem Event **gesucht, nicht indiziert** — ein Index muesste bei jedem An- und Ablegen gepflegt werden, also genau das Invalidierungsproblem, das #8 vermieden hat. ### Quests Eine Quest **ist ein Trigger mit `count = N`** und damit die siebte Registry: ```toml [[quests]] id = "core:rattenplage" on = "creature_died" count = 5 condition = [ { check = "has_tag", who = "target", value = "core:rat" } ] actions = [ { action = "give_item", item = "core:gold", count = 50, target = "self" } ] ``` "Besitze 100 Gold" braucht keine zweite Mechanik: `on = "item_picked_up"`, `has_item >= 100`, `count = 1`. `QuestProgress { quest, count, done }` — erledigte bleiben mit `done = true` stehen, sonst kann eine Kette auf `quest_completed` nicht zwischen "lief nie" und "gerade fertig" unterscheiden. ### Zwei Nachtraege an geschlossene Tickets - **#2 (01)**: `start_quest` kommt als **siebte Aktion** dazu. Ohne Aktivierung waere "geh in den Keller" erfuellt, bevor jemand die Quest erwaehnt hat, und ein Mod mit 40 Quests liesse 40 Zaehler pro Event mitlaufen. - **#8 (07)**: `Game::attribute(who, attr, against: Option<CreatureRef>, reg)`. Ohne den Gegner ist "+5 Schaden gegen Untote" beim Auslesen von `damage` nicht auswertbar. Als Trigger mit Zusatzschaden zu loesen waere *nicht dasselbe Ergebnis* — der zweite Schadensfall liefe getrennt durch Resistenz und Krit. Dazu `events: Vec<Event>` als neues Feld. ### Auflagen - **#11** (10 Prefabs): Raumbetreten als Trigger-Ort — `floor_entered` deckt nur die Etage ab - **#14** (13 Script-Engine): `emit_event` und namespaced Events, sobald Stufe (c) steht - **#15** (14 Kampf): wo genau `attack_hit` und `damage_taken` im Zug feuern
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RDietermann/rustkeep#9