Files
llamamod/docs/developers.md

109 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`).