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.
144 lines
6.6 KiB
Markdown
144 lines
6.6 KiB
Markdown
# 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](../../../CONTEXT.md).
|
|
|
|
### 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
|
|
|
|
```toml
|
|
# 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](../../../CONTEXT.md) 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.
|