Files
rustkeep/.scratch/rustkeep-1.0/issues/02-typ-registry.md
MDietermann 69efa72d68 feat(charakterbau): Rasse und Klasse werden gewaehlt
Der Held war seit M1 als Zwergenkrieger verdrahtet, waehrend zwei Rassen
und zwei Klassen ungenutzt in der Registry lagen. Der Charakterbau ist
eine Schleife ueber categories_ordered() und options_of() — ein Mod, der
"Talent" mit pick = 2 ergaenzt, bekommt eine dritte Spalte ohne Code.

- CharacterOption bekommt abilities und on_create (Ticket 02, TODO seit M3)
- abilities_of liest jetzt Rasse und Klasse: Krieger traegt Wuchtschlag,
  Dieb Hinterhalt, Zwerg Steinsinn
- on_create gibt die Startausruestung als gewoehnliche Aktionen —
  starting_hero() schrumpft auf Creature::new(name)
- MainMenu trennt "neuer Held" von "Lauf fortsetzen"

Befund: Das Feld abilities fehlte, obwohl Ticket 15 sein ganzes Argument
darauf stuetzt — eine Klasse konnte nur flache Modifikatoren geben.
2026-08-13 08:03:19 +02:00

6.6 KiB

02 — Typ-Registry statt Enums

Type: grilling Status: resolved Blocked by: 01

Question

Wie werden Item-Typen, Rassen und Klassen zu Daten statt zu Rust-Enums?

enum ItemType kann ein Modder nicht erweitern — es muss eine Registry aus String-IDs werden. Zu klären:

  • Was deklariert ein Typ? Ausrüstungsslot, welche Felder Pflicht sind, welche Effekte er tragen darf.
  • Gibt es Slots als eigene Registry (core:helmet), oder ist der Slot ein fester Aufzählungswert?
  • Wie prüft das Spiel beim Laden, dass ein Item einen existierenden Typ nennt — und was passiert, wenn nicht?
  • Wie unterscheiden sich Rasse und Klasse strukturell, oder sind beide dasselbe: eine benannte Effektliste plus Metadaten?
  • Was verliert man ohne Enum an Compile-Zeit-Sicherheit, und was fängt das ab (Validierung beim Laden)?

Ergebnis: das Registry-Datenmodell plus TOML-Beispiel für einen selbstdefinierten Item-Typ.

Answer

Begriffe im Glossar.

Fünf Registries

Gegenstände · Slots · Charakteroption-Kategorien · Charakteroptionen · Fähigkeiten. Alle moddbar, alle namespaced.

Es gibt keinen Item-Typ mehr

enum ItemType verschwindet ersatzlos — er vermischte drei Konzepte: Helmet ist ein Slot, Consumable ein Verhalten, Currency eine Kategorie. Genau deshalb war er nie erweiterbar. An seine Stelle treten drei Felder am Gegenstand: slot (leer = nicht ausrüstbar), stackable, consumed_on_use.

ItemWeapon und ItemArmor entfallen ebenfalls. Ein Gegenstand ist einheitlich { id, name, base_price, slot?, stackable, consumed_on_use, modifiers[], actions[], abilities[] }. Ein Schwert ist ein Gegenstand mit einem Modifikator auf damage, ein Helm einer mit einem Modifikator auf resistance.

Tragende Regel: Braucht ein Gegenstand etwas, das Modifikator, Aktion oder Fähigkeit nicht ausdrücken können, ist das eine Lücke im Vokabular — sie wird dort geschlossen, nicht hier mit einem Sonderfeld umgangen.

Fähigkeit — der dritte Begriff

Modifikator rechnet um, Aktion wirkt einmal, Fähigkeit ist ein benanntes, wiederholt auslösbares Angebot an den Spieler, das Aktionen ausführt. Sie erscheint im Kampfmenü, hat Symbol und Kosten, kann gesperrt sein. Ein Zauberspruch als Gegenstand im Inventar wäre die Alternative gewesen — dann könnte ein Magier seine Zaubersprüche aber verkaufen oder verlieren.

Dieses Ticket legt nur fest, dass Fähigkeiten eine eigene Registry sind und von Gegenständen wie Charakteroptionen referenziert werden. Kosten, Abklingzeit und Verfügbarkeit entscheidet Ticket 14.

Deklarationen

# slots.toml
[[slots]]
id = "core:head"
name = "Kopf"
order = 10

# character_option_categories.toml
[[categories]]
id = "core:race"
name = "Rasse"
order = 10
pick = 1

# races.toml
[[options]]
id = "core:dwarf"
category = "core:race"
name = "Zwerg"
description = "Zäh, misstrauisch, trinkfest."
modifiers = [ { attribute = "constitution", flat = 2 } ]
abilities = ["core:stonesense"]
on_create = [ { action = "give_item", item = "core:pickaxe", count = 1, target = "self" } ]

Kategorien tragen pick = N, damit „wähle zwei Talente" ohne neue Maschine möglich ist.

on_create ist keine neue Mechanik, sondern eine Liste gewöhnlicher Aktionen, einmal beim Charakterbau ausgeführt. Startgold, Startausrüstung und Startbuff sind damit moddbar, ohne dass die Engine den Begriff „Startausrüstung" kennt.

Umbenennung

Item.value → base_price (Grundwert des Gegenstands). Attribut shop_price → price_modifier (Charaktereigenschaft, über die Charisma wirkt). Zwei Dinge, zwei Namen.

Nachtrag aus Ticket 09: zwei Fähigkeiten sind implizit

Oben steht, Fähigkeiten würden „von Charakteroptionen und Gegenständen gewährt". Genau das erzeugt eine Lücke: Ein Held, dessen Klasse nach einem Mod-Wechsel fehlt und der nichts trägt, hätte gar keine Fähigkeit — und könnte seit Ticket 11 nicht einmal angreifen, weil core:attack eine Fähigkeit ist.

Deshalb: core:attack und core:flee sind jedem Wesen implizit gewährt.

Das ist keine Sonderregel für den Ladefall. Es trifft ebenso jeden Gegner ohne abilities-Eintrag, vor dessen leerer Liste die KI aus Ticket 14 sonst stünde.

Auflagen an andere Tickets

  • 11 UI-Gerüst und Screen-Modell: Charakterbogen und Charakterbau sind Schleifen über Registries, keine fest gemauerten Layouts — Modder können Slots und Wahlkategorien ergänzen.
  • 14 Kampfformeln und Rundenablauf: muss Fähigkeitskosten, Abklingzeit und Verfügbarkeit mitentscheiden. Ticketkörper wurde entsprechend erweitert.

Folgen für bestehenden Code

src/items/weapon.rs und src/items/armor.rs werden gelöscht, enum ItemType entfällt, weapons.toml und armors.toml verschmelzen zu einer gewöhnlichen Gegenstandsdatei, items.rs schrumpft auf Laden plus Registry.

Befunde aus M8

abilities und on_create standen seit M3 als TODO — und abilities war eine echte Lücke. Ticket 15 hat später argumentiert: „Was Charakteroptionen dauerhaft trägt, sind Fähigkeiten — ein Magier bleibt ein Magier, weil er zaubern kann." Genau das Feld, auf dem dieses Argument ruht, gab es nicht; eine Klasse konnte bis M8 ausschließlich flache Modifikatoren geben, also genau die Hälfte, die laut demselben Ticket auf Stufe 30 zu Rauschen wird. Beide Felder sind jetzt da, abilities_of liest sie, und Krieger wie Dieb tragen eine eigene Fähigkeit.

on_create hat sich als das erwiesen, was es sein sollte: keine Mechanik. Startausrüstung kommt über give_item, Startgold über dasselbe — die Engine kennt den Begriff nicht. starting_hero() in main.rs, das seit M1 Schwert, Kappe und Trank von Hand anlegte, ist auf Creature::new(name) geschrumpft. Das war der eigentliche Test der Entscheidung: Der hartcodierte Held war die Startausrüstungs-Sonderregel, nur an einer Stelle, an der niemand sie so genannt hat.

„Registry-Schleife statt gemauertem Layout" trägt. Der Charakterbau liest categories_ordered() und options_of(); ein Mod, der „Talent" mit pick = 2 ergänzt, bekommt eine dritte Spalte, ohne dass eine Zeile Code davon weiß. Geprüft wird pick in rejects, nicht im Screen — der Screen zählt gar nicht mit.

Der Startpunkt braucht ein Flag, keine Herleitung. Naheliegend wäre gewesen, „Held ist noch nicht gebaut" aus player.options.is_empty() zu folgern. Das ist falsch: Ein Speicherstand, dem der Rassen-Mod fehlt, hat nach der Verwerf-Regel aus Ticket 09 ebenfalls keine Optionen — und würde neu gebaut statt fortgesetzt. MainMenu { neu } weiß es stattdessen von main, das den Unterschied kennt.