Vierzehnte Registry: NPCs sind keine Wesen — kein Kampfzustand, keine Initiative, keine Attribute. Ihre Antwortliste ist flach; was erst nach einer Antwort erscheint, ist eine Option mit einer Bedingung. - open_shop als elfte Aktion (game/ setzt eine Absicht, die UI schiebt) - Talk/Choose/Buy/Sell, Talk wirkt auf das eigene Feld wie PickUp - Preisspanne statt Goldkonto beim Haendler, Charisma in der Preisfunktion - Warenkorb einmal bei der Etagenerzeugung gezogen - Stapelregel aus Ticket 07 endlich gebaut: 50-120 Gold war 120 Eintraege - Dialog- und Shop-Screen, NPC-Symbol im Grid
8.8 KiB
01 — Effekt-Vokabular
Type: grilling Status: resolved Blocked by: —
Question
Was kann ein Effekt ausdrücken, und wie sieht er in TOML aus?
Das Effekt-Vokabular ist die Mod-API: ein Modder kann exakt das bauen, was hier definiert wird. Zu klären:
- Welche Wirkungen gibt es? Stat-Modifikator (flach, prozentual), Sofortschaden/Heilung, Ressourcenverbrauch, Statuseffekt anwenden/entfernen, Event auslösen.
- Zeitlichkeit: permanent (Rasse), solange ausgerüstet (Item), N Runden (Statuseffekt), einmalig (Trank).
- Wie werden Effekte gestapelt und in welcher Reihenfolge verrechnet? Erst flach, dann prozentual?
- Wie referenziert ein Effekt sein Ziel — Träger, Gegner, alle Gegner?
- Wie sieht die TOML-Repräsentation aus, und bleibt sie lesbar genug für Modder ohne Doku?
Ergebnis: eine Liste der Effekt-Varianten mit TOML-Beispiel je Variante.
Answer
Begriffe sind im Glossar festgehalten.
Zwei Begriffe, nicht einer
Modifikator rechnet ein Attribut um, solange er existiert — er wird nie ausgeführt, sondern beim Auslesen aufsummiert. Aktion ändert Zustand einmalig. Getrennte Maschinen, getrennte TOML-Arrays. Ein gemeinsamer Oberbegriff „Effekt" ist ausdrücklich kein kanonischer Begriff mehr.
Attributkatalog (Engine-Vokabular, ohne Namensraum)
strength · dexterity · constitution · charisma · luck · level · max_health ·
max_stamina · damage · hit_chance · critical_chance · critical_factor ·
resistance (typ-indiziert) · shop_price · carry_capacity
max_health ist ein Attribut und wird von Modifikatoren umgerechnet. Der aktuelle Lebensstand ist
kein Attribut, sondern Zustand — den ändern Aktionen. Diese Trennung muss scharf bleiben.
Modifikator
modifiers = [
{ attribute = "damage", flat = 5 },
{ attribute = "strength", flat = 2, percent = 5 },
{ attribute = "critical_chance", percent = 10, duration = "turns", turns = 3,
target = "all_enemies", symbol = "curse", name = "Verflucht" },
]
flatundpercentbeide optional, mindestens eines Pflicht.duration:permanent·while_equipped·turns·until_floor_end.target:self·target·all_enemies. Der Modifikator wohnt immer beim Träger und wird beim Auslesen des Ziels eingesammelt — stirbt der Träger, verschwindet die Aura von selbst.symbolundnamenur bei befristeten Zuständen; Item-Boni erben beides vom Item.symbolist ein Schlüssel, keine Farbe — die Zuordnung machtui/theme.rs.
Aktion
actions = [
{ action = "damage", amount = 12, damage_type = "fire", target = "target" },
{ action = "change_attribute", attribute = "health", amount = 20, target = "self" },
{ action = "apply_modifier", modifier = "core:poisoned", target = "target" },
{ action = "remove_modifier", modifier = "core:poisoned", target = "self" },
{ action = "give_item", item = "core:gold", count = 50, target = "self" },
{ action = "remove_item", item = "core:torch", count = 1, target = "self" },
]
Sechs Aktionen. damage bleibt eigenständig statt change_attribute health -12, weil Schaden durch
eine Kette läuft (Resistenz mindert, Krit vervielfacht, Bedingungen greifen) und „bei erlittenem
Schaden" ein Event ist. Flache Liste, keine verschachtelten Aktionen — Ketten laufen über das
Event-System.
Regeln
| Stapeln | (Basis + Σ flach) × (1 + Σ prozentual), intern f32, gerundet an genau einer Stelle |
| Uhr | Ein Zug. Ein Feld gehen = ein Zug, eine Kampfaktion = ein Zug. Debuffs ticken auch beim Laufen |
| Schadenstypen | fire ice poison shock holy slash blunt — fest, Engine-Vokabular |
| Minderung | Ein Mechanismus: resistance je Typ. armor am Item ist Kurzschreibweise für Resistenz gegen slash und blunt |
| Namensraum | Doppelpunkt = Modder-Inhalt (core:poisoned). Ohne = Engine-Vokabular (strength, fire) |
| Tags | Freie namespaced Strings am Wesen, Grundlage für Bedingungen |
| Sprache | Schadenstyp poison ≠ Modifikator core:poisoned („Vergiftet") |
Nachtrag aus Ticket 08: start_quest
Der Katalog hat sieben Aktionen, nicht sechs. start_quest kam mit
Ticket 08 dazu, weil eine Quest sonst ab dem ersten Zug mitzählte und
Questketten beim Betreten des Kellers bereits erfüllt wären.
{ action = "start_quest", quest = "core:rattenplage", target = "self" }
Das hier angeforderte Bedingungsvokabular steht ebenfalls in 08, zusammen mit dem Trigger als vierter Wirkungsform.
Nachtrag aus Ticket 11: symbol trägt das Zeichen, nicht einen Schlüssel
Oben steht, symbol sei ein Schlüssel und die Zuordnung mache ui/theme.rs. Darin steckt eine Lücke:
ui/theme.rs ist Code. Ein Modder, der meinmod:frostbite anlegt, kann dort nichts eintragen —
sein Statuseffekt hätte weder Zeichen noch Farbe.
Stattdessen trägt die TOML beides selbst, die Farbe als Name aus fester Liste:
symbol = "†"
color = "cyan"
data/ kennt nur char und ein Farbnamen-Enum, ui/theme.rs übersetzt in ratatui::Color — game/
und data/ bleiben damit weiterhin ratatui-frei.
Nachtrag aus Ticket 14: damage skaliert, und flee ist die achte Aktion
Die damage-Aktion oben kennt nur amount = 12, also einen festen Wert. Ein Waffenangriff soll aber
das damage-Attribut des Angreifers verwenden — das war gar nicht ausdrückbar. Die Menge ist deshalb
immer:
menge = amount + scale * damage-Attribut(Verursacher) # beide optional, Standard 0
Eine Formel für alles: Basisangriff scale = 1.0 · Feuerball amount = 20 · Wuchtschlag
scale = 1.5 · Mischformen beides. Keine zwei Modi, kein Sonderfall für core:attack.
Dazu flee als achte Aktion — Ticket 11 hat Flucht zu einer Fähigkeit gemacht, und die braucht
eine Aktion, die sie ausführt.
Nachtrag aus Ticket 13: run_script
{ action = "run_script", script = "meinmod:feuerregen" } als neunte Aktion. Ein Script ist damit
überall verwendbar, wo eine Aktion stehen darf, und braucht keine eigenen Platzierungsregeln. Es gibt
Aktionen zurück, die in die Warteschlange aus 08 wandern — der Schreibpfad bleibt also genau einer.
Nachtrag aus Ticket 15: give_exp
{ action = "give_exp", amount = 50, target = "self" } als zehnte Aktion, für Questbelohnungen.
Erfahrung aus Tötungen läuft dagegen über das Feld exp_reward an der creatures-Registry — die
Kurzform für einen Todes-Trigger, den sonst jede Gegnerdefinition mitschleppen müsste.
Ticket 15 hat außerdem geprüft, ob ein Modifikator „je Level" skalieren können muss (per_level).
Antwort: nein — Charakteroptionen geben flache Boni, und was sie dauerhaft relevant hält, sind
Fähigkeiten. Das Vokabular bleibt hier unverändert.
Der Katalog
damage · change_attribute · apply_modifier · remove_modifier · give_item · remove_item ·
start_quest (08) · flee (14) · run_script (13) · give_exp (15) · open_shop (M4) —
elf.
open_shop kam mit dem Handelsmodell dazu: Eine Dialogoption oeffnet den Laden, und als Aktion steht
sie in derselben Liste wie start_quest statt als Sonderfeld daneben.
Nachtrag aus M3: benannte Modifikatoren brauchen eine Registry
Oben steht { action = "apply_modifier", modifier = "core:poisoned", … } — eine Aktion referenziert
einen Modifikator per ID. Aufgefallen beim Bauen von M3: Keine der zwölf Registries definiert
benannte Modifikatoren. core:poisoned hatte kein Zuhause.
Das ist die dreizehnte Registry, [[modifiers]]:
[[modifiers]]
id = "poisoned"
name = "Vergiftet"
symbol = "☠"
turns = 4
attributes = [ { attribute = "damage", percent = -15 } ]
triggers = [ { on = "turn_started",
actions = [ { action = "damage", amount = 2, damage_type = "poison", target = "self" } ] } ]
Das innere Feld heißt bewusst attributes und nicht modifiers — ein Modifikator, der Modifikatoren
enthält, liest sich wie ein Fehler. Und effects wäre der Begriff, den dieses Ticket ausdrücklich
verworfen hat.
Ein Modifikator ist damit ein Bündel: mehrere Umrechnungen plus Trigger plus Dauer. Gift senkt den Schaden und tickt — das wäre mit einer einzelnen Umrechnung nicht ausdrückbar.
Bewusst nicht entschieden
Das Bedingungsvokabular für condition. Es kommt aus Ticket 08, damit Quests und Modifikatoren
dieselbe Grammatik benutzen statt zweier, die sich subtil unterscheiden. Ticket 01 fordert es nur an.
Folgen für bestehenden Code
ItemArmor.armor fällt weg und wird ein Modifikator auf resistance. Stats trennt Attribute von
Zustand. Der Attributkatalog braucht abgeleitete Größen, die es heute gar nicht gibt (hit_chance,
carry_capacity).