7.5 KiB
Level editor – play-test straight into the level (editor.tools.playtest, mods.boottolevel)
Before: Play installed the level and started the game at its main menu; the player had to choose Spiel starten → slot 3 → Enter on the map. Now Play (F5) lands in the level a few seconds after the game window opens, and Escape quits back to the editor.
Editor side (PlaytestEditorPlugin, PlaytestRunner, FloorFinder)
| Action | Key | |
|---|---|---|
Play (replaces the built-in level.play) |
F5 | build → install level mod → apply user mods + boot mod → save slot → start |
| Play from camera position | Shift+F5 (toolbar) | the llama starts on the ground in the middle of the view, facing the camera direction |
| Play boots straight into the level | Level menu (check) | off = old behaviour (main menu, map cursor) |
| Escape quits the play-test | Level menu (check) | boot mod option quitOnEscape |
- Run:
PlayTest.build(built-in, background) → the project snapshot is written to<data>/levels/<stem>.llamalevel→ the user's active mods (others on the same WAD and Test World on chapter 1 turned off) + the level mod becomeenabledMods;boot-to-levelis applied in addition (not stored inenabledMods) with world/chapter of the project, slot (Preferences, default 3),logos=true,quitOnEscape,start→ save slot unlocked up to the level →GameLauncher.launch. - Restore: a daemon thread polls
GameLauncher.isRunning()every 2 s; when the game has ended it re-applies the user's selection without the boot mod (cached outputs, < 1 s) and restores the boot mod's own options → LlamaMod's normal Play starts at the menu again (RestoreCheck: active[level-…, boot-to-level]→[level-…], exe without mod patches). If the editor is closed before the game, LlamaMod shows the selection as "apply needed". - Play from camera position: ground point = depth-buffer hit at the viewport centre
(export space → game x, y, −z), heading = camera yaw (
atan2(f.x, −f.z), 0 = +z).FloorFinderthen looks for collision: highest floor polygon (|n.y| ≥ 0.5) at or below the hit + 0.6 among scene tiles and brush-generated tiles, searching up to 0.75 cells around (rings of 8); material 30 (death floor) is rejected. Needed because render geometry often has no collision (village: the hill edge east of the start renders, but only a death floor at y 2.25 lies below – the llama fell to a game over). -Dllamamod.editor.noLaunch=trueskips the start (tests start the game with apptest).
Exe mod boot-to-level (GAMEPLAY, modifiesExecutable)
Options: world 1–9, chapter 1–49, slot 1–3, logos (skip logo movies/title, same patch
as intro-skip – only applied if the bytes are still original, so both mods can be on),
quitOnEscape, start ("x, y, z[, yaw]" in cells; empty = the level's own start). Order 90
(after level mods). Registered through META-INF/services/com.eliasmueller.llamamod.mods.Mod
(ServiceLoader; ModRegistry was not edited).
Engine facts used
| 0x5846E4 | game state: 1 ?, 2 main menu, 3 in game, 4 attract demo, 5 options?, 6 save slots, 12 world map, 0x11 quit |
| 0x578F28 | frame handler per state (returns 1 = state changed → main loop runs the enter handler) |
| 0x578F7C | enter handler per state; state 3 = FUN_0054ae20: intro movie, loading screen T1%d%d.COR, FUN_00419510(tribe, world, chapter, 0) loads the level |
| 0x579050 | per-state update/render (3 = FUN_0054b590, the game frame) |
| 0x584EB4/B8/BC/C0 | tribe, world, chapter, map of the level to load; 0x585588/0x584C24/0x584C20/0x58557C = map cursor |
| 0x584700 | next state for fade transitions; 0x584638 = sub state (7 = enter level) |
| FUN_0041c0e0 | reads king0..2.ksv into 0x585274 + slot·0x100 |
| FUN_0041cc70 | loads slot 0x584C28 into the game variables (new game in it if empty) |
| 0x546CC0, 0x546990([0x6D94B4]), 0x5464D0 | music/sound shutdown done before every state switch (copied from the demo switch at 0x549E5D) |
| 0x5FCF08 bit 0x8 | "pause" pressed this frame (Escape) |
| main loop (WinMain) | exits through its normal cleanup (FUN_00545030, DestroyWindow, CoUninitialize) when the state is 0x11 – the main menu's Beenden |
Patches
- Boot:
jmpat 0x549C50 (state-2 frame handler,A1 2C 37 58 00=mov eax,[0x58372c]). The cave runs once (flag in anallocateDatablock): pushad, read slots,[0x584C28]=slot−1, load slot, set tribe 1/world/chapter/map 0 (+ map cursor),[0x5855B0]=0, sub state 7, music shutdown, state 3, popad,mov eax,1; ret→ the main loop enters state 3 exactly like the map's Enter would. Later main-menu frames (after quitting to the menu) run the original code. - Escape quits (
quitOnEscape):jmpat 0x549F40 (state-3 frame handler, same 5 bytes):test byte [0x5FCF08],8; jz original; mov [0x5846E4],0x11; mov eax,1; ret. - Logos: 0x41D56E
68 28 25→53 EB 62(see mod-introskip.md). - Start override (data, not exe): the level's WAD (as written by earlier mods) is imported,
the Llama moved/turned and rebuilt (
StartOverride; an unchanged rebuild is byte-identical). Its waypoint chain moves and turns along only if it is an editor/levelbuild camera chain (all nodes but the first on the llama); original levels use the llama's chain as a level-wide list, which stays.
Verified in game (apptest, display :9, Wine 6, 2026-11-06)
| Test | Result |
|---|---|
boot-to-level world 1 chapter 2, original data |
Village 2 fly-by 5 s after the window, gameplay at ~20 s, walking works |
Editor Play on examples/testwelt.llamalevel (TestLevel 2) |
game shows the Testwelt plaza with HUD 4 s after the window opens (play_boot.jpg) |
| Editor Play from camera position, Village 1, start 2.3 cells east | llama stands at the new spot (play_from_here.jpg); the original village fly-by is skipped (its cut-scene camera is tied to the original start) |
| Same with the view on the hill edge (no collision) | before FloorFinder: llama fell to a game over; now the editor finds the nearest floor or refuses |
| Escape in gameplay (village and TestLevel) | groove.exe gone after 1.3 s, no crash, no Wine error dialog, the next start works |
| Restore after the game | active mods back to the user's selection, exe has no mod patches |
Images: img/editor/play_boot.jpg, play_from_here.jpg, play_editor.jpg,
play_windows_editor.jpg.
Windows package under Wine
./gradlew packageWindows → LlamaMod-windows-x64.zip (45 MB). Unpacked into an own win64
prefix (Wine 6.0.3), started as JDK_JAVA_OPTIONS="-XX:ActiveProcessorCount=4 -Dllamamod.data=Z:\…\data" wine runtime/bin/javaw.exe -jar LlamaMod.exe --editor t1l1m001
(the launch4j exe itself hangs in its javaw -version check under Wine 6, see llamamod-app.md):
the editor window opens (within about two minutes on the first start, software GL) and renders the village with all phase-3 plugins (Play from
camera position, Textures tab, paint tool) – img/editor/play_windows_editor.jpg. The game was
not started from the Windows build (the private Wine prefix has no game install); real Windows
hardware is untested.
Tests
BootToLevelModTest (hooks at both frame handlers, logo patch, exe re-parses; Escape hook only
when asked; start parsing; original village: only the llama moves, file size unchanged;
editor-style chain moves and turns with the llama), RestoreCheck (manual), EdTour +
PlayTourSteps (scripted editor runs on an X display: camera placement, play-from-here).
