95 lines
5.6 KiB
Markdown
95 lines
5.6 KiB
Markdown
# `.llamalevel` – level project format (version 1)
|
||
|
||
A zip file (deterministic: fixed entry times, `level.json` first, then `thumbnail.png`, then the
|
||
assets sorted by path). Code: `editor.io.ProjectIO`, `SceneDiff`, `NodeCodec`.
|
||
|
||
| Entry | Content |
|
||
|---|---|
|
||
| `level.json` | header + the changes against the reference scene (below) |
|
||
| `thumbnail.png` | optional preview (also used for the TestLevel loading picture) |
|
||
| `assets/<name>/…` | imported meshes with their MTL/textures/buffers (`MeshAsset.file` points here) |
|
||
| `assets/pages/<id>.png` | pixels of added texture pages (lossless: ABGR1555 ↔ RGBA with alpha 0/128/255) |
|
||
|
||
**No original game data.** A project is stored as the difference to a *reference scene* that is
|
||
rebuilt from the user's own CD when the project is loaded:
|
||
|
||
* `kind: "edit"` – reference = the original level `base` imported unchanged (`Templates.original`).
|
||
* `kind: "new"` – reference = `base` with all tiles, tile models, objects and waypoints removed and
|
||
visibility regions off (`Templates.newLevel`). The base contributes object code (STPC),
|
||
textures, zones (fog/music), lights and sound. Default base `t1l1m001` (village).
|
||
A newly created project (`Project.create`) additionally removes, as ordinary edits stored in the
|
||
diff, everything bound to the base's positions (`Templates.clearBaseLeftovers`, since 0.2.0):
|
||
all zones except the start zone (flag 8), all non-directional lights, waypoints, and sets every
|
||
region mask to "all". The reference stays unchanged, so projects saved before keep their zones
|
||
and lights. Textures stay (STPC object models index them).
|
||
|
||
Building the same project with the same CD gives a byte-identical WAD (`WadBuilder` is
|
||
deterministic; brushes use the stored `seed`).
|
||
|
||
## level.json
|
||
|
||
```json
|
||
{
|
||
"formatVersion": 1,
|
||
"app": "LlamaMod level editor",
|
||
"kind": "new", // "edit" | "new"
|
||
"base": "t1l1m001", // base level (CD wads/<base>.wad)
|
||
"baseSha256": "9cff…", // of the base WAD; a mismatch gives a load warning
|
||
"name": "Testwelt", // copies of settings for listing without the CD
|
||
"targetChapter": 2, // TestLevel chapter for new levels (0 = none)
|
||
"nextId": 264, // counter for new node ids ("kind:nN")
|
||
"settings": { "<prop>": value, … }, // changed LevelSettings props
|
||
"regions": { "<prop>": value, … }, // changed Regions props
|
||
"nodes": {
|
||
"<kind prefix>": { // chunk, page, tex, anim, model, tile, wp, strat, zone, light, mesh, brush
|
||
"removed": ["tile:12", …], // reference nodes deleted
|
||
"changed": { "strat:11": { "x": 120716, … } }, // changed props of reference nodes
|
||
"added": [ { "id": "strat:n1", "<prop>": value, … } ], // all persistent props (+ "brushKind")
|
||
"order": ["tile:0", …] // only if the list order differs from reference order + added
|
||
}
|
||
},
|
||
"…": … // unknown top-level keys are kept when saving
|
||
}
|
||
```
|
||
|
||
Node kinds, ids and properties: `notes/editor-api.md` (every persistent, settable `Prop`).
|
||
Values by `PropType`: numbers (INT/FIXED/ANGLE/FLAGS/COLOR as integers, DOUBLE), booleans,
|
||
strings, REF = node id (or `null`). Arrays: up to 64 elements as JSON lists, longer ones as
|
||
`{"i32": base64}` (int32 LE), `{"f64": base64}` (float64 LE) or `{"bytes": base64}`; a short double
|
||
array containing -0.0 is written as `f64` too (JSON numbers drop the sign; since 2026-11-07). A *changed*
|
||
array of unchanged length is stored as runs of the changed elements only:
|
||
`{"runs": [[start, values], …]}` (values encoded as above) – e.g. editing a few vertices or
|
||
pixels of an original page stores just those values. Runs apply to the reference value read before any
|
||
property of the node is set (setting a model's vertices clears its header; until 2026-11-07 header runs
|
||
after a vertex change failed with "project file is damaged: NullPointerException", e.g. on
|
||
`llamamod --editor-import` of a levelbuild WAD).
|
||
|
||
Loading: reference scene → removals → create all added nodes (all kinds, so references across
|
||
kinds resolve) → set values of added/changed nodes and settings → apply `order`. Problems
|
||
(reference to a missing node, a removed id that does not exist) become load warnings, not errors.
|
||
Unknown properties of nodes are kept and written back (forward compatibility); a file with a
|
||
higher `formatVersion` loads with a warning.
|
||
|
||
## Ids are positions in the original WAD
|
||
|
||
Reference node ids are `kind:index` of the original WAD, so a project stays valid as long as the
|
||
base level is the same file (checked through `baseSha256`). Deleting reference nodes keeps the ids
|
||
of the others (they are not renumbered in the project), but it does change the *indices* written
|
||
to the WAD: strat scripts that address tiles by index (stMapreplace / stMapadd) may then hit
|
||
other tiles – prefer moving or hiding original tiles over deleting them.
|
||
|
||
## Mods
|
||
|
||
`<data>/levels/*.llamalevel` are listed as mods (`mods.levels.LevelProjectSource`, see
|
||
`notes/mod-levels.md`). CLI:
|
||
|
||
```
|
||
llamamod --editor-import <level.wad|t1l1m001> <project> # edit project (diff to the CD original)
|
||
llamamod --editor-new <project> [base] [--name <name>] # empty new level
|
||
llamamod --editor-build <project> <out.wad> # build (prints statistics, warnings)
|
||
llamamod --import-level <project> # copy into <data>/levels
|
||
(all take --cd <CD folder>; default: the installed original CD of the data folder)
|
||
```
|
||
|
||
Example: `examples/testwelt.llamalevel` (the Test World as 125 brushes + 55 objects, 7 KB).
|