109 lines
7.0 KiB
Markdown
109 lines
7.0 KiB
Markdown
# For developers
|
||
|
||
How the code is organised, how to build and test it, and where the engineering notes are.
|
||
|
||
## Build
|
||
|
||
Requirements: a **JDK 25** installed (the Gradle toolchain compiles with it; there is no
|
||
auto-download). Gradle itself and Spotless currently need to run on **Java 21**, e.g.
|
||
`JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew …` on Debian/Ubuntu.
|
||
|
||
```bash
|
||
./gradlew spotlessApply build # compile, format, run the tests
|
||
./gradlew fatJar # build/libs/llamamod-<ver>-all.jar
|
||
java -jar build/libs/llamamod-*-all.jar # launcher
|
||
java -jar build/libs/llamamod-*-all.jar --editor # the editor alone
|
||
./gradlew packageLinux # tar.gz with its own Java runtime
|
||
./gradlew packageWindows # zip with LlamaMod.exe + runtime (built on Linux)
|
||
./gradlew packageAll # both systems, full and launcher (no editor) edition
|
||
```
|
||
|
||
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>` 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
|
||
|
||
Package root [`src/main/java/com/eliasmueller/llamamod/`](../src/main/java/com/eliasmueller/llamamod/):
|
||
|
||
| Package | Content | Notes |
|
||
|---|---|---|
|
||
| `format` | WAD, TRAK, STPC, MAP, TEXT, collision, COR | [Reference](reference/README.md) |
|
||
| `format.strat` | strat disassembler, assembler, script view, compiler | [Strats](strats/README.md), [notes/strat-language.md](../notes/strat-language.md) |
|
||
| `format.stpcmerge` | importing object types between levels (code, models, textures, sounds, texts) | [notes/editor-cross-import.md](../notes/editor-cross-import.md) |
|
||
| `levelbuild` | Java level builder (`LevelBuilder`, `LevelScript`) | [Levels from scripts](guide/levels-in-code.md) |
|
||
| `mods` | mod framework (`Mod`, `ModContext`, `ModManager`, options) and the built-in mods | [notes/llamamod-app.md](../notes/llamamod-app.md#mods), `notes/mod-*.md` |
|
||
| `exe` | patching `groove.exe`: PE image, code caves, a tiny x86 emitter | [notes/llamamod-app.md](../notes/llamamod-app.md#grooveexe-patches-and-code-caves-exeexeimage-exex86) |
|
||
| `platform` | install from ISO/CD, data folder, config, registry, Wine/Windows launch | [notes/llamamod-app.md](../notes/llamamod-app.md) |
|
||
| `save` | KSV save games, TestLevel chapters | [Game files](reference/game-files.md#save-games-ksv) |
|
||
| `ui` | launcher window | |
|
||
| `editor.model`, `editor.io` | scene model, commands/undo, project format, WAD import/build | [notes/editor-api.md](../notes/editor-api.md), [notes/editor-format.md](../notes/editor-format.md) |
|
||
| `editor.render` | OpenGL viewport | [notes/editor-render.md](../notes/editor-render.md) |
|
||
| `editor.ui` | editor shell and plugin API | [notes/editor-ui.md](../notes/editor-ui.md) |
|
||
| `editor.tools.*` | editor plugins: geometry, objects, environment, textures, catalog (cross-world import), strats, playtest, gltf, help | `notes/editor-tools-*.md`, [notes/editor-playtest.md](../notes/editor-playtest.md) |
|
||
|
||
## Writing a mod
|
||
|
||
A mod implements [`Mod`](../src/main/java/com/eliasmueller/llamamod/mods/Mod.java): `id`, `name`,
|
||
`description`, `category` (LEVELS, GAMEPLAY, VISUAL, CHEATS), `options()` and `apply(ModContext)`.
|
||
`apply` reads files of the game as earlier mods left them (`ctx.read`) and writes replacements
|
||
(`ctx.write`); `ctx.exe()` gives access to `groove.exe` for patches. Mods are registered in
|
||
[`ModRegistry`](../src/main/java/com/eliasmueller/llamamod/mods/ModRegistry.java). LlamaMod caches
|
||
mod outputs and never touches the original files.
|
||
|
||
Good examples: [`mods/testworld/`](../src/main/java/com/eliasmueller/llamamod/mods/testworld/) (a
|
||
level built in code), [`mods/arena/`](../src/main/java/com/eliasmueller/llamamod/mods/arena/) (level
|
||
+ new strat types, walkthrough: [Arena](strats/arena.md)),
|
||
[`mods/fog/`](../src/main/java/com/eliasmueller/llamamod/mods/fog/) (data-only change of all levels),
|
||
[`mods/practice/`](../src/main/java/com/eliasmueller/llamamod/mods/practice/) (exe code caves with
|
||
hotkeys). Full API: [notes/llamamod-app.md](../notes/llamamod-app.md#writing-a-mod).
|
||
|
||
Every mod should be checked in the running game; [`tools/apptest.py`](../tools/apptest.py) does
|
||
this unattended on Linux ([Tools](tools.md#testing-in-the-game)).
|
||
|
||
## The editor manual
|
||
|
||
[`docs/manual/`](manual/index.md) is the editor's user manual **and** its built-in help: the build
|
||
bundles the folder into the jar (`processResources` in `build.gradle`), the Help window renders it
|
||
with its own small Markdown converter, and F1 maps editor topics to chapter anchors
|
||
(`editor/tools/help/context-help.txt`). Rules for that folder:
|
||
|
||
* Only the Markdown subset described at the top of `index.md`; every chapter must be linked from
|
||
`index.md`; links must stay **inside** `docs/manual` (the help window cannot open other files).
|
||
* Blocks between `generated:NAME` comments (shortcuts, menus, mod reference) are rewritten from the
|
||
live program by `ManualTest` – do not edit them by hand.
|
||
* `./gradlew test --tests '*ManualTest*'` checks links, anchors and images.
|
||
|
||
Maintenance notes: [notes/editor-manual.md](../notes/editor-manual.md).
|
||
|
||
## Engineering notes
|
||
|
||
[`notes/`](../notes/README.md) is the project's lab notebook: one file per format, editor area and
|
||
mod, with addresses in `groove.exe`, confidence levels, what was verified in the game and known
|
||
limits. Process material (agent briefs, review reports) is in [`notes/dev/`](../notes/dev/).
|
||
|
||
## Releasing
|
||
|
||
Releases are built by Gitea Actions ([.gitea/workflows/release.yml](../.gitea/workflows/release.yml)):
|
||
|
||
1. Add a `## <version> – …` section at the top of [CHANGELOG.md](../CHANGELOG.md) (it becomes the
|
||
release text; the workflow fails if it is missing).
|
||
2. `git tag v<version> && git push origin v<version>`
|
||
|
||
The workflow builds and tests (tests that need the game CD skip themselves on the runner), runs
|
||
`packageAll` with `-PreleaseVersion=<version>` (the tag decides the version; without it
|
||
`build.gradle`'s default is used), writes `SHA256SUMS.txt` and creates the release with the four
|
||
packages via [tools/release/publish_gitea.py](../tools/release/publish_gitea.py) (standard library
|
||
only; re-running replaces files of the same name). [.gitea/workflows/build.yml](../.gitea/workflows/build.yml)
|
||
builds every push.
|
||
|
||
Requirements on the Gitea side: Actions enabled (`[actions] ENABLED = true`), an `act_runner` with
|
||
the label `ubuntu-latest` (an image with `python3`, `curl`/network access – the build downloads the
|
||
Temurin JDK/jmods and launch4j), and an attachment size limit above ~60 MB (`[attachment] MAX_SIZE`
|
||
in `app.ini`). If the built-in token may not create releases, add a repository secret
|
||
`RELEASE_TOKEN` with write access. Locally the same packages come from
|
||
`./gradlew -PreleaseVersion=<version> packageAll` (output in `build/dist`).
|