63 lines
3.6 KiB
Markdown
63 lines
3.6 KiB
Markdown
# 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.
|