Repository navigation
Feature Pause Menu Buttons
Reframes the singleplayer pause menu (Esc) around Dungeon Train's roguelike loop. The vanilla "Save and Quit to Title" slot becomes a single red "Abandon This Run" button; the normal exits move behind Shift.
| State | Button(s) in the slot |
|---|---|
| Default | Abandon This Run (red, full width) |
| Hold Shift | Exit to Title (grey) | Quit Game (dark grey) — 50 / 50 |
On a server the vanilla Disconnect button keeps its slot (there is no run to abandon from the client) and holding Shift swaps it for the same Exit to Title | Quit Game pair (PR #1741, v0.1091.0); LAN hosts have an integrated server and get the singleplayer layout. Every other pause-menu button (Back to Game, Advancements, Options, …) is unchanged.
Press Esc and the old "Save and Quit to Title" is now a red Abandon This Run. Click it and your run ends immediately — the player is killed and the narrative death screen opens, exactly as if you'd died in-world (same recap, Board anew / Leave the line). Hold Shift and the red button splits into the familiar exits: Exit to Title (saves and returns to the title screen) and Quit Game (saves and closes Minecraft to desktop). Release Shift and the red Abandon button returns.
The button closes the pause screen first — in singleplayer that unpauses the integrated server so it can act on the request — then sends the serverbound AbandonRunPacket. The server kills the sender with player.kill() (generic kill — bypasses invulnerability, so it ends a creative / Free Play run too). That fires the normal LivingDeathEvent, so RunStatsEvents builds and sends the DeathStatsPacket and the client opens the death screen — there is no new death plumbing, it reuses the existing in-run death flow.
Both Shift-revealed exits save the world and reuse the same Sable-safe teardown the death screen's Board anew uses, so they don't trip the Sable issue #679 "Saving world…" shutdown spin (the drain runs on ServerStoppingEvent via ShipShutdownEvents):
-
Exit to Title →
DeathScreenLayoutHandler.goToTitleScreen()— pre-drain, disconnect (saves), return to title. Skips vanilla's "Are you sure you want to give up?" confirm. -
Quit Game →
DeathScreenLayoutHandler.quitToDesktop()— the same teardown, but ends inMinecraft.stop()to close to desktop.
PauseMenuLayoutHandler hooks ScreenEvent.Init.Post on PauseScreen (mirrors TitleScreenLayoutHandler) and, only in singleplayer:
- Finds the vanilla button by its translation key
menu.returnToMenu("Save and Quit to Title"). - Hides it (
visible = false,active = false) and captures its bounds. - Lays three
PauseMenuActionButtons over the slot: Abandon (full width), Exit-to-Title (left half), Quit Game (right half).
If menu.returnToMenu can't be found (a third-party mod rewrote the menu), the handler logs a WARN and leaves the menu untouched rather than half-modifying it.
PauseMenuActionButton is a tinted Button (same sprite-tint trick as DarkTintedButton) carrying a visibleWhenShift flag. Because AbstractWidget.render is final and can't be overridden, the Shift swap is driven by a ScreenEvent.Render.Pre pass in the handler that, each frame, sets every button's visible to hasShiftDown() == button.visibleWhenShift(). visible gates both rendering and click handling, so the Abandon button and the Exit/Quit pair swap cleanly as Shift is pressed and released.
| Path | Purpose |
|---|---|
PauseMenuLayoutHandler.java |
Find/hide the vanilla button, lay the three buttons, Shift-swap render pass |
PauseMenuActionButton.java |
Tinted, Shift-gated Button subclass |
AbandonRunPacket.java |
Serverbound kill request → player.kill()
|
DeathScreenLayoutHandler.java |
goToTitleScreen() (Exit) + quitToDesktop() (Quit) |
assets/dungeontrain/lang/en_us.json |
gui.dungeontrain.abandon_run, gui.dungeontrain.exit_to_title (Quit Game reuses vanilla menu.quit) |
-
Abandon is singleplayer only. Gated by
Minecraft.hasSingleplayerServer()+ themenu.returnToMenulookup. On a serverlayoutMultiplayerfindsmenu.disconnectinstead and lays the Shift pair over it, with Disconnect as the un-Shifted face (the same treatment the Train Editor world gives Save and Quit);DeathScreenLayoutHandleronly shows "Saving level" on the way out when an integrated server exists. -
Sable. The Exit / Quit teardown is the same drained-shutdown path as the death screen's Board-anew, verified clean against Sable #679. Client-only handler (
@EventBusSubscriber(value = Dist.CLIENT)). -
Networking. Adds one serverbound packet →
DungeonTrainNet.PROTOCOL_VERSION31 → 32. -
Robust to other menu mods. If
menu.returnToMenuisn't present atInit.Post, the handler bails with aWARNand the menu is left untouched.
PR #1298 adds Shaders beside NeoForge's
Mods button, halving that slot rather than inserting a row — so it needs to know nothing about the
rest of the pause menu's layout, and adds nothing if Mods is absent. It opens the same
Shaders page the title screen does, and appears in multiplayer too: a shader
pack is a client-side choice and has nothing to do with whose world you are in.
PR #1369 lays the title screen's icon column
flat on the right end of the Mods | Shaders row: Discord (the invite, through the vanilla
confirm-link screen), Videos (the community videos page, in-game) and
Help Translate — the translation editor opened over the world, so a
translator can work on the game's text without leaving the train. Mods and Shaders keep their
labels and split what is left of the row; the icons are 18px, centred on the 20px row.
The translate pencil follows the same rule as its title-screen and Language-screen siblings: hidden on
en_us in a release build (the source language has nothing to translate into), and pointed at the dev
target locale on a dev build. Opening it in-world also runs the once-per-session relay housekeeping the
title screen normally does — outbox flush, approved-pool / coverage / credits fetches — for a client that
launched straight into a world. The three translation screens (list, edit, submit) lay a 50%-black tint
over vanilla's blurred in-world background so their text stays readable over a moving train; at the title
screen they render as before.
With Discord moved into the strip, Support the Mod takes the whole Feedback | Report Bugs row on
its own. The strip is added by client.menu.PauseMenuIconRow at EventPriority.LOWEST (after
PauseMenuLayoutHandler has halved the Mods slot), skips Train Builder worlds (their pause menu has
its own Discord button under the same label), the widgetless F3+Esc pause, and any menu where Mods or
Shaders cannot be found — no orphan icons.