translated the remaining german notes, python comments, help and messages to english (in-game strings, parsed report lines and cli values stay german)
This commit is contained in:
1 parent
86fe26d786
commit
526f6072f6
36 files changed
+1553
-1544
No files matched your search
+222
-222
@@ -1,31 +1,31 @@
|
||||
# Levelbau mit `tools/levelbuild.py`
|
||||
# Level building with `tools/levelbuild.py`
|
||||
|
||||
Stand: 2026-11-06 (Ausgang + Parkour überarbeitet). Baut komplette Level-WADs aus einer Level-Beschreibung (Python-Skript).
|
||||
Basis ist immer ein Original-Level (Standard: Dorf `t1l1m001.wad`): dessen Strat-Code/Objektmodelle
|
||||
(STPC), Texturen (TEXT), Sound usw. werden übernommen; **Geometrie (TRAK), Kachelraster,
|
||||
Vertexfarben, Objekte und Wegpunkte (MAP) werden komplett neu erzeugt.**
|
||||
Ergebnis im Spiel geprüft: `out/testwelt.wad` (= installierte Testwelt, siehe unten).
|
||||
As of: 2026-11-06 (exit + parkour reworked). Builds complete level WADs from a level description (Python script).
|
||||
The base is always an original level (default: village `t1l1m001.wad`): its strat code/object models
|
||||
(STPC), textures (TEXT), sound etc. are taken over; **geometry (TRAK), tile grid,
|
||||
vertex colours, objects and waypoints (MAP) are generated completely from scratch.**
|
||||
Result checked in game: `out/testwelt.wad` (= installed test world, see below).
|
||||
|
||||
## Aufruf
|
||||
## Invocation
|
||||
|
||||
```
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/testwelt.wad # bauen
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/testwelt.wad --install # + als TestLevel installieren
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/t.wad --set start=ziel # Variablen ans Skript
|
||||
python3 tools/levelbuild.py --types # platzierbare Strat-Typen des Basislevels
|
||||
python3 tools/levelbuild.py --textures tex.png # Textur-Kontaktabzug + Namensliste
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/testwelt.wad # build
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/testwelt.wad --install # + install as TestLevel
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/t.wad --set start=ziel # pass variables to the script
|
||||
python3 tools/levelbuild.py --types # placeable strat types of the base level
|
||||
python3 tools/levelbuild.py --textures tex.png # texture contact sheet + name list
|
||||
python3 tools/texsheet.py WAD out.png [--all] [--only 1,2,3] [--size 96]
|
||||
```
|
||||
`--install` kopiert nach `~/.wine-lama/mods/testlevel/wads/T1L9M001.wad` (legt einmalig
|
||||
`.orig` an) und legt die Kapitel-Symlinks 2..49 an (s. „Ausgang“). Test im Spiel: `tools/lamatest.py run out/x.wad ordner` (s. testharness.md).
|
||||
`--install` copies to `~/.wine-lama/mods/testlevel/wads/T1L9M001.wad` (creates
|
||||
`.orig` once) and creates the chapter symlinks 2..49 (see "Exit"). Test in game: `tools/lamatest.py run out/x.wad outdir` (see testharness.md).
|
||||
|
||||
## Level-Skript
|
||||
## Level script
|
||||
|
||||
```python
|
||||
LEVEL_ARGS = dict(name='Testwelt', seed=7) # NAME-Chunk, Zufallssaat (Texturdrehung)
|
||||
LEVEL_ARGS = dict(name='Testwelt', seed=7) # NAME chunk, random seed (texture rotation)
|
||||
|
||||
def build(L): # L = levelbuild.Level
|
||||
L.sea(0, 0, 88, 72, y=-2.0, kill=(6, 4, 80, 66)) # Meer + Todesebene
|
||||
L.sea(0, 0, 88, 72, y=-2.0, kill=(6, 4, 80, 66)) # sea + death plane
|
||||
L.box(14, 12, 26, 24, -0.4, 0, top='stein', side='stein')
|
||||
L.stairs(26, 18, 29, 22, 0, 1.5, 3, '+x')
|
||||
L.player(20, 14.5, rot=0)
|
||||
@@ -34,240 +34,240 @@ def build(L): # L = levelbuild.Level
|
||||
L.strat('Bunny', 66, 34)
|
||||
L.coins([(27, 20), (28, 20)])
|
||||
|
||||
def report(L): # optional, nach dem Bauen
|
||||
def report(L): # optional, after building
|
||||
...
|
||||
```
|
||||
`L.opts` enthält die `--set`-Werte (Strings).
|
||||
`L.opts` contains the `--set` values (strings).
|
||||
|
||||
### Koordinaten
|
||||
* Spielkoordinaten wie bei Strats: **x nach Osten, y nach oben, z = Rasterzeile (positiv,
|
||||
"Süden")**, 1 Einheit = 1 Rasterzelle. Raster des Dorfes: 96×96 → alles muss in x,z ∈ [0,96) liegen.
|
||||
* Winkel in **Grad**, 0° = Blick nach +z, 90° = Blick nach +x (im Spiel geprüft).
|
||||
* Intern wird in den Exportraum (z gespiegelt) umgerechnet; Umlaufsinn wird automatisch
|
||||
korrigiert (`_orient`), man gibt nur die gewünschte Außennormale an.
|
||||
### Coordinates
|
||||
* Game coordinates as for strats: **x towards east, y up, z = grid row (positive,
|
||||
"south")**, 1 unit = 1 grid cell. Village grid: 96×96 → everything must lie within x,z ∈ [0,96).
|
||||
* Angles in **degrees**, 0° = facing +z, 90° = facing +x (checked in game).
|
||||
* Internally converted into export space (z mirrored); winding order is corrected
|
||||
automatically (`_orient`), you only specify the desired outward normal.
|
||||
|
||||
### Geometrie-Bausteine
|
||||
| Methode | Beschreibung |
|
||||
### Geometry building blocks
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `floor(x0,z0,x1,z1,y, tex, rot='random', coll=True)` | waagerechte Fläche (in 1×1-Kacheln unterteilt) |
|
||||
| `box(x0,z0,x1,z1,y0,y1, top, side, side_top=None, bottom=None, sides='nesw', side_coll=None)` | Quader; `side_top` = Textur der obersten Wandreihe (z. B. `'graskante'`), `side_coll=False` = Wände ohne Kollision (niedrige Sockel begehbar) |
|
||||
| `platform(x,z,w,d,y, top, side, thick=0.5, side_top='graskante', bottom='erde')` | schwebende Plattform, Oberkante y |
|
||||
| `pillar(x,z,y0,y1,size, side, top)` | Säule um Mittelpunkt |
|
||||
| `ramp(x0,z0,x1,z1, ya, yb, '+x', top, side, base)` | Rampe, steigt in Richtung `+x/-x/+z/-z` |
|
||||
| `stairs(x0,z0,x1,z1, y0, y1, n, '+x', top, side, base, walkable=True)` | Treppe; `walkable`: unsichtbare Schräge als Kollision |
|
||||
| `fence(pts, y, height)` | Zaun (Pfosten + Latte) entlang Punktliste |
|
||||
| `tree(x,z,y, height, crown)` | Klotz-Baum |
|
||||
| `sea(x0,z0,x1,z1, y, tex='meer', kill=(...))` | Wasserfläche ohne Kollision + Todesebene |
|
||||
| `quad([BL,BR,TR,TL], tex, coll, rot)` | einzelnes Viereck (Ecken von vorne gesehen) |
|
||||
| `collider(pts, normal)` | unsichtbare Kollisionsfläche |
|
||||
| `obj(path, offset, scale, tex)` | OBJ-Datei (Spielkoordinaten), `usemtl tex_N` oder Texturname |
|
||||
| `height_at(x,z)` | Höhe der obersten begehbaren Fläche |
|
||||
| `floor(x0,z0,x1,z1,y, tex, rot='random', coll=True)` | horizontal surface (subdivided into 1×1 tiles) |
|
||||
| `box(x0,z0,x1,z1,y0,y1, top, side, side_top=None, bottom=None, sides='nesw', side_coll=None)` | cuboid; `side_top` = texture of the topmost wall row (e.g. `'graskante'`), `side_coll=False` = walls without collision (low plinths walkable) |
|
||||
| `platform(x,z,w,d,y, top, side, thick=0.5, side_top='graskante', bottom='erde')` | floating platform, top edge y |
|
||||
| `pillar(x,z,y0,y1,size, side, top)` | pillar around a centre point |
|
||||
| `ramp(x0,z0,x1,z1, ya, yb, '+x', top, side, base)` | ramp, rises in direction `+x/-x/+z/-z` |
|
||||
| `stairs(x0,z0,x1,z1, y0, y1, n, '+x', top, side, base, walkable=True)` | stairs; `walkable`: invisible slope as collision |
|
||||
| `fence(pts, y, height)` | fence (posts + rail) along a point list |
|
||||
| `tree(x,z,y, height, crown)` | blocky tree |
|
||||
| `sea(x0,z0,x1,z1, y, tex='meer', kill=(...))` | water surface without collision + death plane |
|
||||
| `quad([BL,BR,TR,TL], tex, coll, rot)` | single quad (corners seen from the front) |
|
||||
| `collider(pts, normal)` | invisible collision surface |
|
||||
| `obj(path, offset, scale, tex)` | OBJ file (game coordinates), `usemtl tex_N` or texture name |
|
||||
| `height_at(x,z)` | height of the topmost walkable surface |
|
||||
|
||||
Texturen: Name aus `TEX` (Dorf: `gras, gras2, gras_dunkel, blumen, sand, erde, graskante,
|
||||
Textures: name from `TEX` (village: `gras, gras2, gras_dunkel, blumen, sand, erde, graskante,
|
||||
stein, stein_kante, stein_moos, platte, holz_dunkel, holz_maser, holzbalken, rinde, kiste,
|
||||
inka, muster_lila, …`), direkter Index (int) oder Flachfarbe (`'meer'`, `'rot'`, … oder
|
||||
`(r,g,b)`; wird als Flachfarb-Eintrag (Flags 0x105, Seite −1) an die Texturtabelle angehängt).
|
||||
Kontaktabzug mit Indizes: `notes/img/texturen_dorf.png`.
|
||||
**Jedes Dreieck zeigt eine ganze Texturkachel** (die Engine hat keine freien UVs) – daher wird
|
||||
alles in 1×1-Stücke zerlegt; schmale/flache Wände zeigen die Textur gestaucht.
|
||||
inka, muster_lila, …`), a direct index (int) or a flat colour (`'meer'`, `'rot'`, … or
|
||||
`(r,g,b)`; appended to the texture table as a flat-colour entry (flags 0x105, page −1)).
|
||||
Contact sheet with indices: `notes/img/texturen_dorf.png`.
|
||||
**Every triangle shows a whole texture tile** (the engine has no free UVs) – so
|
||||
everything is split into 1×1 pieces; narrow/flat walls show the texture squashed.
|
||||
|
||||
### Beleuchtung (Vertexfarben)
|
||||
### Lighting (vertex colours)
|
||||
`L.sun = dict(azimuth=215, elevation=50, color=(1,.93,.8), strength=.62, sky=(.5,.55,.66),
|
||||
ground=(.3,.28,.24), ao=.55, ao_radius=1.3, shadow=.35)` – je Vertex:
|
||||
Himmel/Boden-Halbkugellicht + Sonne (Lambert) mit **Schatten** (Strahl zur Sonne gegen alle
|
||||
Quader) + **Umgebungsverdeckung** (24 Strahlen, Reichweite `ao_radius`). Quader, Säulen,
|
||||
Treppen, Plattformen melden sich als Schattenwerfer an (`solids`). 1.0 = Texturfarbe
|
||||
(Vertexfarbe 0x80), max. 2.0. `tint=(r,g,b)` je Baustein multipliziert.
|
||||
ground=(.3,.28,.24), ao=.55, ao_radius=1.3, shadow=.35)` – per vertex:
|
||||
sky/ground hemisphere light + sun (Lambert) with **shadows** (ray towards the sun against all
|
||||
cuboids) + **ambient occlusion** (24 rays, range `ao_radius`). Cuboids, pillars,
|
||||
stairs, platforms register as shadow casters (`solids`). 1.0 = texture colour
|
||||
(vertex colour 0x80), max. 2.0. `tint=(r,g,b)` multiplies per building block.
|
||||
|
||||
### Objekte (Strats)
|
||||
### Objects (strats)
|
||||
`L.strat(name, x, z, y=None, rot=None, params=None, wp='relative', variant=0, hidden=False)`
|
||||
* `y=None`: auf die oberste begehbare Fläche bei (x,z) stellen.
|
||||
* Typfelder (nvars, Slots, Flags, Zusatzspeicher) und Parameter werden von der **ersten
|
||||
Original-Instanz** des Typs kopiert (`params=[…]` überschreibt; Anzahl muss passen).
|
||||
* `variant`: Typen mit mehreren Code-Einstiegen (z. B. SmashPot: 2 Spawn-Stubs).
|
||||
* **Wegpunkte** (`wp`): `'relative'` = Wegpunktkette des Originals mitverschieben (Versatz zum
|
||||
Objekt bleibt, y wird auf den Boden gesetzt, wenn dort einer ist), `'here'` = alle Punkte auf
|
||||
das Objekt, `'none'` = keine (−1), oder eigene Liste `[(x,z[,y[,grad]]), …]` (Zusatzfelder
|
||||
der Original-Wegpunkte werden übernommen). Ketten werden neu nummeriert (keine Verweise ins
|
||||
alte Level).
|
||||
* `player(x,z,rot)` (Strat `Llama`), `checkpoint(x,z)` (`CheckPointPost`), `exit(x,z)`
|
||||
* `y=None`: place on the topmost walkable surface at (x,z).
|
||||
* Type fields (nvars, slots, flags, extra memory) and parameters are copied from the **first
|
||||
original instance** of the type (`params=[…]` overrides; count must match).
|
||||
* `variant`: types with several code entry points (e.g. SmashPot: 2 spawn stubs).
|
||||
* **Waypoints** (`wp`): `'relative'` = move the original's waypoint chain along (offset to the
|
||||
object is kept, y is set to the floor if there is one), `'here'` = all points on
|
||||
the object, `'none'` = none (−1), or your own list `[(x,z[,y[,deg]]), …]` (extra fields
|
||||
of the original waypoints are taken over). Chains are renumbered (no references into the
|
||||
old level).
|
||||
* `player(x,z,rot)` (strat `Llama`), `checkpoint(x,z)` (`CheckPointPost`), `exit(x,z)`
|
||||
(`LevelDoor`), `coins([(x,z),…])`.
|
||||
* Typen, die im Dorf-Code stehen, aber dort nicht platziert sind, über `Strats.BORROWED` mit Typfeldern
|
||||
aus einem anderen Level: derzeit `RubyIdol` (aus t1l1m002).
|
||||
* `poolExtra` wird auf max(Original, Anzahl Strats + 80) gesetzt; der Parameterpool des
|
||||
Originals bleibt erhalten, neue Parameter werden angehängt.
|
||||
* Types that are in the village code but not placed there, via `Strats.BORROWED` with type fields
|
||||
from another level: currently `RubyIdol` (from t1l1m002).
|
||||
* `poolExtra` is set to max(original, number of strats + 80); the original's parameter pool
|
||||
is kept, new parameters are appended.
|
||||
|
||||
### Was `build()` erzeugt
|
||||
1. Alle Dreiecke werden nach (Rasterzelle, Höhenband 10 Einheiten ab y = −3) auf Kacheln
|
||||
verteilt; Kachelursprung = Zellmitte/Bandmitte (Kollisions-d muss in ±8 liegen).
|
||||
2. **Kollision**: jedes Viereck mit `coll=True` wird ein Kollisionspolygon; Böden nur in der
|
||||
eigenen Zelle, Wände/Schrägen zusätzlich in Nachbarzellen bis 0,3 Abstand (die Engine prüft
|
||||
nur die Kacheln der Zelle, in der der Spieler steht). Unbenutzte Hilfsvertices sorgen dafür,
|
||||
dass die Kachel-Box die Kollisionspolygone umfasst.
|
||||
3. Identische Kachelmodelle werden zusammengefasst (Vertexfarben stehen in MAP je Kachel).
|
||||
4. **Todesebene**: Original-Modell 0 des Dorfes (unsichtbar, Material 0x1e) in jeder Zelle
|
||||
des `kill`-Bereichs, die nicht vollständig von Boden bedeckt ist.
|
||||
5. MAP: Zonenraster = 0, Zone 0 sieht alles (Maske 0xffffffff), keine Kachel-Wechsel (Alt-Block
|
||||
leer), Kachelblöcke A/B/Modelle/Vertexfarben konsistent, Strats + Wegpunkte neu, Licht-/
|
||||
Umgebungszonen (90-Byte-Block) und Rest unverändert.
|
||||
6. TEXT: angehängte Flachfarben. NAME: `LEVEL_ARGS['name']`.
|
||||
7. Gegenprobe: MAP wird nach dem Schreiben wieder vollständig geparst.
|
||||
### What `build()` generates
|
||||
1. All triangles are distributed onto tiles by (grid cell, height band of 10 units from y = −3);
|
||||
tile origin = cell centre/band centre (collision d must be within ±8).
|
||||
2. **Collision**: every quad with `coll=True` becomes a collision polygon; floors only in their
|
||||
own cell, walls/slopes additionally in neighbouring cells up to 0.3 distance (the engine only checks
|
||||
the tiles of the cell the player stands in). Unused helper vertices make sure
|
||||
the tile box encloses the collision polygons.
|
||||
3. Identical tile models are merged (vertex colours are stored per tile in MAP).
|
||||
4. **Death plane**: original model 0 of the village (invisible, material 0x1e) in every cell
|
||||
of the `kill` area that is not completely covered by floor.
|
||||
5. MAP: zone grid = 0, zone 0 sees everything (mask 0xffffffff), no tile swaps (alt block
|
||||
empty), tile blocks A/B/models/vertex colours consistent, strats + waypoints new, light/
|
||||
environment zones (90-byte block) and rest unchanged.
|
||||
6. TEXT: appended flat colours. NAME: `LEVEL_ARGS['name']`.
|
||||
7. Cross-check: MAP is fully parsed again after writing.
|
||||
|
||||
## Erkenntnisse aus den Spieltests (lamatest, Xvfb)
|
||||
* **Maßstab**: Lama ≈ 0,6 Einheiten hoch, läuft ≈ 1,1 Einheiten/s (Münz-Messbahn, s. u.; die
|
||||
früher notierten 0,85 waren zu niedrig), braucht ~1 s zum Beschleunigen. Sprung (X) überwindet
|
||||
0,35 hohe Kanten, 0,5 nicht. **Mit Anlauf fliegt es ~1,3 weit** (horizontal gleich schnell wie
|
||||
beim Laufen), auf +0,2 landet es nach ~1,05; aus dem Stand deutlich kürzer (0,5-Lücke nicht
|
||||
geschafft). → Parkour: Lücken 0,3 mit +0,2, Plattformen davor/dahinter ≥ 1,5 lang (nach der
|
||||
Landung muss Platz für den nächsten Absprung sein), größere Höhen über Treppen/Rampen.
|
||||
Messlevel: `levels/sprunglabor.py` (`--set spur=a..g|m`; `m` = Münz-Messbahn: Münze alle 0,25 →
|
||||
Münzzähler × 0,25 = gelaufene Strecke; `f` = Weitsprung über Wasser).
|
||||
* **Keine Stufen-Logik**: schon eine 0,12 hohe Kante *mit Wandkollision* blockiert das Lama.
|
||||
Ohne Wandkollision (`side_coll=False`) läuft es auf niedrige Sockel hinauf; Treppen mit
|
||||
unsichtbarer Schräge (`stairs(walkable=True)`) und Rampen sind begehbar (geprüft).
|
||||
* **Steuerung ist kamerarelativ** (Hoch = Kamerablickrichtung). Die Startkamera steht hinter
|
||||
dem Lama; steht direkt dahinter ein Hindernis (z. B. Pfosten), weicht sie seitlich aus →
|
||||
Startpunkte hinten freihalten.
|
||||
* Rot 0° = Blick +z, 90° = Blick +x.
|
||||
* **Todesebene funktioniert**: Sturz ins Meer → Lebensenergie weg, Respawn am letzten
|
||||
Checkpoint (bzw. Start).
|
||||
* Spieler-Wegpunktkette (14 Punkte beim Dorf-Lama, Bedeutung unklar): `player()` legt den
|
||||
ersten 8 Einheiten hinter das Lama, den Rest auf das Lama, Zusatzfelder vom Original.
|
||||
* **LevelDoor** (Ausgang) – gelöst, siehe Abschnitt „Ausgang (LevelDoor)“ unten.
|
||||
* Größen, die laufen: TRAK 0,6–1,0 MB, bis 1100 Modelle, 5200 Kacheln, WAD bis 6,9 MB
|
||||
(Originale: TRAK ≤ 1 MB, ≤ 5800 Kacheln, WAD ≤ 6,85 MB). Testwelt: 628 Modelle,
|
||||
2474 Kacheln, TRAK 646 KB, WAD 6,3 MB.
|
||||
## Findings from the game tests (lamatest, Xvfb)
|
||||
* **Scale**: llama ≈ 0.6 units tall, walks ≈ 1.1 units/s (coin measuring track, see below; the
|
||||
0.85 noted earlier was too low), needs ~1 s to accelerate. A jump (X) clears
|
||||
0.35 high edges, 0.5 not. **With a run-up it flies ~1.3 far** (horizontally as fast as
|
||||
when walking), onto +0.2 it lands after ~1.05; from standing much shorter (0.5 gap not
|
||||
cleared). → Parkour: gaps 0.3 with +0.2, platforms before/after ≥ 1.5 long (after
|
||||
landing there must be room for the next take-off), larger heights via stairs/ramps.
|
||||
Measuring level: `levels/sprunglabor.py` (`--set spur=a..g|m`; `m` = coin measuring track: a coin every 0.25 →
|
||||
coin counter × 0.25 = distance walked; `f` = long jump over water).
|
||||
* **No step logic**: even a 0.12 high edge *with wall collision* blocks the llama.
|
||||
Without wall collision (`side_coll=False`) it walks up onto low plinths; stairs with
|
||||
an invisible slope (`stairs(walkable=True)`) and ramps are walkable (checked).
|
||||
* **Controls are camera-relative** (Up = camera view direction). The start camera is behind
|
||||
the llama; if there is an obstacle right behind it (e.g. a post), it moves aside →
|
||||
keep the area behind start points clear.
|
||||
* Rot 0° = facing +z, 90° = facing +x.
|
||||
* **Death plane works**: falling into the sea → health gone, respawn at the last
|
||||
checkpoint (or start).
|
||||
* Player waypoint chain (14 points for the village llama, meaning unclear): `player()` puts the
|
||||
first one 8 units behind the llama, the rest on the llama, extra fields from the original.
|
||||
* **LevelDoor** (exit) – solved, see section "Exit (LevelDoor)" below.
|
||||
* Sizes that work: TRAK 0.6–1.0 MB, up to 1100 models, 5200 tiles, WAD up to 6.9 MB
|
||||
(originals: TRAK ≤ 1 MB, ≤ 5800 tiles, WAD ≤ 6.85 MB). Test world: 628 models,
|
||||
2474 tiles, TRAK 646 KB, WAD 6.3 MB.
|
||||
|
||||
## Ausgang (LevelDoor) – wie er funktioniert
|
||||
## Exit (LevelDoor) – how it works
|
||||
|
||||
Strat-Bytecode (Dorf, STPC 0x430332, 3 Parameter, unsichtbar, kein Modell), dekodiert über die
|
||||
VM `FUN_0054d180` (Opcode < 0x45: Tabelle 0x5796bc mit 16-Bit-Operand, sonst 0x578ff4+op·4):
|
||||
Strat bytecode (village, STPC 0x430332, 3 parameters, invisible, no model), decoded via the
|
||||
VM `FUN_0054d180` (opcode < 0x45: table 0x5796bc with 16-bit operand, otherwise 0x578ff4+op·4):
|
||||
```
|
||||
g65,g66,g67 = Param 0,1,2 ; Stamm, Welt, Kapitel des nächsten Levels (20.12)
|
||||
loop: if g21 == 3 ; Spielzustand "läuft"
|
||||
and Spieler.Boden.Material == 16 ; Eigenschaft 0x11 = *(strat+0xc0)+4 = Material-Byte des
|
||||
; Kollisionspolygons unter dem Spieler (FUN_00403020)
|
||||
and floor(Spieler.x) == floor(Tür.x) and floor(Spieler.z) == floor(Tür.z) ; gleiche Rasterzelle
|
||||
-> op 0x192 (FUN_0054d620): Levelwechsel(Param), g1 += 1, Ende
|
||||
g65,g66,g67 = param 0,1,2 ; tribe, world, chapter of the next level (20.12)
|
||||
loop: if g21 == 3 ; game state "running"
|
||||
and player.floor.material == 16 ; property 0x11 = *(strat+0xc0)+4 = material byte of the
|
||||
; collision polygon below the player (FUN_00403020)
|
||||
and floor(player.x) == floor(door.x) and floor(player.z) == floor(door.z) ; same grid cell
|
||||
-> op 0x192 (FUN_0054d620): level change(param), g1 += 1, end
|
||||
yield
|
||||
```
|
||||
* Die Tür ist also nur ein Auslöser; im Dorf liegt im Türdurchgang ein Bodenpolygon mit
|
||||
**Material 16** (8 Stück im Dorf, auch in allen anderen Levels). Unsere Testwelt hatte überall
|
||||
Material 0 → nichts passierte. **Fix:** Mosaik im Tor mit `mat=16` (`box(..., mat=TUER_MAT)`),
|
||||
Tür in dieselbe Rasterzelle, Spielerweg führt mitten hindurch.
|
||||
* **Nur Kacheln der Türzelle zählen** (Zellliste, FUN_004036d0), die Höhe nicht (t1l3m002–4: Boden
|
||||
3 Einheiten unter der Tür). **Dorf 1 & Co.:** In der Türzelle liegt zunächst *kein* Material 16 –
|
||||
es steckt im **Wechselmodell** der Kachel (MAP-Kachel-Wechsel, WFPC 0x10000): die RubyIdolDoor in
|
||||
derselben Zelle tauscht beim Öffnen (Op 0x11a/0x11b) Kachel 420 von Modell 48 auf 49, und erst
|
||||
Modell 49 hat das Ausgangspolster (42,25–42,75 / 49,23–49,50, y 3,25). Im Spiel geprüft
|
||||
(2026-11-07): Modell 49 eingesetzt + Tür entfernt, Lama bei 42,5/3,5/49,37 → sofort Bilanz
|
||||
„Dorf Kapitel 1“; Original (Tür zu) → nichts. Betrifft 8 der 31 Original-Türen (Ruby-,
|
||||
PressurePad-, Lab-Türen); das Polster bei 43,5/48,45 (Zelle 43,48) ist dagegen ein Hauseingang
|
||||
(Zonen-Teleport). t1l3m003: 2 der 4 Türen (Zellen 71,48/71,49) haben gar kein Polster.
|
||||
* Zweiter Weg (Strat-Code bei 0x4252aa, Spielerlogik [wahrsch.]): Boden-Material 16 und Zelle =
|
||||
g104/g105 (setzt die LevelDoor beim Start, Llama setzt sie auf −1) → Levelwechsel(g101..g103).
|
||||
* Parameter in den Originalen: (1, Welt, Kapitel+1) = nächstes Kapitel; im letzten Kapitel einer
|
||||
Welt **(7,7,7)** = „Welt geschafft“ → zurück zur Auswahlkarte, nächste Welt freischalten.
|
||||
* `FUN_0054d620`: merkt aktuelles Level (0x585008..14), setzt nächstes (0x584eb4/b8/bc), zeigt die
|
||||
Bilanz (Münzen x/55, Geheimnisse) mit „EINGABETASTE = speichern / ESC = weiter“. Fortschritt
|
||||
0x585018/19 (max. Welt/Kapitel, im KSV +0x42/+0x43): ist man im höchsten freien Kapitel, wird
|
||||
das nächste Kapitel freigeschaltet; bei (7,7,7) die nächste Welt.
|
||||
* **(7,7,7) in Welt 9 stürzt ab**: schaltet „Welt 10“ frei → nach dem Speichern steht der
|
||||
Kartencursor auf Welt 10, Weltnamen-Zeiger 0x57bd40[9] = NULL → Page Fault bei 0x41B5CC.
|
||||
* **Lösung Testwelt: Parameter (1,9,1)** – nach der Bilanz wird Kapitel 1 neu geladen (kein
|
||||
Absturz, getestet mit Speichern). Nebenwirkung: Kapitel 2 wird freigeschaltet (und von
|
||||
Kapitel n aus Kapitel n+1). Fehlt dessen WAD, stürzt die Auswahl ab („Couldnt load wad“).
|
||||
Deshalb legt `levelbuild.py --install` (Funktion `kapitel_links()`) in der Mod
|
||||
**Symlinks T1L9M002..049.wad → T1L9M001.wad und T192..T1949.COR → T191.COR** an (Welt 9 hat
|
||||
49 Kapitel-Plätze, Tabelle 0x572420) und ruft `lama-mod an` auf. `lama-mod` übernimmt dafür jetzt
|
||||
auch Symlinks aus Mod-Ordnern (`find … -type f -o -type l`). Geprüft: Kapitel 2 auf der Karte
|
||||
wählbar, lädt die Testwelt.
|
||||
* Die Auswahlkarte zeigt für Welt 9 keinen Hintergrund (nur Text) und 772 als Münzziel
|
||||
(Münztabelle 0x5722f4 hat nur Welten 1–8) – kosmetisch.
|
||||
* So the door is only a trigger; in the village there is a floor polygon with
|
||||
**material 16** in the doorway (8 of them in the village, also in all other levels). Our test world had
|
||||
material 0 everywhere → nothing happened. **Fix:** mosaic in the gate with `mat=16` (`box(..., mat=TUER_MAT)`),
|
||||
door in the same grid cell, the player's path leads right through it.
|
||||
* **Only tiles of the door cell count** (cell list, FUN_004036d0), not the height (t1l3m002–4: floor
|
||||
3 units below the door). **Village 1 & co.:** the door cell initially contains *no* material 16 –
|
||||
it is in the **swap model** of the tile (MAP tile swap, WFPC 0x10000): the RubyIdolDoor in
|
||||
the same cell swaps tile 420 from model 48 to 49 when opening (op 0x11a/0x11b), and only
|
||||
model 49 has the exit pad (42.25–42.75 / 49.23–49.50, y 3.25). Checked in game
|
||||
(2026-11-07): model 49 inserted + door removed, llama at 42.5/3.5/49.37 → immediately the summary
|
||||
"Dorf Kapitel 1"; original (door closed) → nothing. Affects 8 of the 31 original doors (Ruby,
|
||||
PressurePad, Lab doors); the pad at 43.5/48.45 (cell 43,48), on the other hand, is a house entrance
|
||||
(zone teleport). t1l3m003: 2 of the 4 doors (cells 71,48/71,49) have no pad at all.
|
||||
* Second path (strat code at 0x4252aa, player logic [likely]): floor material 16 and cell =
|
||||
g104/g105 (set by the LevelDoor at start, Llama sets them to −1) → level change(g101..g103).
|
||||
* Parameters in the originals: (1, world, chapter+1) = next chapter; in the last chapter of a
|
||||
world **(7,7,7)** = "world completed" → back to the selection map, unlock the next world.
|
||||
* `FUN_0054d620`: remembers the current level (0x585008..14), sets the next (0x584eb4/b8/bc), shows the
|
||||
summary (coins x/55, secrets) with "EINGABETASTE = speichern / ESC = weiter" (Enter = save / Esc = continue). Progress
|
||||
0x585018/19 (max. world/chapter, in KSV +0x42/+0x43): if you are in the highest unlocked chapter,
|
||||
the next chapter is unlocked; with (7,7,7) the next world.
|
||||
* **(7,7,7) in world 9 crashes**: unlocks "world 10" → after saving, the
|
||||
map cursor is on world 10, world name pointer 0x57bd40[9] = NULL → page fault at 0x41B5CC.
|
||||
* **Solution for the test world: parameters (1,9,1)** – after the summary chapter 1 is reloaded (no
|
||||
crash, tested with saving). Side effect: chapter 2 is unlocked (and from
|
||||
chapter n, chapter n+1). If its WAD is missing, the selection crashes ("Couldnt load wad").
|
||||
That is why `levelbuild.py --install` (function `kapitel_links()`) creates in the mod
|
||||
**symlinks T1L9M002..049.wad → T1L9M001.wad and T192..T1949.COR → T191.COR** (world 9 has
|
||||
49 chapter slots, table 0x572420) and calls `lama-mod an`. For this, `lama-mod` now also takes
|
||||
symlinks from mod folders (`find … -type f -o -type l`). Checked: chapter 2 on the map is
|
||||
selectable and loads the test world.
|
||||
* The selection map shows no background for world 9 (only text) and 772 as the coin target
|
||||
(coin table 0x5722f4 only has worlds 1–8) – cosmetic.
|
||||
|
||||
## Die Testwelt (`levels/testwelt.py` → `out/testwelt.wad`, installiert als T1L9M001.wad)
|
||||
**2026-11-06 kompakt umgebaut** („alles näher zusammen“): L-förmige Insel, Hauptteil 32,5×23,5
|
||||
(x 11..43,5, z 9,5..33) + Streifen unter dem Parkour-Ende (x 43,5..49, z 9,5..20,5), vorher
|
||||
42×34 (x 10..52, z 8..42). Galerie, Gehege und Parkour grenzen direkt aneinander, keine leeren
|
||||
Wiesen mehr. NAME-Chunk "Testwelt" (wird im Spiel nirgends angezeigt).
|
||||
Vorschaubilder (Handy, 1280 px): `notes/img/vorschau/` (01–03 Blender-Renderings mit
|
||||
Beschriftung, 04–11 Spielfotos, 12 Montage). Ältere Bilder `notes/img/testwelt_*.jpg` zeigen
|
||||
noch das alte, weitläufige Layout.
|
||||
## The test world (`levels/testwelt.py` → `out/testwelt.wad`, installed as T1L9M001.wad)
|
||||
**Rebuilt compactly on 2026-11-06** ("everything closer together"): L-shaped island, main part 32.5×23.5
|
||||
(x 11..43.5, z 9.5..33) + strip below the parkour end (x 43.5..49, z 9.5..20.5), previously
|
||||
42×34 (x 10..52, z 8..42). Gallery, enclosure and parkour adjoin each other directly, no empty
|
||||
meadows any more. NAME chunk "Testwelt" (not shown anywhere in the game).
|
||||
Preview images (phone, 1280 px): `notes/img/vorschau/` (01–03 Blender renderings with
|
||||
labels, 04–11 game shots, 12 montage). Older images `notes/img/testwelt_*.jpg` still show
|
||||
the old, sprawling layout.
|
||||
|
||||
| Bereich | alt | neu |
|
||||
| Area | old | new |
|
||||
|---|---|---|
|
||||
| Startplatz | x 14..22, z 11..19 (8×8) | x 15..21, z 12..19 (6×7) |
|
||||
| Galerie | z 22..38, Spalten 2,5, Reihen z 25/28,5/32/35,5 | x 14,5..32,5, z 20,5..32, Spalten 1,9, Reihen z 22,5/25,2/27,9/30,6 (Abstand 2,7) |
|
||||
| Gehege | x 39..49, z 24..35 | x 33..42, z 20,8..30 (direkt östlich der Galerie) |
|
||||
| Parkour | x 22..53,6 | x 21..52,6 (nur um −1 in x verschoben, Maße gleich) |
|
||||
| Ausgang | LevelDoor (51,5/15,5) | LevelDoor (50,5/15,5), Zelle 50/15, Mosaik mit Material 16 |
|
||||
| Start square | x 14..22, z 11..19 (8×8) | x 15..21, z 12..19 (6×7) |
|
||||
| Gallery | z 22..38, columns 2.5, rows z 25/28.5/32/35.5 | x 14.5..32.5, z 20.5..32, columns 1.9, rows z 22.5/25.2/27.9/30.6 (spacing 2.7) |
|
||||
| Enclosure | x 39..49, z 24..35 | x 33..42, z 20.8..30 (directly east of the gallery) |
|
||||
| Parkour | x 22..53.6 | x 21..52.6 (only shifted by −1 in x, same dimensions) |
|
||||
| Exit | LevelDoor (51.5/15.5) | LevelDoor (50.5/15.5), cell 50/15, mosaic with material 16 |
|
||||
|
||||
* **Startplatz** (x 15..21, z 12..19): Steinplatz mit Inka-Mosaik, 4 Inka-Säulen, Blumenbeete,
|
||||
Bäume, 2 Münzen, Checkpoint (16,1/17,4). Lama startet bei (18, 14,6) mit Blick nach Süden
|
||||
(Galerie direkt dahinter, Parkour-Treppe links im Bild).
|
||||
* **Galerie** (z 20,5..32): 4 Reihen, je Objekt ein flacher Sockel (0,9 breit, 0,08 hoch,
|
||||
begehbar), Lücke 1,0 zwischen Sockeln, Sandweg (0,6) vor jeder Reihe, farbiger Pfosten am
|
||||
Reihenanfang (gelb/grau/blau/rot), Hauptweg Nord–Süd bei x 15,6..16,3:
|
||||
1. Sammeln: Coin, ChargeBooster, RubyIdol, HealthFull, HealthGenerator, ChacaChargePotion, Secret
|
||||
2. Kisten/Töpfe: SmashPot (2 Varianten), Jar, ObjectDontDropInBox, WaterReboundPoints, CheatTeleport
|
||||
3. Figuren: TipoVillage, ChacaVillage, PachaGivingInfo, KidOnLlama, Wompy
|
||||
4. Mechanik: PressurePadDoor, RubyIdolDoor, SwitchPlatformPadVillage, AppearingPlatformsVillage,
|
||||
* **Start square** (x 15..21, z 12..19): stone square with Inca mosaic, 4 Inca pillars, flower beds,
|
||||
trees, 2 coins, checkpoint (16.1/17.4). The llama starts at (18, 14.6) facing south
|
||||
(gallery directly behind it, parkour stairs on the left of the picture).
|
||||
* **Gallery** (z 20.5..32): 4 rows, each object on a flat plinth (0.9 wide, 0.08 high,
|
||||
walkable), gap 1.0 between plinths, sand path (0.6) in front of each row, coloured post at the
|
||||
start of the row (yellow/grey/blue/red), main path north–south at x 15.6..16.3:
|
||||
1. Collectables: Coin, ChargeBooster, RubyIdol, HealthFull, HealthGenerator, ChacaChargePotion, Secret
|
||||
2. Crates/pots: SmashPot (2 variants), Jar, ObjectDontDropInBox, WaterReboundPoints, CheatTeleport
|
||||
3. Characters: TipoVillage, ChacaVillage, PachaGivingInfo, KidOnLlama, Wompy
|
||||
4. Mechanics: PressurePadDoor, RubyIdolDoor, SwitchPlatformPadVillage, AppearingPlatformsVillage,
|
||||
RisingColumns, SinkingStatue, FallingTree, CheckPointPost
|
||||
* **Gehege** (x 33..42, z 20,8..30, Zaun, Eingang Westseite z 20,8..24): Bunny ×2, Ladybird,
|
||||
FlockOfBirds, VillagerHerder; Aussichtspodest (0,6) mit Treppe an der Westseite zwischen
|
||||
Galerie-Reihe 2 und 3.
|
||||
* **Parkour** (beginnt an der Ostkante des Platzes, **alles auf der Linie z = 15,5 nach Osten**,
|
||||
damit man ohne Kameradrehen durchkommt – Drehen per A/S oder Links/Rechts war im Skript nicht
|
||||
reproduzierbar). Geometrie im Skript als „alte x + PX“ (PX = −1) geschrieben:
|
||||
Treppe 0→0,8 · **Anschlag-Stufe** +0,3 (ohne Lücke: Lama läuft dagegen und steht still) ·
|
||||
2 Lücken 0,3 mit je +0,2 auf 1,5 lange Holzklötze · Balken 0,5 breit · Mittelplattform (1,5) mit
|
||||
Checkpoint · Sandrampe 1,5→2,4 · **Anschlag-Stufe** +0,3 · 3 Trittsteine (1,5 lang, Lücken 0,3,
|
||||
je +0,2) · schmaler Balken 0,45 · Treppe 3,3→4,0 · Ziel-Plattform (4,0; x 47,6..52,6, ragt über
|
||||
die Küste) mit Säulen, Brustwehr und Tor; darunter Mosaik mit Material 16, LevelDoor bei
|
||||
(50,5 / 15,5). 18 Münzen entlang. Unter dem Parkour liegt Wiese (Herunterfallen ist harmlos).
|
||||
* **Skript-Durchlauf** (Start → Ziel → Ausgang → Bilanz), alte Fassung 4/4, kompakte Fassung 2/2
|
||||
Läufe erfolgreich (Bilanz 18/55 Münzen); die .seq ist zeitbasiert/relativ und blieb unverändert:
|
||||
* **Enclosure** (x 33..42, z 20.8..30, fence, entrance on the west side z 20.8..24): Bunny ×2, Ladybird,
|
||||
FlockOfBirds, VillagerHerder; viewing pedestal (0.6) with stairs on the west side between
|
||||
gallery rows 2 and 3.
|
||||
* **Parkour** (starts at the east edge of the square, **everything on the line z = 15.5 going east**,
|
||||
so it can be done without rotating the camera – rotating via A/S or Left/Right was not
|
||||
reproducible in scripts). Geometry is written in the script as "old x + PX" (PX = −1):
|
||||
stairs 0→0.8 · **stop step** +0.3 (no gap: the llama runs into it and stands still) ·
|
||||
2 gaps of 0.3 with +0.2 each onto 1.5 long wooden blocks · beam 0.5 wide · middle platform (1.5) with
|
||||
checkpoint · sand ramp 1.5→2.4 · **stop step** +0.3 · 3 stepping stones (1.5 long, gaps 0.3,
|
||||
+0.2 each) · narrow beam 0.45 · stairs 3.3→4.0 · goal platform (4.0; x 47.6..52.6, juts out over
|
||||
the coast) with pillars, parapet and gate; below it a mosaic with material 16, LevelDoor at
|
||||
(50.5 / 15.5). 18 coins along the way. Below the parkour there is meadow (falling down is harmless).
|
||||
* **Scripted run** (start → goal → exit → summary), old version 4/4, compact version 2/2
|
||||
runs successful (summary 18/55 coins); the .seq is time-based/relative and stayed unchanged:
|
||||
```
|
||||
python3 tools/levelbuild.py levels/testwelt.py out/testwelt_parkour.wad --set start=parkour
|
||||
lamatest.py run out/testwelt_parkour.wad AUSGABE --seq "$(cat levels/testwelt_parkour.seq)"
|
||||
lamatest.py run out/testwelt_parkour.wad OUTDIR --seq "$(cat levels/testwelt_parkour.seq)"
|
||||
```
|
||||
(`run:T:x@t,…` hält Up T s und drückt X zu festen Zeiten; die Anschlag-Stufen setzen den
|
||||
Zeitfehler zurück. Fotos (`shot@`) blockieren ~0,4 s → nie kurz vor einem Sprung.)
|
||||
* Im Spiel geprüft: Laden ohne Absturz (viele Läufe), alle 31 Galerie-Objekte erscheinen
|
||||
(Münze/Secret/… reagieren: Münzzähler, "GEHEIMNIS!"), Lama läuft Galerie/Gehege ab,
|
||||
Treppe hoch, Sprung auf Plattform B und auf Trittstein 1, Rampe hoch, Treppe zur
|
||||
Ziel-Plattform, Meer = Respawn, kompletter Parkour per Skript, Ausgang → Bilanz → Kapitel 1 neu.
|
||||
Bilder: aktuell `notes/img/vorschau/` (kompaktes Layout); `notes/img/testwelt_*.jpg` =
|
||||
altes weitläufiges Layout (1–6 + Montage sogar noch mit altem Parkour).
|
||||
* Startpunkt für Tests umschaltbar: `--set start=platz|reihe1..4|weg1..4|gehege|podest|parkour|pk1..pk5|ziel|ziel2`
|
||||
(`podest` = auf dem Aussichtspodest, Blick nach Westen über die Galerie);
|
||||
Ausgangsparameter `--set tuer=1,9,1`.
|
||||
(`run:T:x@t,…` holds Up for T s and presses X at fixed times; the stop steps reset the
|
||||
timing error. Shots (`shot@`) block ~0.4 s → never shortly before a jump.)
|
||||
* Checked in game: loading without a crash (many runs), all 31 gallery objects appear
|
||||
(coin/secret/… react: coin counter, "GEHEIMNIS!"), the llama walks through gallery/enclosure,
|
||||
up the stairs, jumps onto platform B and onto stepping stone 1, up the ramp, stairs to the
|
||||
goal platform, sea = respawn, complete parkour by script, exit → summary → chapter 1 again.
|
||||
Images: current `notes/img/vorschau/` (compact layout); `notes/img/testwelt_*.jpg` =
|
||||
old sprawling layout (1–6 + montage even still with the old parkour).
|
||||
* Start point for tests is switchable: `--set start=platz|reihe1..4|weg1..4|gehege|podest|parkour|pk1..pk5|ziel|ziel2`
|
||||
(`podest` = on the viewing pedestal, looking west over the gallery);
|
||||
exit parameters `--set tuer=1,9,1`.
|
||||
|
||||
### Nicht platzierte Dorf-Typen
|
||||
| Typ | Grund |
|
||||
### Village types not placed
|
||||
| Type | Reason |
|
||||
|---|---|
|
||||
| Llama | ist der Spieler (Start) |
|
||||
| LevelDoor | Ausgang am Parkour-Ende (braucht Boden mit Material 16, s. o.) |
|
||||
| CameraSide | Kamera-Zone, unsichtbar |
|
||||
| CutSceneCameraDirectional | Zwischensequenz-Kamera, unsichtbar |
|
||||
| CutSceneTrigger | startet Zwischensequenz |
|
||||
Kein platzierbarer Typ hat das Spiel zum Absturz gebracht (keine Bisektion nötig).
|
||||
Hinweis: Mechanik-Objekte (Türen, Säulen, Statue) gehören im Dorf zu bestimmter Geometrie /
|
||||
Kachel-Wechseln; in der Galerie stehen sie "nackt" (z. B. schwebendes RubyIdolDoor-Schild).
|
||||
Typen, die nur im Code stehen (BouncingCoin, Sparkles, RubyIdolStatue …), haben keine
|
||||
Original-Instanz mit Typfeldern und werden nicht angeboten (Ausnahme RubyIdol über `Strats.BORROWED`).
|
||||
| Llama | is the player (start) |
|
||||
| LevelDoor | exit at the end of the parkour (needs a floor with material 16, see above) |
|
||||
| CameraSide | camera zone, invisible |
|
||||
| CutSceneCameraDirectional | cutscene camera, invisible |
|
||||
| CutSceneTrigger | starts a cutscene |
|
||||
No placeable type crashed the game (no bisection needed).
|
||||
Note: mechanics objects (doors, pillars, statue) belong to specific geometry /
|
||||
tile swaps in the village; in the gallery they stand "bare" (e.g. a floating RubyIdolDoor sign).
|
||||
Types that only exist in the code (BouncingCoin, Sparkles, RubyIdolStatue …) have no
|
||||
original instance with type fields and are not offered (exception: RubyIdol via `Strats.BORROWED`).
|
||||
|
||||
## Vorschau-Renderings (Blender)
|
||||
`/usr/bin/blender` (Debian 3.0.1): **Cycles rendert nur Schwarz** (auch die Standardszene), OCIO
|
||||
fehlt („Color management: fallback“, nur View „Standard“). **Eevee geht**, braucht aber ein
|
||||
X-Display → eigenes `Xvfb :7` (nie :0, :5 gehört lamatest). Ablauf (Skripte in
|
||||
`tools/vorschau/`: `strats_json.py` (aus levelbau/ aufrufen), `render_level.py`, `label.py`):
|
||||
## Preview renderings (Blender)
|
||||
`/usr/bin/blender` (Debian 3.0.1): **Cycles renders only black** (even the default scene), OCIO
|
||||
is missing ("Color management: fallback", only the "Standard" view). **Eevee works**, but needs an
|
||||
X display → own `Xvfb :7` (never :0, :5 belongs to lamatest). Procedure (scripts in
|
||||
`tools/vorschau/`: `strats_json.py` (call from levelbau/), `render_level.py`, `label.py`):
|
||||
1. `geom_export.py out/testwelt.wad GE` → level.obj/mtl + page_NN.png
|
||||
2. Strat-Positionen: Levelskript mit `levelbuild.Level` ausführen (ohne `L.build`), `L.strats` als JSON
|
||||
2. Strat positions: run the level script with `levelbuild.Level` (without `L.build`), `L.strats` as JSON
|
||||
3. `DISPLAY=:7 STRATS=strats.json VIEWS=schraeg,schraeg2,oben blender -b -P render_level.py -- GE r 64`:
|
||||
eigener OBJ-Leser (der 3.0-Importer kennt keine Vertexfarben), Spiegelung OBJ→Spiel (y_blender =
|
||||
−Spiel-z, Umlaufsinn drehen), Material = Textur (Closest) × Vertexfarbe, etwas Emission,
|
||||
Sonne + Himmel, Marker für Strats (Münzen gold, Lama-Start oranger Kegel, Galerie-Objekte
|
||||
in Reihenfarbe, Tiere weiß, Ausgang lila Ring); schreibt je Ansicht PNG + JSON mit
|
||||
Bildkoordinaten der Bereichsnamen.
|
||||
4. PIL: auf 1280 px skalieren, Beschriftungen (Start/Galerie/Gehege/Parkour/Ziel), JPG q85.
|
||||
own OBJ reader (the 3.0 importer does not know vertex colours), mirroring OBJ→game (y_blender =
|
||||
−game z, flip winding), material = texture (closest) × vertex colour, a little emission,
|
||||
sun + sky, markers for strats (coins gold, llama start orange cone, gallery objects
|
||||
in row colour, animals white, exit purple ring); writes PNG + JSON per view with
|
||||
image coordinates of the area names.
|
||||
4. PIL: scale to 1280 px, labels (Start/Galerie/Gehege/Parkour/Ziel), JPG q85.
|
||||
Reference in new issue
Block a user