Repository navigation
Feature Starter Train
Since: v0.15.0 — PR #19
v0.27.0 — PR #37: train assembled at world-gen time; player spawn avoids water and the void.
v0.83.0 — PR #120: login spawn rewritten — cached placement + PlayerFudgeSpawnMixin deliver the player at the final position from frame 1 (no visible jump). Now its own feature page; see Login spawn placement for the full pipeline.
The mod starts itself — no /dungeontrain spawn needed. When the server starts, if no Dungeon Train is present, a 10-carriage train is auto-assembled at (0, trainY, 0) before any player joins, so the world's first frame already contains the train. Every joining player (including the first) is then placed off-corridor, on natural ground, looking at the nearest above-ground stretch of track — see the dedicated Login spawn placement page for that pipeline.
TrainBootstrapEvents subscribes to ServerStartedEvent with EventPriority.LOW so the client-side WorldLifecycleEvents handler commits any pending Create-World-screen choices (trainY, dims, startsWithTrain) into DungeonTrainWorldData first. The bootstrap then:
- Reads per-world
DungeonTrainWorldDatafrom the overworld. - Skips if
startsWithTrain=falseor any carriage of a Dungeon Train already exists. - Otherwise calls
TrainAssembler.spawnTrain(level, (0, trainY, 0), TRAIN_VELOCITY, 10, origin, dims). Internally this delegates tospawnGroupto place a seed group of carriages with half-flatbed pads on each end (see Feature-Train § Per-carriage architecture for the full mechanism). The append-onlyTrainCarriageAppenderextends from there once a player gets within range. - Calls
PlayerJoinEvents.computeAndCacheBootstrapPlacement(...)to compute and cache the player's eventual login spawn position, then writes it to the level's default spawn point. This is what lets first-time players land at the cached placement directly via thePlayerFudgeSpawnMixin(no visible vanilla-spawn flash).
Common-scope (no @Dist guard), so dedicated servers also auto-spawn — previously the train only appeared on first player login.
The full pipeline (cached placement, mixin, buffered-X scan, perpendicular walk, on-train-roof last resort, leaves rejection, no-water guarantee) lives on its own page: Login spawn placement.
For this page, the short version: every joining player is placed off-corridor on natural ground within ~50 blocks of the train, facing the nearest above-ground stretch of track. The placement is computed at ServerStartedEvent time (right after the bootstrap above) and cached, so the very first frame the player sees on a fresh world is already correct — no (0, 64, 0) flash, no visible teleport, no rotation snap.
The spawn altitude is a server-scoped Forge config value (same config file as numCarriages / speed):
| Field | Range | Default |
|---|---|---|
| Train Y | [-64, 320] |
78 |
Default 78 is ~15 blocks above vanilla sea level (63) — high enough to clear most coastline terrain but low enough that the train visibly plows through hills and shorelines. Editable via Esc → Mods → Dungeon Train → Config (new "Train Y:" row) or directly in <save>/serverconfig/dungeontrain-server.toml:
[train]
trainY = 78Applies to next spawn only. Changing the config doesn't move an existing train — its Y is baked into the ship when it was assembled. To apply a new Y: delete the current train (e.g. /dungeontrain spawn 10 re-assembles) or rejoin a world where no train is loaded.
The existing /dungeontrain spawn [count] command keeps working — it still spawns 10 blocks in front of the player's look direction, independent of trainY. Treat auto-spawn as the default path and /dungeontrain spawn as the admin/testing override.
-
TrainBootstrapEvents.java—ServerStartedEventhandler that assembles the starter train + caches the login placement, before any player joins -
PlayerJoinEvents.java— login retry queue, cached-placement check, fallback pipeline (see Login spawn placement) -
PlayerFudgeSpawnMixin.java— overridesServerPlayer.adjustSpawnLocationto skip vanilla's spawn-radius search -
DungeonTrainConfig.java—TRAIN_YIntValue+getTrainY/setTrainY -
DungeonTrainSettingsScreen.java— "Train Y:" edit row with signed-integer filter
-
Starting dimension is configurable per world. The bootstrap runs in whichever dimension the world's
startingDimensionsays (default Overworld). See Feature-World-Type for the Nether / End starting-dimension flow. -
Login event only. Respawning after death fires
PlayerRespawnEvent, which is not hooked — the player respawns at their bed or world spawn. The level's default spawn point is the cached placement, so dying without a bed lands the player back on natural ground next to the train. -
No cross-dimension teleport on login. If the player's last-logged-in dimension differs from
startingDimension, they stay where they were. Teleporting them across dimensions on every login would feel invasive. -
Placement is fixed at server start, not per-login. The cached placement is computed once at
ServerStartedEventfrom the train's then-current position. By login time the train has moved a few blocks; the appender extends the train back over the cached placement, so the player still spawns next to a carriage. Long server uptimes between bootstrap and first login are fine — the train is append-only. - On-train-roof last resort in tiny-island biomes. If neither the buffered-X scan nor the perpendicular walk (capped at 60 blocks) finds dry ground, the player is placed on top of the carriage roof rather than underwater on a seabed. See Login spawn placement § Fallback chain for the full ladder.