Skip to content

Feature Pause Menu Buttons

Claude edited this page Oct 1, 2026 · 4 revisions

Pause menu buttons — Abandon This Run

Since: v0.333.0 — PR #492

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.

What the player sees

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.

Abandon This Run

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.

Exit to Title / Quit Game

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 in Minecraft.stop() to close to desktop.

How it works

PauseMenuLayoutHandler hooks ScreenEvent.Init.Post on PauseScreen (mirrors TitleScreenLayoutHandler) and, only in singleplayer:

  1. Finds the vanilla button by its translation key menu.returnToMenu ("Save and Quit to Title").
  2. Hides it (visible = false, active = false) and captures its bounds.
  3. 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.

Files

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)

Compatibility notes

  • Abandon is singleplayer only. Gated by Minecraft.hasSingleplayerServer() + the menu.returnToMenu lookup. On a server layoutMultiplayer finds menu.disconnect instead 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); DeathScreenLayoutHandler only 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_VERSION 31 → 32.
  • Robust to other menu mods. If menu.returnToMenu isn't present at Init.Post, the handler bails with a WARN and the menu is left untouched.

Shaders (v0.795.0)

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.

Icon strip — Discord, Videos, Help Translate (v0.846.0)

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.

Clone this wiki locally