182 lines
8.6 KiB
Markdown
182 lines
8.6 KiB
Markdown
# 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]` |
|