Files
llamamod/notes/mod-randomizer.md
T

3.6 KiB
Raw Permalink Blame History

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.