114 lines
8.2 KiB
Markdown
114 lines
8.2 KiB
Markdown
# Llama Coaster (`coaster`, LEVELS)
|
||
|
||
Package `mods/coaster`: `CoasterMod` (options, chapter check, writes `wads/T1L9M0nn.wad` +
|
||
loading picture `T19nn.COR`), `CoasterLevel` (level script: track geometry + objects),
|
||
`CoasterTrack` (layouts → segments, corners, waypoint chain), `CoasterSpeed` (patches the
|
||
RollerCoaster strat's speed constants), `CoasterSim` (frame model of the strat's speed/jump logic,
|
||
used by the tests to check a layout). Tests: `CoasterTest`. Registered in `ModRegistry` (one line).
|
||
Docs: [docs/strats/roller-coaster.md](../docs/strats/roller-coaster.md), manual chapter 18.
|
||
|
||
| Option | Default | Meaning |
|
||
|---|---|---|
|
||
| `track` | `loop` | `loop` (~103 units/lap) or `grand` (~150 units, two jumps, two spin pads) |
|
||
| `laps` | 2 (1–3) | laps before the ride ends at the station |
|
||
| `speed` | `normal` | `slow` ×0.8, `normal` ×1, `fast` ×1.25 (cruise 614 raw and boost 20 raw/frame) |
|
||
| `coins` | on | CoinRollerCoaster along the straights (loop 27, grand 44; placed once, not per lap) |
|
||
| `chapter` | 48 (32–49) | TestLevel chapter; a chapter another mod already wrote → apply error |
|
||
|
||
## How the original rides work
|
||
|
||
**Roller coaster = Catacombs 1, 3, 5 (`t1l7m001/3/5`, world 7 – not world 8; Yzma's lab is
|
||
world 8 and has no ride).** Strat units of t1l7m001: `RollerCoaster` (unit 0, 1775 words),
|
||
`CameraFixedPointBox`, `RollerDoor`, `CoinRollerCoaster`, `WompyRollerCoaster`,
|
||
`FireworksTrigger`, `Camera`, `SpawnableRing`, `Sparkles`, `SpawnableNeedleBall`.
|
||
|
||
* **The cart is the player.** There is no `Llama` strat: `RollerCoaster` calls `isPlayer()` and
|
||
`lookAtMe()`, uses `model:0` (cart with Kuzco-llama and Pacha, scale 2) and its animations
|
||
(lean, cheer, look back, fall). The llama is part of the cart model, not attached at runtime.
|
||
* **Camera:** the strat spawns the shared `Camera` strat (`spawn<4,41,2,0x248000,0>(0.9, 1.0, …,
|
||
unit:Camera)`), the same chase camera as the Llama's; it follows `g29` = the cart's heading,
|
||
which the strat sets to the next waypoint's heading in corners (so the camera swings round
|
||
before the cart). `CameraFixedPointBox` (one per level) is a fixed-point camera zone.
|
||
* **Track following = waypoint chain + floor.** At start (`L_0200`) every waypoint x/z is rounded
|
||
to halves and its heading to a quarter turn (param2). The cart drives straight along its heading
|
||
at speed `v19` (min 0.15 = 614 raw), the floor height comes from the collision floor below it
|
||
(`noFloorPhysics`, `self.y = floorHeight`; a climb steeper than ~0.1 per frame loses the
|
||
floor). Within 0.75 in front of the current waypoint it turns by 90° on a small arc round the
|
||
waypoint (funnel: lateral offset clamped to 1.2·distance) and switches to the next one – so the
|
||
tracks are straight legs on the grid with 90° corners; only the geometry makes slopes.
|
||
Left/right input shifts the cart sideways (`v18`, ±half the speed); the rails are collision walls.
|
||
* **Floor materials** (collision material of the track bed): 5 boost (+20 raw speed and
|
||
"excess" per frame, decays back to cruise), 11 jump (vertical speed 163 raw, gravity 14 raw per
|
||
frame), 13 spin (small hop + half turn: the cart rides backwards until the next corner), 15 an
|
||
animation/sound pad, 16 the end (only when the current waypoint is the last one), 30 the
|
||
invisible death plane far below (fall → restart).
|
||
* **Start:** the first two waypoints have param 1.0 (station): the cart crawls (0.025 + nudges)
|
||
out of the station with letterbox borders and the start line, then picks up cruise speed.
|
||
* **End:** on material 16 with the last waypoint active the strat plays a sound and after 30
|
||
frames calls `endLevel(param1..3)` – the cart's own params 1–3 are the target (tribe, world,
|
||
chapter) → tally screen. `RollerDoor` are gates the cart passes, `FireworksTrigger` fires rockets
|
||
at its waypoint spots when the cart passes, `WompyRollerCoaster` is a spinning Wompy pick-up.
|
||
|
||
**River = Jungle river 1–4 (`t1l3m001…4`, world 3; world 2 is the jungle at night).** The player
|
||
strat is `KuscoAndPachaTogetherOnATree` (both on a log, also `isPlayer()` + the shared `Camera`).
|
||
It also rounds its waypoints to halves/quarter turns and turns at waypoints whose param is 1 or 2
|
||
(left/right), but it floats on water (`floorMaterial 5` = water there: rumble, splash particles)
|
||
and is pushed by current/force zones (`RiverCurrentBox`, many "kraft" zones); the player steers
|
||
freely between the banks, `CameraLift`/`RiverWaterfallCamera` give the scripted shots, and the
|
||
level ends at a normal `LevelDoor`. It is a steerable boat, not a rail ride, so the coaster mod
|
||
uses the RollerCoaster strat.
|
||
|
||
## Design
|
||
|
||
* **Base level = t1l7m001** (not the village + import): its STPC already has every unit the ride
|
||
needs and their textures, sounds and the ride music; importing `RollerCoaster` + `Camera` into
|
||
the village would only duplicate them. The original geometry and objects are replaced by
|
||
`LevelBuilder` output (`LevelScript.buildWad`), so `format.stpcmerge` is only used read-only
|
||
(`StpcImage`/`StratCode`) by `CoasterSpeed`.
|
||
* **Track:** straight legs along the axes, 2 wide (two 1-wide lanes, centre line on integers),
|
||
joined by flat 2 × 2 corner squares; heights change only on straights. Pieces: TRACK, BOOST (mat
|
||
5), JUMP (11), SPIN (13), END (16), STATION, GAP (no floor). Rails 0.35 high (inner face =
|
||
collision), underside, pillars every 5 units and under corners, a boarding platform with posts
|
||
left of the station, ground plane at y = 0 with material 30. Textures are t1l7m001 TEXT indices.
|
||
* **Leg 0** of every layout: run-in, END pad, brake run, STATION (5), first drop with a boost at
|
||
its top. The circuit must close (checked) and END must come before STATION on leg 0.
|
||
* **Waypoint chain** (`CoasterTrack.nodes`): node 0 one unit behind the cart and node 1 at the
|
||
station's end, both param 1.0 (crawl); then one node per corner, 0.5 before its centre, heading
|
||
after the turn, repeated per lap; last node 9 units past the END pad on leg 0, so the END pad is
|
||
live only in the last lap. RollerCoaster params = (0, 1, 9, chapter) ×4096 → the tally restarts
|
||
the ride, like the arena.
|
||
* **Speed** (`CoasterSpeed`): finds the six `pushShort 614` (cruise floor/cap) and two
|
||
`pushShort 20` (boost, after `pushVar v19/v22`) in the RollerCoaster unit with `StratListing`
|
||
and rewrites the 16-bit operands; nothing else changes.
|
||
* **Checking a layout offline** (`CoasterSim`, test `everyJumpLandsAndNoClimbLosesTheFloor`):
|
||
for every track × speed × laps every jump must clear its gap and land before the commit zone of
|
||
the next corner, no climb > 0.1 per frame, speed stays 0.02–0.45.
|
||
* Objects: `RollerCoaster` at the station start, a `RollerDoor` at the station exit, a
|
||
`FireworksTrigger` before the END pad with six rocket spots, CoinRollerCoaster every 1.5 units
|
||
on straights ≥ 4 long outside leg 0 (on a slight zig-zag). The tally total (LNFO) is the coin count.
|
||
* Loading/tally picture: drawn in Java (dark purple, a rail curve, title "Llama Coaster" in the
|
||
band y 265–335 that the game's tally text leaves free).
|
||
|
||
## Verified in game (apptest, :26, 2026-11-07)
|
||
|
||
* loop/normal 1 lap and 2 laps, loop/fast 1 lap, grand/fast 1 lap (chapter 40), grand/slow:
|
||
station crawl with letterbox and start line, drops, climbs, boost arrows, start hint "Kusco, bleib in der
|
||
Mitte der Kurve und wir werden uns automatisch umdrehen!", spin pad (cart turns backwards), jump over the gap and
|
||
landing, corners, coins counted (e.g. 14/27, 19/44), END pad → fireworks → tally with the
|
||
loading picture. No crash in any run.
|
||
* After the tally: **Enter** restarts the ride (same chapter); **Esc** asks "Möchtest du das
|
||
Spiel beenden?" (No → back to the tally). In the ride, Esc → *Beenden und zur Auswahlkarte
|
||
wechseln* → Yes → world-9 map on "TestLevel Kapitel 48". Same behaviour as the arena.
|
||
|
||

|
||

|
||

|
||
|
||
## Limits
|
||
|
||
* Only 90° corners on the grid and straight slopes (the strat cannot follow curves or banking);
|
||
no loops/inversions (the cart has no roll).
|
||
* The tally coin count is not kept on the world map (it showed 0 after the ride) – same as the
|
||
other TestLevel mods; not investigated.
|
||
* The original levels' sky/fog/lighting of t1l7m001 stays (dark catacombs, fog end 23 cells).
|