tool paths configurable by environment (LLAMAMOD_CD, LAMA_WINEPREFIX, LAMA_MOD, APPTEST_ROOT, APPTEST_ISO; tools/lamapaths.py), music tracks without a file on the cd marked in the editor and the tutorial uses kronk, levelbuild.py accepts the english texture names

This commit is contained in:
MineTec committed 2026-11-07 16:34:17 +01:00
1 parent a0d961357e
commit 6d449257a7
21 files changed
+153 -44

No files matched your search

+2 -1
View File
@@ -109,7 +109,8 @@ Where the tools look for the game files (or pass `--base`/`--src`):
|---|---|---|
| `LLAMAMOD_CD` | the CD folder (`wads/`, `music/`, …), also for the Gradle tests | `~/.wine-lama/iso-dk/Setup/Eng/Cd` |
| `LAMA_WINEPREFIX` | Wine prefix of a hand-made game setup (`lamatest.py`, `levelbuild.py --install`) | `~/.wine-lama` |
| `APPTEST_ROOT`, `APPTEST_ISO` | session folders and ISO of `apptest.py` | `~/.cache/llamamod-apptest`, `~/Downloads/GROOVE_DK.iso` |
| `LAMA_MOD` | the `lama-mod` script used by `lamatest.py` | `~/.local/bin/lama-mod`, else `tools/lama-mod` |
| `APPTEST_ROOT`, `APPTEST_ISO` | session folders and ISO of `apptest.py` | `~/.cache/llamamod-apptest` (`$CLAUDE_JOB_DIR/tmp` if set), `~/Downloads/GROOVE_DK.iso` |
| `LLAMAMOD_DATA` | data folder of the app | `~/.local/share/LlamaMod` (Windows `%LOCALAPPDATA%\LlamaMod`) |
All tools and variables: [docs/tools.md](docs/tools.md#paths).
+3 -2
View File
@@ -92,8 +92,9 @@ processResources {
test {
useJUnitPlatform()
// golden tests: -Dllamamod.cd=<cd dir> -Dllamamod.ref=<python reference dir>
systemProperty 'llamamod.cd', System.getProperty('llamamod.cd', "${System.getProperty('user.home')}/.wine-lama/iso-dk/Setup/Eng/Cd")
// golden tests: -Dllamamod.cd=<cd dir> (or env LLAMAMOD_CD) -Dllamamod.ref=<python reference dir>;
// tests that need the CD are skipped when the folder does not exist
systemProperty 'llamamod.cd', System.getProperty('llamamod.cd', System.getenv('LLAMAMOD_CD') ?: "${System.getProperty('user.home')}/.wine-lama/iso-dk/Setup/Eng/Cd")
systemProperty 'llamamod.ref', System.getProperty('llamamod.ref', '')
// level builder references: python3 tools/levelbuild.py levels/testwelt.py <lbref>/testwelt.wad etc.
systemProperty 'llamamod.lbref', System.getProperty('llamamod.lbref', '')
+2 -2
View File
@@ -21,8 +21,8 @@ java -jar build/libs/llamamod-*-all.jar --editor # the editor alone
Details (jlink, launch4j, editions, version and build id): [notes/llamamod-app.md](../notes/llamamod-app.md#build).
**Tests that need the game.** Golden and in-game-data tests read the CD folder given by
`-Dllamamod.cd=<…/Setup/<lang>/Cd>` (default: the author's Wine setup); without it they are
skipped. Byte-exact round trips over all level WADs (WAD, TRAK, MAP, TEXT, strat code) are the
`-Dllamamod.cd=<…/Setup/<lang>/Cd>` or the environment variable `LLAMAMOD_CD` (default:
`~/.wine-lama/iso-dk/Setup/Eng/Cd`); if that folder does not exist they are skipped. Byte-exact round trips over all level WADs (WAD, TRAK, MAP, TEXT, strat code) are the
safety net for every format change.
## Code map
+8 -4
View File
@@ -43,10 +43,14 @@ Building blocks: `floor`, `box`, `platform`, `pillar`, `ramp`, `stairs`, `fence`
`quad`, `collider`, `obj` (an OBJ file), `height_at`. The exit needs a floor with collision
material 16 in its cell – the test world uses `box(..., mat=16)` under the door.
The Python tools have a few **hard-coded paths** at the top of the scripts (`WADS`, `MODWAD` in
`levelbuild.py`, `CD_WADS` in `debugviz.py`, `PREFIX` in `lamatest.py`) that point into the original
author's Wine setup; use `--base <cd>/wads/t1l1m001.wad` or adjust them. `--install` only works with
that setup – with LlamaMod, use a Java level or the editor instead.
**Texture names**: the Python builder's own names are German (`gras`, `stein`, `erde`, `platte`,
`meer` …, list: `--textures`); the English names of the Java builder (`grass`, `stone`, `earth`,
`slab`, `sea` …) work as well and give the same result.
The base level is `t1l1m001.wad` in the CD folder `$LLAMAMOD_CD/wads` (default
`~/.wine-lama/iso-dk/Setup/Eng/Cd`), or `--base <file>`. `--install` copies the WAD into the TestLevel
mod of a hand-made Wine setup (`$LAMA_WINEPREFIX/mods/testlevel`, default `~/.wine-lama`) – with
LlamaMod, use a Java level or the editor instead. All variables: [Tools: Paths](../tools.md#paths).
## A Java level
+1 -1
View File
@@ -113,7 +113,7 @@ Colour pickers update the viewport live while you pick; the whole pick is one un
| Field | Meaning |
|---|---|
| **Music** | Track started when the zone becomes active (`music/<name>.ASF`): VILLAGE, JUNGLE1, RIVER, CITY, CATACOMB, LAB, BALLOON, KRONK, VICTORY … *0 – (none)* stops the music. |
| **Music** | Track started when the zone becomes active (`music/<name>.ASF`): VILLAGE, JUNGLE1, RIVER, CITY, CATACOMB, LAB, BALLOON, KRONK, VICTORY … *0 – (none)* stops the music. Tracks marked *(no file on the CD)* have no music file (on the German CD) – pick another one. |
| **Sky model** | The object model drawn as the sky dome (one of the sky models used in this level), or none. |
| **Sky yaw** | Turns the sky dome. |
+1 -1
View File
@@ -50,7 +50,7 @@ The start of the level uses the **start zone**:
4. In the Inspector, click **Fog / sky colour** and choose a new colour – for example a soft
orange for an evening mood. The viewport follows while you pick.
5. Lower **End (Max)** a little for a denser fog.
6. Under **Music & sky**, choose another **Music** track, for example `PUZZLE2`.
6. Under **Music & sky**, choose another **Music** track, for example `22 – KRONK`.
The village houses have their own zones (one per door), so the inside of a house keeps its
original fog. See [Environment](12-environment.md) for every field.
+1 -1
View File
@@ -30,7 +30,7 @@ The id table, as used by the editor's zone inspector
| 9 | TITLE | 19 | RIVRACE | 29 | CITYPUZZ | 39 | ENDGAME |
Four names (GAMEOVER, TALLY, PUZZLE2, PLATFORM) have no `.asf` file on the German CD; what the
game plays for them (probably nothing) was not checked.
game plays for them (probably nothing) was not checked. The editor marks them *(no file on the CD)*.
**Changing the music of a level** works today: pick another id for the start zone (or any zone)
in the editor's *Environment* panel ([manual: Environment](../manual/12-environment.md),
+18 -4
View File
@@ -82,8 +82,22 @@ The reverse-engineering toolkit the project started with. Python 3.8+ with `nump
Level scripts in [`levels/`](../levels/): `testwelt.py` (the original test world),
`sprunglabor.py` (jump lab used to measure the llama), `testwelt_parkour.seq` (scripted run).
Several scripts have **hard-coded paths** to the original author's Wine setup at the top (see
[Levels from scripts](guide/levels-in-code.md#a-python-level)).
### Paths
The tools find the original game files through these environment variables
([`tools/lamapaths.py`](../tools/lamapaths.py)); the defaults match a hand-made Wine setup.
Command-line options (`--base`, `--src`, `--root`, `--iso`) win over them.
| Variable | Used by | Default |
|---|---|---|
| `LLAMAMOD_CD` | `levelbuild.py`, `debugviz.py`, `debugviz_check.py`, Gradle tests (`-Dllamamod.cd`) | `$LAMA_WINEPREFIX/iso-dk/Setup/Eng/Cd` – the CD folder (`wads/`, `music/`, `*.cor`) |
| `LAMA_WINEPREFIX` | `lamatest.py`, `levelbuild.py --install` | `~/.wine-lama` |
| `LAMA_MOD` | `lamatest.py` | `~/.local/bin/lama-mod`, else `tools/lama-mod` |
| `APPTEST_ROOT` | `apptest.py` (session folders) | `$CLAUDE_JOB_DIR/tmp` if set, else `~/.cache/llamamod-apptest` |
| `APPTEST_ISO` | `apptest.py` (first install) | `~/Downloads/GROOVE_DK.iso` |
| `LAMATEST_DIR` | `lamatest.py` (state, logs) | `/tmp/lamatest-$USER` |
A missing folder stops the tool with a message naming the variable to set.
## Testing in the game
@@ -93,8 +107,8 @@ Several scripts have **hard-coded paths** to the original author's Wine setup at
| [`tools/lamatest.py`](../tools/lamatest.py) | the older harness for a hand-made Wine setup: loads a WAD as TestLevel and runs a key sequence | [notes/testharness.md](../notes/testharness.md) |
Linux only; needs Wine, Xvfb, xdotool, ImageMagick, tesseract (German data, for reading menu
texts) and PulseAudio. `apptest.py` defaults to a session root inside the author's job folder –
set `APPTEST_ROOT`.
texts) and PulseAudio. Session folders and the ISO: `APPTEST_ROOT`, `APPTEST_ISO` (see
[Paths](#paths)).
```bash
./gradlew fatJar
+3 -3
View File
@@ -359,9 +359,9 @@ On real hardware no wrapper should be needed (unverified).
Runs the game through the LlamaMod app (fat jar CLI), invisible and muted, one isolated *session*
per name: own data folder (`<root>/<name>/data`, = LLAMAMOD_DATA, own Wine prefix and save games),
own Xvfb display, own PulseAudio daemon with only a null sink (PulseEffects on the desktop steals
streams from null sinks on the main server). Root: `$APPTEST_ROOT`, default
`/home/elias/.claude/jobs/90c6eed7/tmp`. The original game data is installed once from
`~/Downloads/GROOVE_DK.iso` into `<root>/_llamamod-template` (file lock, so parallel agents wait)
streams from null sinks on the main server). Root: `$APPTEST_ROOT`, else `$CLAUDE_JOB_DIR/tmp`
(AI agent sessions), else `~/.cache/llamamod-apptest`. The original game data is installed once from
the ISO (`--iso`, `$APPTEST_ISO`, default `~/Downloads/GROOVE_DK.iso`) into `<root>/_llamamod-template` (file lock, so parallel agents wait)
and hard-linked into each session (~30 MB per session + ~200 MB Wine prefix on first start).
`~/.wine-lama` and the user's own game are never touched.
+3 -2
View File
@@ -57,7 +57,8 @@ WAD path absolute!), `rs.sh NAME "SEQ"` (restart with installed WAD + `seq`, mon
**Caution**: no `pgrep -f groove.exe` in wait loops – it matches your own shell
(the command line contains "groove.exe") and lamatest takes it for a foreign instance.
State/PIDs/wine.log: `$LAMATEST_DIR` (default `/tmp/lamatest-$USER`).
State/PIDs/wine.log: `$LAMATEST_DIR` (default `/tmp/lamatest-$USER`). Game: `$LAMA_WINEPREFIX` (default `~/.wine-lama`),
`lama-mod`: `$LAMA_MOD` (see [docs/tools.md](../docs/tools.md#paths)).
`run` exit codes: 0 OK, 3 crash, 4 timeout/hang, 5 image frozen.
Output in `<outdir>`: `report.txt`, `wine.log` (stderr, truncated to 2 MB),
@@ -66,7 +67,7 @@ Output in `<outdir>`: `report.txt`, `wine.log` (stderr, truncated to 2 MB),
## Safety rules (implemented in the script)
- Aborts if a groove.exe with a different DISPLAY is running (Elias' instance).
- Aborts if a groove.exe with a different DISPLAY is running (the user's own instance).
- xdotool only with DISPLAY=:5.
- Sound: own null sink, the app is never muted; `stop` unloads the sink.
- Save games (`*.ksv` in the game folder) are backed up on `start` and restored
@@ -57,7 +57,9 @@ public final class ViewerDemo {
o.put(a, args[++i]);
}
}
Path cd = Path.of(o.getOrDefault("--cd", System.getProperty("user.home") + "/.wine-lama/iso-dk/Setup/Eng/Cd"));
String envCd = System.getenv("LLAMAMOD_CD");
Path cd = Path.of(o.getOrDefault(
"--cd", envCd != null ? envCd : System.getProperty("user.home") + "/.wine-lama/iso-dk/Setup/Eng/Cd"));
byte[] wad;
if (args[0].equals("testworld")) {
byte[] base = Files.readAllBytes(cd.resolve(LevelBuilder.BASE_WAD));
@@ -191,8 +191,14 @@ public final class EnvMath {
"ENDGAME"
};
/** Ids whose {@code .ASF} file the (German) CD does not have: GAMEOVER, TALLY, PUZZLE2, PLATFORM. */
private static final List<Integer> NO_FILE = List.of(10, 11, 36, 37);
public static String musicLabel(int id) {
return id >= 0 && id < MUSIC.length ? id + " – " + MUSIC[id] : id + " – (unknown)";
if (id < 0 || id >= MUSIC.length) {
return id + " – (unknown)";
}
return id + " – " + MUSIC[id] + (NO_FILE.contains(id) ? " (no file on the CD)" : "");
}
// ------------------------------------------------------------ lights
@@ -50,6 +50,13 @@ class EnvironmentToolsTest {
assertArrayEquals(new double[] {6, 9}, EnvMath.fog(z, 1), 1e-9);
}
@Test
void musicLabelsMarkTracksWithoutFile() {
assertEquals("22 – KRONK", EnvMath.musicLabel(22));
assertEquals("36 – PUZZLE2 (no file on the CD)", EnvMath.musicLabel(36));
assertEquals("99 – (unknown)", EnvMath.musicLabel(99));
}
@Test
void facingRoundTrip() {
for (double d : new double[] {0, 22.5, 90, 180, 270, 337.5}) {
@@ -121,7 +121,10 @@ public final class StratsGameLevel {
.replace(" collisionOn<0x20000>()\n collRadius(0.6)\n", "");
public static void main(String[] a) throws Exception {
Bases cd = Bases.fromCd(Path.of(System.getProperty("user.home") + "/.wine-lama/iso-dk/Setup/Eng/Cd"));
String env = System.getenv("LLAMAMOD_CD");
Bases cd = Bases.fromCd(Path.of(System.getProperty(
"llamamod.cd",
env != null ? env : System.getProperty("user.home") + "/.wine-lama/iso-dk/Setup/Eng/Cd")));
StratEdits.init();
if (a.length > 1 && a[1].equals("v2")) {
v2(cd, Path.of(a[0]));
+7 -4
View File
@@ -7,7 +7,7 @@ daemon (null sink only) and Wine prefix, so several agents can test in parallel:
<root>/<NAME>/data LLAMAMOD_DATA (config.json, original/ (hard links), game/, cd-active/, wine/)
<root>/<NAME>/harness state.json, pulse/ (socket, rt, home), logs, xvfb.log
root = $APPTEST_ROOT or /home/elias/.claude/jobs/90c6eed7/tmp
root = $APPTEST_ROOT, else $CLAUDE_JOB_DIR/tmp (AI agent sessions), else ~/.cache/llamamod-apptest
Commands (NAME first; --display only for 'start'/'run', remembered afterwards):
apptest.py setup NAME install from the ISO (shared hard-linked template)
@@ -27,7 +27,7 @@ Commands (NAME first; --display only for 'start'/'run', remembered afterwards):
--no-retry turns that off
Options of every command: --root DIR, --jar FILE (default: newest build/libs/*-all.jar),
--iso FILE (default ~/Downloads/GROOVE_DK.iso). Python: `from apptest import Session`.
--iso FILE (default $APPTEST_ISO or ~/Downloads/GROOVE_DK.iso). Python: `from apptest import Session`.
Environment: APPTEST_CP=<extra classpath> + APPTEST_JAVA_OPTS="-Dllamamod.extraMods=<class>" test
mods that are not registered yet (e.g. APPTEST_CP=build/classes/java/test).
@@ -46,8 +46,11 @@ import time
HOME = os.path.expanduser("~")
PROJECT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEFAULT_ROOT = os.environ.get("APPTEST_ROOT", "/home/elias/.claude/jobs/90c6eed7/tmp")
DEFAULT_ISO = os.path.join(HOME, "Downloads", "GROOVE_DK.iso")
DEFAULT_ROOT = os.path.expanduser(
os.environ.get("APPTEST_ROOT")
or (os.path.join(os.environ["CLAUDE_JOB_DIR"], "tmp") if os.environ.get("CLAUDE_JOB_DIR") else "")
or os.path.join(HOME, ".cache", "llamamod-apptest"))
DEFAULT_ISO = os.path.expanduser(os.environ.get("APPTEST_ISO") or os.path.join(HOME, "Downloads", "GROOVE_DK.iso"))
TEMPLATE = "_llamamod-template"
SCREEN = (1920, 1080)
FULL = "%dx%d+0+0" % SCREEN
+4 -1
View File
@@ -40,7 +40,9 @@ import geomlib as G # noqa: E402
import mapchunk # noqa: E402
import strats as S # noqa: E402
CD_WADS = os.path.expanduser('~/.wine-lama/iso-dk/Setup/Eng/Cd/wads')
import lamapaths # noqa: E402
CD_WADS = lamapaths.CD_WADS
# ------------------------------------------------------------------ colours
KIND_COL = {
@@ -706,6 +708,7 @@ def main():
opt = ap.parse_args()
infos = []
if opt.all:
lamapaths.need(opt.src, 'CD wads folder', 'pass --src or set LLAMAMOD_CD')
os.makedirs(opt.all, exist_ok=True)
files = sorted(glob.glob(os.path.join(opt.src, 't[1-6]l*.wad')))
if opt.only:
+6 -3
View File
@@ -1,8 +1,11 @@
import sys, glob, os, struct
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import geomlib as G, mapchunk
# usage: debugviz_check.py <folder with debug WADs> [<folder with original CD WADs>]
SRC = (sys.argv[2] if len(sys.argv) > 2 else os.path.expanduser('~/.wine-lama/iso-dk/Setup/Eng/Cd/wads')) + '/'
import geomlib as G, mapchunk, lamapaths
USAGE = 'usage: debugviz_check.py <folder with debug WADs> [<folder with original CD WADs, default $LLAMAMOD_CD/wads>]'
if len(sys.argv) < 2 or sys.argv[1] in ('-h', '--help'):
sys.exit(USAGE)
SRC = lamapaths.need(sys.argv[2] if len(sys.argv) > 2 else lamapaths.CD_WADS, 'CD wads folder',
'pass it as 2nd argument or set LLAMAMOD_CD') + '/'
bad = 0
for f in sorted(glob.glob(sys.argv[1] + '/*.wad')):
n = os.path.basename(f); err = []
+1 -1
View File
@@ -15,7 +15,7 @@
# "lama-mod debug an|aus" toggles the "debug" mod independently of the others).
# Order: alphabetical, later mods win (testlevel > debug).
set -e
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-lama}"
export WINEPREFIX="${WINEPREFIX:-${LAMA_WINEPREFIX:-$HOME/.wine-lama}}"
MODS="$WINEPREFIX/mods"
OVL="$WINEPREFIX/cd-overlay"
DBG="$MODS/debug"
+31
View File
@@ -0,0 +1,31 @@
"""Where the Python tools look for the original game files (all overridable by environment variables).
LLAMAMOD_CD folder with the CD contents (wads/, music/, *.cor ...),
default $LAMA_WINEPREFIX/iso-dk/Setup/Eng/Cd
LAMA_WINEPREFIX Wine prefix of a hand-made game setup (lamatest.py, levelbuild.py --install),
default ~/.wine-lama
LAMA_MOD the lama-mod script, default ~/.local/bin/lama-mod, else tools/lama-mod
"""
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
WINEPREFIX = os.path.expanduser(os.environ.get('LAMA_WINEPREFIX') or '~/.wine-lama')
CD = os.path.expanduser(os.environ.get('LLAMAMOD_CD') or os.path.join(WINEPREFIX, 'iso-dk', 'Setup', 'Eng', 'Cd'))
CD_WADS = os.path.join(CD, 'wads')
TESTLEVEL_WAD = os.path.join(WINEPREFIX, 'mods', 'testlevel', 'wads', 'T1L9M001.wad')
def lama_mod():
p = os.environ.get('LAMA_MOD')
if p:
return os.path.expanduser(p)
p = os.path.expanduser('~/.local/bin/lama-mod')
return p if os.path.exists(p) else os.path.join(HERE, 'lama-mod')
def need(path, what, hint):
"""Exit with a clear message if PATH does not exist."""
if not os.path.exists(path):
sys.exit('%s not found: %s (%s)' % (what, path, hint))
return path
+10 -5
View File
@@ -27,7 +27,8 @@ TestLevel (world 9, chapter 1) via save slot 3 and takes screenshots.
--keep leave the tested WAD installed (otherwise the previously
installed T1L9M001.wad is restored - not .orig!)
State/logs: $LAMATEST_DIR (default /tmp/lamatest-$USER).
State/logs: $LAMATEST_DIR (default /tmp/lamatest-$USER). Game: $LAMA_WINEPREFIX (default ~/.wine-lama),
see lamapaths.py.
Report lines (report.txt, "ergebnis: ...") and `status` output stay German: helper
scripts grep for them. OCR expectations are German game text.
Docs: ../notes/testharness.md
@@ -43,13 +44,16 @@ import time
HOME = os.path.expanduser("~")
DISPLAY = ":5"
SINK = "lama_stumm"
PREFIX = os.path.join(HOME, ".wine-lama")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import lamapaths # noqa: E402
PREFIX = lamapaths.WINEPREFIX
GAMEDIR = os.path.join(PREFIX, "drive_c", "Program Files", "Disney Interactive",
"Ein Koenigreich fuer ein Lama")
GAMEEXE = r"C:\Program Files\Disney Interactive\Ein Koenigreich fuer ein Lama\groove.exe"
MODWAD = os.path.join(PREFIX, "mods", "testlevel", "wads", "T1L9M001.wad")
OVERLAY = os.path.join(PREFIX, "cd-overlay")
LAMA_MOD = os.path.join(HOME, ".local", "bin", "lama-mod")
LAMA_MOD = lamapaths.lama_mod()
STATE = os.environ.get("LAMATEST_DIR", "/tmp/lamatest-%s" % os.environ.get("USER", "x"))
WINELOG = os.path.join(STATE, "wine.log")
XVFBLOG = os.path.join(STATE, "xvfb.log")
@@ -190,10 +194,11 @@ def check_slot3():
# ---------------------------------------------------------------- commands
def cmd_start(timeout=60):
lamapaths.need(GAMEDIR, "game folder", "set LAMA_WINEPREFIX")
os.makedirs(STATE, exist_ok=True)
f = foreign_groove()
if f:
die("Elias' own game instance is running (pid %s) - test aborted" % f)
die("the user's own game instance is running (pid %s) - test aborted" % f)
if game_alive():
log("game already running on %s" % DISPLAY)
return
@@ -756,7 +761,7 @@ def cmd_run(wad, outdir, n=8, seq=None, do_tour=None, keep=False):
rep = ["wad: %s" % os.path.abspath(wad)]
status = 0
if foreign_groove():
die("Elias' own game instance is running - aborted")
die("the user's own game instance is running - aborted")
# remember the previously installed WAD -> put it back afterwards (not .orig!)
prev = os.path.join(STATE, "vorher_T1L9M001.wad")
os.makedirs(STATE, exist_ok=True)
+31 -6
View File
@@ -13,8 +13,9 @@ ambient occlusion) and object placement (strats by type name, parameters from th
Coordinates (as in the game / for strats): x east, y up, z = grid row
(positive). 1 unit = 1 grid cell. The base level's grid is 96x96 (x,z in 0..96).
Angles in degrees (0..360) about the y axis.
Texture names (TEX/FLAT below) are German keys and part of the level script API; TEX indices are
village indices, other bases (--base) map a name to the same picture or reject it.
Texture names (TEX/FLAT below) are German keys and part of the level script API; the English names of
the Java builder work too (ALIASES). TEX indices are village indices, other bases (--base) map a name
to the same picture or reject it.
Docs: ../notes/levelbau.md
"""
import argparse
@@ -35,9 +36,11 @@ import mapchunk as M # noqa: E402
import stpcwalk # noqa: E402
import strats as S # noqa: E402
WADS = os.path.expanduser('~/.wine-lama/iso-dk/Setup/Eng/Cd/wads')
import lamapaths # noqa: E402
WADS = lamapaths.CD_WADS
BASE = os.path.join(WADS, 't1l1m001.wad')
MODWAD = os.path.expanduser('~/.wine-lama/mods/testlevel/wads/T1L9M001.wad')
MODWAD = lamapaths.TESTLEVEL_WAD
FX = 4096
# texture names for the village (t1l1m001). Contact sheet: notes/img/texturen_dorf.png
@@ -61,6 +64,23 @@ FLAT = {
'rot': (190, 50, 40), 'gelb': (230, 190, 60), 'blau': (60, 90, 190), 'schwarz': (20, 20, 20),
'gruen': (70, 140, 50), 'grau': (120, 120, 120),
}
# English names of the Java builder (levelbuild/LevelBuilder.java) -> the German keys above
ALIASES = {
'grass': 'gras', 'grass2': 'gras2', 'grass_light': 'gras_hell', 'grass_striped': 'gras_streifen',
'grass_dark': 'gras_dunkel', 'grass_patch': 'gras_fleck', 'flowers': 'blumen', 'flowers2': 'blumen2',
'flowers3': 'blumen3', 'earth': 'erde', 'earth2': 'erde2', 'earth3': 'erde3', 'grass_edge': 'graskante',
'grass_edge2': 'graskante2', 'grass_edge_sand': 'graskante_sand', 'stone': 'stein', 'stone2': 'stein2',
'stone_blue': 'stein_blau', 'stone_edge': 'stein_kante', 'stone_green': 'stein_gruen',
'stone_green2': 'stein_gruen2', 'stone_moss': 'stein_moos', 'slab': 'platte', 'slab2': 'platte2',
'slab_edge': 'platte_kante', 'wood_dark': 'holz_dunkel', 'bamboo': 'bambus', 'bamboo2': 'bambus2',
'wood_grain': 'holz_maser', 'wood_grain2': 'holz_maser2', 'crate': 'kiste', 'bark': 'rinde',
'wood_brown': 'holz_braun', 'wood_brown2': 'holz_braun2', 'beam': 'holzbalken', 'wood_rings': 'holz_ringe',
'wood_edge': 'holzkante', 'inca': 'inka', 'pattern_purple': 'muster_lila', 'pattern_purple2': 'muster_lila2',
'pattern_purple3': 'muster_lila3', 'mushroom_red': 'pilz_rot', 'mushroom_red2': 'pilz_rot2',
'stone_stairs': 'treppe_stein',
'sea': 'meer', 'sea_deep': 'meer_tief', 'white': 'weiss', 'red': 'rot', 'yellow': 'gelb', 'blue': 'blau',
'black': 'schwarz', 'green': 'gruen', 'grey': 'grau',
}
# strat types present in the village code but not placed in the village:
# take type fields (nvars, slots, flags, extra) + parameters from another level
FOREIGN = {'RubyIdol': 't1l1m002.wad'}
@@ -200,6 +220,7 @@ class Level:
key = 'rgb%02x%02x%02x' % t
FLAT.setdefault(key, t)
t = key
t = ALIASES.get(t, t)
if t in TEX:
names = self._base_names()
if t not in names:
@@ -425,7 +446,7 @@ class Level:
V.append(tuple(float(c) * scale + o for c, o in zip(p[1:4], offset)))
elif p[0] == 'usemtl':
m = p[1]
cur = int(m[4:]) if m.startswith('tex_') else (m if (m in TEX or m in FLAT) else tex)
cur = int(m[4:]) if m.startswith('tex_') else (m if (m in TEX or m in FLAT or m in ALIASES) else tex)
elif p[0] == 'f':
ids = [int(t.split('/')[0]) for t in p[1:]]
pts = [V[i - 1 if i > 0 else len(V) + i] for i in ids]
@@ -980,6 +1001,8 @@ def main():
ap.add_argument('--textures', metavar='PNG')
ap.add_argument('--set', action='append', default=[], help='variable for the level script, e.g. --set teil=a')
a = ap.parse_args()
if a.types or a.textures or a.level:
lamapaths.need(a.base, 'base level', 'pass --base or set LLAMAMOD_CD')
if a.types:
L = Level(a.base)
for n in L.types():
@@ -990,9 +1013,10 @@ def main():
if a.textures:
import subprocess
names = {v: k for k, v in TEX.items()}
english = {g: e for e, g in ALIASES.items()}
subprocess.run([sys.executable, os.path.join(HERE, 'texsheet.py'), a.base, a.textures], check=True)
for i in sorted(names):
print('%4d %s' % (i, names[i]))
print('%4d %-16s %s' % (i, names[i], english.get(names[i], '')))
return
if not (a.level and a.out):
ap.error('specify LEVEL.py and OUT.wad')
@@ -1006,6 +1030,7 @@ def main():
mod.report(L)
if a.install:
import shutil
lamapaths.need(os.path.dirname(MODWAD), 'TestLevel mod folder', 'set LAMA_WINEPREFIX')
if not os.path.exists(MODWAD + '.orig'):
shutil.copy2(MODWAD, MODWAD + '.orig')
shutil.copyfile(a.out, MODWAD)