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:
1 parent
a0d961357e
commit
6d449257a7
21 files changed
+153
-44
No files matched your search
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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. |
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+7
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user