3.6 KiB
Randomizer mod (randomizer, GAMEPLAY)
Code: src/main/java/com/eliasmueller/llamamod/mods/randomizer/ (RandomizerMod, Shuffler),
test src/test/.../mods/randomizer/RandomizerModTest (needs -Dllamamod.cd=<cd dir>).
Options
| Key | Type | Default | Effect |
|---|---|---|---|
seed |
SEED | random on first use | same seed = same layout |
coins |
BOOL | on | Coin, Coin5 |
food |
BOOL | on | Health, HealthFull, Grapes, HealthBanana |
mix |
BOOL | on | coins and food share one pool (otherwise each kind only among its own places) |
pots |
BOOL | off | SmashPot(spawn), Jar among their own places |
enemies |
BOOL | off | enemies without patrol path, only among places of the same type |
How it works
Per level WAD t1l[1-8]mNNN.wad: parse MAP (MapChunk, TRAK vertex counts), name every strat from
STPC (Strats.nameOr), sort eligible strats into pools, and permute the places inside each pool:
only fields 0–5 of the 58-byte strat record (rotation x/y/z, position x/y/z) are exchanged. Type
code, parameters, nVars/slots/flags/extra and the waypoint index stay with the strat, so the
multiset of objects in a level and the set of occupied places are unchanged – every pickup ends
on a spot where the original game had a pickup (reachable by construction). MAP is rebuilt with
MapChunk.build(); all other chunks are untouched.
PRNG: java.util.Random(seed * 0x9E3779B97F4A7C15 + levelFileName.hashCode()), pools in MAP order,
Collections.shuffle – both are specified by the JDK, so a seed gives the same result everywhere.
Never moved:
- every type not in the lists above (Llama/start, LevelDoor, CheckPoint*, Camera*, CutScene*, triggers, Force*, switches/doors, RubyIdol (doors depend on it), ChargeBooster, CoinLine/Circle/ RollerCoaster, Secret, platforms …),
- strats with a waypoint (index ≠ −1: patrol paths, secret areas – their coordinates are absolute) and strats with flag bit 1 (spawned later by an event),
- pots within 3 tiles of a
Secret(the village secret is "smash the pot cluster").
Pool sizes (all options on, seed 1): 10–65 pickups per level, up to 14 pots; enemies only in
t1l4m002/3 (VenusFlyTrap, AxeGuard) because almost all enemies have patrol waypoints. Roller
coaster levels (t1l7m001/3/5) and t1l8m006 are unchanged.
Save game: coins per chapter are counted at pickup; the number of coins in a level is unchanged, so the per-chapter totals and the "all coins" flag behave as in the original.
Verified in game (apptest, 2026-11-06)
- Village t1l1m001, seed 1: level loads and plays, no crash (most swaps there are coin↔coin, so the start view looks the same).
- Jungle t1l2m001, seed 2 (chosen because 3 food items land near the start): three of the four coins on
the path ahead of the start are replaced by Health/HealthFull pickups, the coins moved elsewhere
(
img/mods/randomizer-jungle-seed2.jpg); level runs normally. - Unit test (
RandomizerModTest, all 33 WADs): deterministic per seed, different seeds differ, pickup places are an exact permutation, type/params/waypoint fields unchanged, every other strat (Llama, LevelDoor, CheckPoint*, cameras, triggers …) byte-identical, strats with waypoints never move.
Known limits
- Not played to the level exit in game; correctness of the exit/checkpoints rests on them being untouched (unit test).
- A coin that sat in the air in an arc may now be a food item there (still reachable, it was a pickup spot before); first coin parameter (0x1000 vs 0) stays with the strat.
- Enemy shuffle has little effect: almost all enemies have patrol waypoints and are excluded.