diff --git a/README.md b/README.md index 3e04c49..b661081 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/build.gradle b/build.gradle index 54cba2b..f72c250 100644 --- a/build.gradle +++ b/build.gradle @@ -92,8 +92,9 @@ processResources { test { useJUnitPlatform() - // golden tests: -Dllamamod.cd= -Dllamamod.ref= - systemProperty 'llamamod.cd', System.getProperty('llamamod.cd', "${System.getProperty('user.home')}/.wine-lama/iso-dk/Setup/Eng/Cd") + // golden tests: -Dllamamod.cd= (or env LLAMAMOD_CD) -Dllamamod.ref=; + // 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 /testwelt.wad etc. systemProperty 'llamamod.lbref', System.getProperty('llamamod.lbref', '') diff --git a/docs/developers.md b/docs/developers.md index eb86ee1..e58b3cb 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -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//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//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 diff --git a/docs/guide/levels-in-code.md b/docs/guide/levels-in-code.md index 6f90aeb..319b843 100644 --- a/docs/guide/levels-in-code.md +++ b/docs/guide/levels-in-code.md @@ -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 /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 `. `--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 diff --git a/docs/manual/12-environment.md b/docs/manual/12-environment.md index 5e3d6dd..ef3a651 100644 --- a/docs/manual/12-environment.md +++ b/docs/manual/12-environment.md @@ -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/.ASF`): VILLAGE, JUNGLE1, RIVER, CITY, CATACOMB, LAB, BALLOON, KRONK, VICTORY … *0 – (none)* stops the music. | +| **Music** | Track started when the zone becomes active (`music/.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. | diff --git a/docs/manual/tutorial-original-level.md b/docs/manual/tutorial-original-level.md index e7c5d89..f4cb18b 100644 --- a/docs/manual/tutorial-original-level.md +++ b/docs/manual/tutorial-original-level.md @@ -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. diff --git a/docs/reference/audio.md b/docs/reference/audio.md index c3861e7..6c901e4 100644 --- a/docs/reference/audio.md +++ b/docs/reference/audio.md @@ -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), diff --git a/docs/tools.md b/docs/tools.md index 95988a0..a1007f2 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -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 diff --git a/notes/llamamod-app.md b/notes/llamamod-app.md index 0f22e8d..a3b1ea9 100644 --- a/notes/llamamod-app.md +++ b/notes/llamamod-app.md @@ -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 (`//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 `/_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 `/_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. diff --git a/notes/testharness.md b/notes/testharness.md index cd86739..16918d6 100644 --- a/notes/testharness.md +++ b/notes/testharness.md @@ -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 ``: `report.txt`, `wine.log` (stderr, truncated to 2 MB), @@ -66,7 +67,7 @@ Output in ``: `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 diff --git a/src/main/java/com/eliasmueller/llamamod/editor/render/ViewerDemo.java b/src/main/java/com/eliasmueller/llamamod/editor/render/ViewerDemo.java index 0702043..68ca107 100644 --- a/src/main/java/com/eliasmueller/llamamod/editor/render/ViewerDemo.java +++ b/src/main/java/com/eliasmueller/llamamod/editor/render/ViewerDemo.java @@ -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)); diff --git a/src/main/java/com/eliasmueller/llamamod/editor/tools/environment/EnvMath.java b/src/main/java/com/eliasmueller/llamamod/editor/tools/environment/EnvMath.java index 913b1cd..de9db01 100644 --- a/src/main/java/com/eliasmueller/llamamod/editor/tools/environment/EnvMath.java +++ b/src/main/java/com/eliasmueller/llamamod/editor/tools/environment/EnvMath.java @@ -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 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 diff --git a/src/test/java/com/eliasmueller/llamamod/editor/tools/environment/EnvironmentToolsTest.java b/src/test/java/com/eliasmueller/llamamod/editor/tools/environment/EnvironmentToolsTest.java index 7fdb199..5842246 100644 --- a/src/test/java/com/eliasmueller/llamamod/editor/tools/environment/EnvironmentToolsTest.java +++ b/src/test/java/com/eliasmueller/llamamod/editor/tools/environment/EnvironmentToolsTest.java @@ -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}) { diff --git a/src/test/java/com/eliasmueller/llamamod/editor/tools/strats/StratsGameLevel.java b/src/test/java/com/eliasmueller/llamamod/editor/tools/strats/StratsGameLevel.java index 4cedbb4..7110092 100644 --- a/src/test/java/com/eliasmueller/llamamod/editor/tools/strats/StratsGameLevel.java +++ b/src/test/java/com/eliasmueller/llamamod/editor/tools/strats/StratsGameLevel.java @@ -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])); diff --git a/tools/apptest.py b/tools/apptest.py index b86a811..cbcd3cf 100755 --- a/tools/apptest.py +++ b/tools/apptest.py @@ -7,7 +7,7 @@ daemon (null sink only) and Wine prefix, so several agents can test in parallel: //data LLAMAMOD_DATA (config.json, original/ (hard links), game/, cd-active/, wine/) //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= + APPTEST_JAVA_OPTS="-Dllamamod.extraMods=" 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 diff --git a/tools/debugviz.py b/tools/debugviz.py index 24daf09..88d8026 100644 --- a/tools/debugviz.py +++ b/tools/debugviz.py @@ -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: diff --git a/tools/debugviz_check.py b/tools/debugviz_check.py index 8da035f..68e7040 100644 --- a/tools/debugviz_check.py +++ b/tools/debugviz_check.py @@ -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 [] -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 []' +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 = [] diff --git a/tools/lama-mod b/tools/lama-mod index 0a8203c..1a316bb 100755 --- a/tools/lama-mod +++ b/tools/lama-mod @@ -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" diff --git a/tools/lamapaths.py b/tools/lamapaths.py new file mode 100644 index 0000000..f247a56 --- /dev/null +++ b/tools/lamapaths.py @@ -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 diff --git a/tools/lamatest.py b/tools/lamatest.py index 1200e94..42bc9b7 100755 --- a/tools/lamatest.py +++ b/tools/lamatest.py @@ -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) diff --git a/tools/levelbuild.py b/tools/levelbuild.py index 4b4eae9..cf99904 100644 --- a/tools/levelbuild.py +++ b/tools/levelbuild.py @@ -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)