Files
rustkeep/.scratch/rustkeep-1.0/issues/16-loot-tabellen.md

182 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 16 — Loot-Tabellen
Type: grilling
Status: resolved
Blocked by: 02
## Question
Wie werden Beutefunde definiert, gewichtet und ausgewürfelt?
Die Gegenstands-Registry aus 02 steht, `luck` ist ein Attribut aus 01. Offen ist alles dazwischen:
- **Wo hängt Beute?** Am Gegner, am Raum-Prefab, an der Etage, oder an einer benannten Tabelle, die
mehrere referenzieren können?
- **Wie wird gewichtet?** Gewichte pro Eintrag, Wahrscheinlichkeit in Prozent, oder Seltenheitsstufen
mit fester Verteilung? Dürfen Tabellen andere Tabellen referenzieren?
- **Wie wirkt `luck`?** Auf die Drop-Chance, auf die Seltenheitsstufe, auf die Menge — oder auf
mehreres, und mit welcher Formel?
- **Mengen und Garantien**: Gibt es „genau ein Gegenstand", „null bis drei", garantierte Drops für
Bosse? Kann ein Eintrag mehrfach gezogen werden?
- **Etagenskalierung**: Fallen auf Etage 5 andere Gegenstände als auf Etage 1, und wird das über
getrennte Tabellen ausgedrückt oder über eine Bedingung am Eintrag?
- **Wie greifen Mods ein?** Nur neue Tabellen hinzufügen, oder auch Einträge in fremde Tabellen
einhängen — und wie sieht dieses Einhängen aus, ohne die fremde Datei zu überschreiben?
- **Zufall**: derselbe Generator wie im Kampf (Ticket 14), und gehört sein Zustand in den Speicherstand?
Ergebnis: Tabellenschema mit TOML-Beispiel, Gewichtungsregel, `luck`-Formel und die Mod-Einhängepunkte.
## Vorgabe aus Ticket 07
Beute ist `ItemInstance { def, affixes }`, nicht bloß eine Gegenstands-ID. Damit kommt eine Frage
dazu, die es vor 07 nicht gab: **Wer vergibt Affixe beim Loot, und mit welcher Gewichtung?** Hängen
sie an der Tabelle, am Eintrag, an der Etage — und wirkt `luck` auf die Affix-Zahl, auf deren
Seltenheit, oder gar nicht?
Affixe sind eine eigene Registry (die sechste), also gilt für sie derselbe Mod-Einhängepunkt wie für
Gegenstände.
## Answer
Begriffe im [Glossar](../../../CONTEXT.md). Der Zufall war bereits entschieden: derselbe gesetzte
Generator aus Ticket 14, dessen Seed im Save liegt — Beute würfelt beim Nachladen nicht neu.
### Eine Maschinerie für Gegenstände und Affixe
Zwölfte Registry `[[loot_tables]]`:
```toml
[[loot_tables]]
id = "core:weapon_affixes"
rolls = "0-1"
entries = [
{ affix = "core:flaming", weight = 5 },
{ affix = "core:greater_flaming", weight = 1 }, # Stufe, kein Wert
{ nothing = true, weight = 20 },
]
[[loot_tables]]
id = "core:floor_3_table"
rolls = "1-3"
entries = [
{ item = "core:health_potion", weight = 10, count = "1-2" },
{ item = "core:iron_sword", weight = 3, affixes = "core:weapon_affixes" },
{ table = "core:rare_table", weight = 1 },
{ nothing = true, weight = 20 },
{ item = "core:boss_blade", guaranteed = true },
]
```
**Gewichte statt Prozent** — keine Geschmacksfrage, sondern eine Modding-Frage: Prozentwerte müssen
sich auf 100 summieren, und ein hinzugefügter Eintrag macht die Summe kaputt, woraufhin jemand alle
anderen anpassen muss. Gewichte verdünnen sich anteilig, ohne dass irgendwer rechnet.
**Nieten sind gewöhnliche Einträge**, kein eigenes `drop_chance`-Feld. Ein Gegner, der selten etwas
fallen lässt, bekommt ein hohes `nothing`-Gewicht — die Engine kennt eine Mechanik statt zweier.
**Tabelle-in-Tabelle** ist eine Zeile und liefert Seltenheitsstufen, ohne dass „Seltenheit" ein Begriff
werden muss.
**`guaranteed = true`** wird immer ausgegeben und nimmt an der Gewichtung nicht teil. Mit Gewichten
allein lässt sich „sicher" nicht ausdrücken, und Bosse stehen in der Destination; Questgegenstände sind
derselbe Fall.
**Mehrfachziehung erlaubt**, kein `unique`-Flag. Drei Heiltränke sind ein normales Ergebnis, und
stapelbare Gegenstände (02) tragen das ohne Zutun. Wer Einzelstücke will, gibt ihnen eine eigene
Tabelle mit `rolls = "1-1"`. Die Decke: Falls „nie zweimal dasselbe Schwert" nötig wird, ist `unique`
ein Flag — jetzt wäre es eine Regel ohne Fall.
### Affix-Stufen statt Affix-Werte
Ticket 07 hat festgelegt, dass eine `ItemInstance` **nur Affix-IDs** trägt. „Flammend" und „Stark
flammend" sind deshalb zwei Registry-Einträge mit unterschiedlichen Modifikatoren und Preisfaktoren,
keine Zahl am Exemplar.
Eine Stärke je Exemplar hätte drei Dinge auf einmal gekostet: das Save-Format (ID-Liste), die
Preisformel `base_price × Π price_factor` und die Stapelregel — zwei Schwerter „Flammend 3" und
„Flammend 4" stapeln nicht mehr, aber wie zeigt man den Unterschied an? Mit Stufen kann der Modder
seine Abstufungen **benennen**, statt eine Zahl auszuliefern, die keine Anzeige hat.
### `luck` hat genau eine Regel
**Eine Chance auf einen zusätzlichen Zug — auf jeder Tabelle.**
```
p_extra = clamp(luck * LUCK_FACTOR, 0.0, 0.9)
```
Ein Knopf mehr in `[balance]`. Die Regel wirkt dreifach, ohne dreimal geschrieben zu werden: mehr
Gegenstände, mehr Affixe, und häufiger die stärkere Stufe — weil die als eigener Eintrag mit kleinem
Gewicht danebensteht.
Der Grund gegen „Glück verschiebt zur Seltenheit" ist ein Ausschluss: **Es gibt keinen
Seltenheitsbegriff.** Das Schema kennt nur Gewichte; Seltenheit erst zu definieren und dann Gewichte
umzurechnen wäre ein zweites Konzept für dasselbe Gefühl.
### Der Mod-Einhängepunkt existiert bereits
Ticket 03 gilt unverändert: ganzer Eintrag wird ersetzt. Ein neuer Mechanismus wird trotzdem nicht
gebraucht — **Tabelle-in-Tabelle ist der Einhängepunkt**, zwei gewöhnliche Überschreibungen und null
neue Begriffe:
```toml
[[loot_tables]]
id = "meinmod:floor_3"
entries = [ { table = "core:floor_3_table", weight = 100 },
{ item = "meinmod:zauberstab", weight = 5 } ]
[[floors]]
id = "core:floor_3"
overrides = "core:floor_3"
loot = "meinmod:floor_3"
```
Decke: Zwei Mods, die dieselbe Etage umhängen, kollidieren — der letzte gewinnt. Das ist derselbe
Konflikt wie bei jeder anderen Überschreibung in 03. Falls er häufig wird, ist `[[loot_additions]]` der
Nachbau; ihn *vorher* zu bauen hieße, einen zweiten Mechanismus für etwas einzuführen, das der erste
schon kann.
### Beute wird beim Aufheben gezogen
`Room` aus Ticket 10 bekommt **eine `loot_table` und `loot: Vec<Pos>`** statt fertiger Instanzen — alle
Haufen eines Raums ziehen ohnehin aus derselben Tabelle, die ID je Haufen zu wiederholen wäre
Redundanz. Aufgelöst wird erst beim Aufheben; der Save wird dadurch sogar kleiner.
Die **Zahl** der Haufen steht bei der Erzeugung fest (die `l`-Zeichen im Prefab), ihr Inhalt nicht.
Alle zeigen **dasselbe generische Symbol** — deshalb verrät die Anzeige nichts, und ein Glücksring
wirkt auch auf das, was auf dieser Etage bereits liegt. Bei Ziehung zur Erzeugungszeit wäre der Wurf
für diese Etage schon gefallen, bevor der Spieler den Ring findet.
Fällt der Wurf beim Aufheben auf eine Niete, **verschwindet der Haufen mit einer Meldung**. Kein
Neuwürfeln — das hängt sich bei einer reinen Nieten-Tabelle auf und bräuchte dann doch einen Zähler.
Keine Ladeprüfung „Tabellen für `l`-Plätze dürfen keine Nieten enthalten": Das wäre die Bevormundung,
die Ticket 02 abgeräumt hat. Wer leere Haufen nicht will, lässt Nieten aus der Tabelle weg.
### Etagenskalierung — die Auflage aus 15 ist strukturell erfüllt
Eine Tabelle je Etage, wie Ticket 10 es mit dem `loot`-Feld bereits angelegt hat. **Nirgends steht eine
Fünf**: Etagen sind Registry-Einträge, jeder bringt seine Tabelle mit, ein Mod mit Etage 6 bringt ihre
gleich mit, und der Endlosmodus wiederholt mit der letzten Etage auch deren Tabelle.
Bedingungen am Eintrag (`floor at_least 3` auf einer gemeinsamen Tabelle) klingen flexibler, lösen das
Endlos-Problem aber gerade *nicht*: Ein Eintrag mit `at_least = 3` ist auf Etage 500 genauso gültig wie
auf Etage 3. Echte Tiefenskalierung bräuchte Gewichte als *Funktion* der Etage — eine Formel, keine
Daten, und die wird gebaut, wenn der Endlosmodus kommt.
Ein Prefab darf über ein **optionales** `loot`-Feld seine eigene Tabelle nennen. Sonst ist ein
Schatzkammer-Raum nur ein Raum mit mehr `l`-Zeichen — mehr vom Gleichen statt etwas Besserem. Es nennt
dabei eine Tabellen-**ID**, keine Gegenstände, und bleibt damit über alle Tiefen verwendbar; dieselbe
Begründung, mit der 10 festgelegt hat, dass ein `e` nicht sagt, *welcher* Gegner.
### Wo Beute hängt
An der `creatures`-Registry (14), an der Etage (10) und optional am Prefab. Alle drei nennen schlicht
eine Tabellen-ID.
### Zwei Nachträge an geschlossene Tickets
| Ticket | Nachtrag |
|---|---|
| **10** | `Room` hält `loot_table: LootTableId` plus `loot: Vec<Pos>` statt `Vec<(Pos, ItemInstance)>`; optionales `loot`-Feld am Prefab; generischer Beute-Marker im Grid |
| **14** | `LUCK_FACTOR` in `[balance]` |