Files
llamamod/notes/objects.md
T

136 lines
9.2 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.
# MAP-Chunk & Objekte (Strats) – "Ein Königreich für ein Lama" (PC)
Quelle: groove.exe, MAP-Lader `FUN_0042ac50`, Strat-Spawn `FUN_0054cfc0` → `FUN_0054bfc0`,
Strat-VM `FUN_0054d180`, Chunk-Dispatcher bei 0x558960. Werkzeuge in `../tools/`.
Alle 34 Original-MAPs werden von `mapchunk.parse()` vollständig gelesen (Rest 0 Byte) und
von `mapchunk.build()` **byte-identisch** zurückgeschrieben.
## Begriffe
* **Tile** = platziertes Geometrie-Modell aus **TRAK** (Level-"Baukasten", 1x1-Raster).
* **Strat** = Objekt/Script-Instanz (Gegner, Münze, Tür, Spieler …). Der Strat-Code
(Bytecode, u32-Wörter) und die Objektmodelle liegen in **STPC**; MAP enthält nur Instanzen
mit Offset in STPC. Es gibt **keine Typ-ID** – der Typ *ist* die Code-Adresse.
Typnamen stehen im Code: Opcode `0xd4, 0xea, s32 rel` → Name = C-String bei
(Adresse hinter rel) + rel. `strats.py` löst das auf.
* Koordinaten: Welt-y nach **oben**. Tiles speichern float x, y, z mit **z negativ**;
Strats/fixed-point (20.12, 4096 = 1.0) speichern z **positiv** (= −tile.z).
Winkel: 4096 = 360°. Boden-Tiles (flaches Modell, Vertex-y = +0.25) → Objekte stehen auf
`tile.y + 0.25` (bestätigt an Spieler/Checkpoints/Münzen).
## Layout MAP (alles little endian) — Konfidenz: hoch (aus Lader + Roundtrip)
WFPC (u32 Flags, globale 0x6da330) steuert optionale Blöcke.
| # | Block | Format |
|---|---|---|
| 1 | Kopf | u32 nTiles, u32 W, u32 H (Rastergröße in Zellen) |
| 2 | Tiles | nTiles × {f32 x, f32 y, f32 z, u32 rotY(0/1024/2048/3072), u32 modell, u32 hatAlt} |
| 3 | Parameterpool | u32 n, n × u32 (Strat-Parameter, von Strats per Index referenziert) |
| 4 | Zonen ("Environment") | u32 n, n × 90 Byte (s.u.) |
| 5 | Wegpunkte | u32 n, n × 34 Byte {u32 weiter(≠0: nächster Eintrag gehört zur selben Kette, 0 = Kettenende), u32, u16 rx, u16 ry, u16 rz, s32 x, y, z (fixed, z positiv), u32, u32} |
| 6 | Regionstabelle | 32 × {u32 flags, u32 wert} (→ 0x6da350) |
| 7 | Regionsraster | W×H × u32 Regionsindex (0..31) je Zelle, Zeile = z-Zelle |
| 8 | Tiles fixed | nTiles × 6 u32 {rx=0, rotY, rz=0, x·4096, y·4096, −z·4096} – **exakt aus Block 2 ableitbar** |
| 9 | Tile-Modelle | nTiles × u32 TRAK-Modellindex (= Feld 5 aus Block 2) |
| 10 | Alt-Modelle (WFPC&0x10000) | u32 n, n × 5 u32; n = Anzahl Tiles mit hatAlt≠0, in Tile-Reihenfolge; Feld 4 = alternatives TRAK-Modell (z. B. Tür offen/zu, stMapreplace) |
| 11 | Vertexfarben | je Tile: nv(TRAK-Modell) × u32 RGBA (Byte 3 = Frameanzahl F); wenn max F>1: (F−1)×nv weitere u32; danach dasselbe für das Alt-Modell, falls vorhanden. Farbe wird ×2 (Clamp 255) |
| 12 | Strats | u32 nStrats, u32 poolExtra, nStrats × 58 Byte (s.u.) |
| 13 | Pfade (WFPC&0x200000) | u32 n; je: u32 nsub; je sub: u16 cnt, u16 flags; cnt × {u32,u32,[u32 wenn WFPC&0x4000000],u16 typ,u16} – in keinem Original-Level benutzt |
| 14 | (WFPC&0x10) | u32 (Default 200; Größe eines 0x48-Puffers) |
| 15 | | u16 (→ mapobj+0xc, Bedeutung offen) |
Zellenzuordnung Tile: `col = int(x−0.5)`, `row = −int(z+0.5)`, Index `col + row·W` (verkettete
Liste je Zelle). **Tiles außerhalb 0..W−1 / 0..H−1 schreiben in fremden Speicher.**
### Strat-Eintrag (58 Byte) — `mapchunk.STRAT = '3H3iIIi4Iiih'+'H'`
| Off | Typ | Feld | Konfidenz |
|---|---|---|---|
| 0 | u16 ×3 | Rotation x, y, z (4096=360°; x/z fast immer 0) | hoch |
| 6 | s32 ×3 | Position x, y, z fixed 20.12 (z positiv) | hoch |
| 18 | u32 | **Code-Offset in STPC** (= Objekttyp) | hoch |
| 22 | u32 | Anzahl Parameter n | hoch |
| 26 | s32 | Index in Parameterpool (−1 wenn n=0); Parameter werden auf den Strat-Stack gelegt | hoch |
| 30 | u32 | Anzahl Strat-Variablen (vom Compiler, **typkonstant**) | hoch |
| 34 | u32 | Anzahl 16-Byte-Slots (Kollision/Unterobjekte; typkonstant) | mittel |
| 38 | u32 | Typflags (0x8000/0x80000/0x40000 = Kollisions-/Physikblock; typkonstant) | mittel |
| 42 | u32 | Zusatzspeicher (&0x3fffffff, meist 0..3; typkonstant) | mittel |
| 46 | s32 | Wegpunkt-Index (Block 5) oder −1 | hoch |
| 50 | u32 | immer 0 | – |
| 54 | u16 | Flags; Bit 1 (2) = beim Levelstart nicht erzeugen | hoch |
| 56 | u16 | immer 0 | – |
`poolExtra` (zweites u32 im Strat-Kopf) = max. gleichzeitige Strats − 30 (Engine legt
poolExtra+30 Slots à 0x154 Byte an). **Muss ≥ Anzahl Strats + dynamisch gespawnte sein.**
### Zonen (90 Byte, im Speicher 0x5c) — Konfidenz mittel
u32 cellX, cellY, cellZ, s32 x,y,z (fixed), u32 ?, u32 rot?, u32 flags(+0x20; **Bit 8 = Startzone**
→ DAT_00584644), u32, u32 ptr-Offset in STPC (0=keiner), u32, u32, u32, u16, u32 Nebel nah/fern
(+0x3c/+0x40), u32 ×2 (+0x44/+0x48), u32 Farbe (z. B. 0x404040), u32 Farbe, u16, u16, u32 Musik-ID
(+0x58; FUN_00546b70). Kamera-/Nebel-/Musikbereiche; nicht Lichtquellen (die stehen in LGHT).
## Spieler, Checkpoints, Ausgang
* **Spielerstart** = Position/Rotation der Strat-Instanz **`Llama`** (genau 1× in jedem
Lauf-Level; Typfelder nvars 78, flags 0x248000). Sonderlevel: Fluss (t1l3*) =
`KuscoAndPachaTogetherOnATree`, Achterbahn (t1l7m001/3/5) = `RollerCoaster`,
t1l2m003 = `LlamaChase`.
* **Checkpoints** = `CheckPointPost` (spawnt `CheckPointHead`), Variante `CheckPointBox`.
* **Ausgang** = `LevelDoor` (1×/Level), Sonderfälle `VillageEndDoor`, `EndGameDoor`.
* Immer vorhanden: `CheatTeleport`, `Wompy` (Funktion nicht geprüft), `CutSceneTrigger`.
## Objekttypen
Vollständige Tabelle Typ × Level (Instanzzahlen): `strat_types_table.md`
(erzeugt mit `tools/strat_table.py <wads>`). Kurz:
* in praktisch allen Levels: Coin, CutSceneTrigger, CutSceneCameraDirectional, LevelDoor,
Secret, Llama, CheatTeleport, ChargeBooster, Wompy, CheckPointPost, Coin5, CameraSide, Health,
HealthFull, SmashPot, RubyIdol, Grapes, HealthGenerator, RubyIdolDoor …
* ~300 Typen insgesamt, ~200 weitere nur intern gespawnt (Splitter, Seile, Effekte).
* **Jeder Level hat seinen eigenen kompilierten Strat-Code + Modelle in STPC.** Ein Typ ist
nur in Levels platzierbar, deren STPC ihn enthält (`strats.py level.wad --all` listet alle
benannten Strats im Code, auch nie direkt platzierte). Der Code enthält **absolute
STPC-Offsets** (z. B. Spawn-Stubs `b2 <abs> e0 …`), STPC ist also nicht verschiebbar,
ohne zu relozieren. Typen aus anderen Levels zu übernehmen = STPC (+ TEXT-Indizes)
zusammenführen → aufwendig, vorerst nicht.
* "Spawn-Stub"-Einträge (z. B. SmashPot): MAP zeigt auf einen kleinen Code
`push X; spawn(abs Ziel, nvars, …); end` → `strats.py` meldet `Name(spawn)`.
## Regeln beim Schreiben einer neuen MAP
1. Tile-Blöcke 2/8/9/11 parallel halten (gleiche Anzahl/Reihenfolge); Block 8 aus 2 ableiten;
Block 9 = Feld 5 aus 2; Vertexfarben-Länge exakt = Σ nv(Modell)·Frames (+Alt).
2. Alt-Block: Anzahl = Tiles mit hatAlt≠0, gleiche Reihenfolge. Neue Tiles einfach hinten
anhängen (Strats können Tiles per Index ersetzen – `stMapadd/stMapreplace` – deshalb
bestehende Indizes nicht umsortieren).
3. Alle Tiles in 0..W × 0..H (z negativ!). Regionsraster W·H Einträge.
4. Strat: Code-Offset muss Strat-Anfang in **diesem** STPC sein; nvars/slots/flags/extra
**vom Original-Typ kopieren**; Parameter in Pool anhängen, Index/Anzahl setzen; Wegpunkt
−1 oder gültiger Index (manche Typen brauchen Wegpunkte, sonst "removing strat").
5. poolExtra erhöhen, wenn mehr Strats.
6. WFPC-Flags bestimmen, welche optionalen Blöcke vorhanden sein müssen – nicht ändern.
## Werkzeuge
* `mapchunk.py` – parse/build (Roundtrip identisch für alle 34 MAPs), `vcol_split` je Tile.
* `map_dump.py level.wad [--strats --tiles --lights]`
* `strats.py level.wad [--all]` – Typen mit Instanzzahlen, Code-Offsets, Typfeldern.
* `strat_table.py wads_dir` – Typ×Level-Tabelle.
* `stpcwalk.py` – STPC grob ablaufen (Modelle → Animsets → [WFPC&0x100-Block] → Strat-Code).
* `make_testlevel.py quelle.wad ziel.wad [--name …] [--spacing 3] [--floor-model N] [--keep-original]`
– sucht freien Rasterbereich, legt Bodenplatte aus Kopien des häufigsten flachen
Boden-Tiles (t1l1m001: TRAK-Modell 12) an, stellt je Typ eine Instanz (Parameter vom
Original) im 6er-Raster auf, Spieler an den Rand, Parkour (6 Trittplatten +0.5 mit
Lücken, Münzen) zu einer Zielplattform mit LevelDoor. Ausgelassen: Camera*/CutScene*/
Force*/Trigger. Test (nicht im Spiel geprüft): T1L9M001 → 1753 Tiles, 36 Strats, 27 Typen.
## Andere Chunks (Dispatcher 0x558960)
* WFPC: u32 Feature-Flags (→ 0x6da330), z. B. 0x18410690.
* INFO/VERS: je u32 1 (ignoriert).
* NAME: u32 + Levelname.
* TRAK: Tile-Modelle (u32 n, n×0x84-Header, dann Daten; Header+0x6c u16 nVert, +0x6e nFace(0x1c),
+0x78/7a/7c Anzahl 0x20-Primitive). Face+8 u16 = Index in TEXT-Texturliste.
* STPC: u32 n Modelle (je 0x8c-Header direkt + Daten), u32 n Animsets (0x20-Header + Daten),
[WFPC&0x100: Zusatzmodelle], dann Strat-Code bis Chunkende.
* LGHT: u32 n; je Licht u8 typ, u8 r, u8 g, u8 b (×2), Typ 1/2/…: weitere u32 (Richtung/Position,
z negiert). FUN_0042c180.
* LNFO: 2 u32 (→ 0x5ff070, 0x5ff060), z. B. 55, 2.
* LGPC: u32 n(+1), u32 m, u32; dann (n+1)·m × u32 Größen, danach die Blobs. FUN_00558db0.
* FONT: 256 × 4 u16 (Glyphen-Rechtecke). SPRT: u32 (bei WFPC&0x100000 Liste).
* SMPC / AMPC / SRPC: Soundbänke ("cvg"-Samples / "ambi" Ambient / Referenzen), FUN_00545350.
* 'GRIM' (nicht in Level-WADs gesehen): FUN_00437b40.