Feature Main Menu Buttons - bh679/dungeon-train-mc GitHub Wiki

Main menu buttons β€” Train Editor, Video Tools + the icon column

Since: v0.144.0 β€” PR #185 Icon column + Bilibili: PR #1042 (v0.615.0) Shaders in the bottom row: PR #1298 (v0.795.0) β€” the row went four-way, and vanilla's "Quit Game" / "Options…" lost their extra words to fit.

Adds new buttons to the vanilla Minecraft title screen, reshuffling the existing layout to make room:

Slot Before (vanilla / NeoForge) After
Mods row (full width) Mods (NeoForge) Train Editor | Video Tools (50 / 50)
Bottom row Options | Quit Game (50 / 50) Mods | Shaders | Options | Quit (25 / 25 / 25 / 25)
Icon column (above the accessibility icon) β€” Bilibili (Chinese clients only) / Discord / Credits

Note: Discord began life as the text button in the Mods row described below. It now rides the icon column as a logomark, and Video Tools holds that slot. The Train Editor sections on this page are still current.

The Singleplayer / Multiplayer / Realms column and the language / accessibility icons stay where vanilla puts them. The main menu Dungeon Train wordmark and the Dev TitleScreen New-World shortcut (Shift swap) both continue to work unchanged.

What the player sees

A new "Discord" button on the title screen β€” click it, confirm the vanilla "Open Link in Browser?" prompt, and the system browser opens to the project Discord invite. A new "Train Editor" button next to it β€” click it and a four-tile picker asks what you want to build; pick a tile and the game loads a fresh creative-mode world on the Dungeon Train default preset and drops the player into that part of the editor.

The icon column β€” Bilibili / Discord / Credits

A stack of 20Γ—20 icon buttons owned by TitleScreenCreditsButton, anchored above the vanilla accessibility icon (or above the menu-chat envelope when that is showing) and stacking upward: Credits (a book), Discord one slot up, Bilibili one slot above that.

All three marks are drawn programmatically rather than shipped as textures β€” see DiscordIconButton, BilibiliIconButton, CreditsIconButton in client/menu/.

Clicking Discord or Bilibili opens vanilla ConfirmLinkScreen. Yes β†’ Util.getPlatform().openUri(URI) opens the system browser, then returns the player to the title screen. No / Cancel β†’ returns to title without launching anything. Standard Mojang-blessed pattern, same UX as the vanilla "Realms website" link.

URLs are not hardcoded at the call site. Both read OfficialLinks at click time: baked fallbacks ship in the jar so the buttons work offline, and one anonymous relay fetch per session can overlay a rotated link β€” so a new invite reaches already-shipped jars with no patch release.

Bilibili β€” Chinese-language clients only

The Bilibili icon appears only when the client's selected language is in the zh family (zh_cn, zh_tw, zh_hk, and Classical Chinese lzh), via ClientLanguage.isChinese(). Every other locale sees the column exactly as before: Discord + Credits.

Discord is blocked in mainland China, so for those players the icon below it is a dead end; Bilibili is the community link that actually opens.

Two deliberate choices worth knowing:

  • The gate is language, not location. Minecraft exposes no country. A Chinese-language client anywhere qualifies; a mainland player running an English client does not. It is the best signal available and errs toward offering a working option to whoever is most likely to need one.
  • Added, not substituted. Bilibili sits above Discord rather than replacing it β€” because the gate is language, a Chinese-language client outside China would otherwise lose a link that works fine for them.

The same wide zh gate decides the China payment route on the Support screen; the narrower Simplified-only test that removes Patreon and Revolut is separate on purpose (PaymentLinks.isSimplifiedChineseLocale) β€” one decides what to offer, the other what to take away.

Train Editor button

A "pick what you're building, get a fresh creative world in that part of the editor" shortcut for content authoring. Three steps:

  1. The picker (since PR #1265, v0.776.0) β€” clicking the button opens TrainBuilderScreen, the same four-tile picker the Train Builder uses, titled Train Editor. Since PR #1405 (v0.877.0) it has the Nav tab's shape: the four tiles fill the left column, and the right column shows the picked tile's name, its picture and a short description, with Go here at the bottom and Back on the same row under the tiles. Clicking a tile only picks it (no hover lighting, no tooltips β€” only the chosen tile is lit); nothing is created until Go here is pressed β€” Back returns to the title screen having made no world. Geometry is BuilderPickerLayout, unit-tested across GUI scales 1–4. Each tile stands for one editor category:

    Tile Opens
    Train Outside carriages β€” the carriage shells
    Inside Carriage contents β€” what fills a carriage
    Tracks & Tunnels tracks β€” track tiles, pillars, tunnels
    Dimensional Carriages portals β€” the rooms behind a portal

    The mapping lives in BuilderModeCategory. Before this, every editor launch ran a bare /dungeontrain editor and always landed in Carriages, whatever the author actually meant to work on.

  2. Fresh creative void world β€” same code path as the Dev TitleScreen New-World shortcut. Calls Minecraft.createWorldOpenFlows().createFreshLevel(...) with GameType.CREATIVE, Difficulty.NORMAL, allowCommands=true, a random seed, and the editor's quiet game rules baked in. Since PR #1281 (v0.788.0) the preset is dungeontrain:dungeon_train_editor β€” a void, overworld-only world with no train (PendingWorldChoices is armed with startsWithTrain=false, the Train Builder's seam) β€” rather than the full dungeontrain:dungeon_train world it used to build and never look at. See Development β€” The editor world for its shape. Tile click β†’ standing on a plot went from ~40 s to ~4 s.

  3. Auto-enter editor β€” once the player has fully spawned in (server started + mc.player/mc.level/mc.getConnection/mc.gameMode all non-null + a 10-tick settle delay, down from 40 in #1281 now there is no train bootstrap to wait out), the handler runs /dungeontrain editor <category> via the same dispatch path the X-menu's Editor entry uses. Two chat lines confirm the dispatch (Β§7[Train Editor] auto-running /dungeontrain editor portals + Β§e[Train Editor] please wait a few seconds…).

EditorDevMode.queueOnForNextStart() is armed before the world starts, so the post-load ServerStartedEvent re-asserts EditorDevMode.set(CarriageTemplateStore.sourceTreeAvailable()). In a dev checkout (./gradlew runClient) the source tree is writable β†’ dev mode lands ON; in a packaged jar install with no writable source tree the override is a no-op β†’ dev mode stays OFF, so the status display and dev-only overlay codepaths remain honest for end users who happen to click the button.

How it works

TitleScreenLayoutHandler hooks ScreenEvent.Init.Post on TitleScreen and:

  1. Finds the three vanilla / NeoForge buttons by their translation keys (fml.menu.mods, menu.options, menu.quit).
  2. Captures the Mods button's bounds β€” that's where Video Tools + Train Editor will live.
  3. Resizes Mods / Options / Quit Game to thirds across the row Options + Quit Game used to share.
  4. Builds two new buttons sized to halves of the captured Mods slot and adds them via event.addListener.

If any of the three vanilla buttons can't be located (third-party mod has already rewritten the menu), the handler logs a warning and skips both the layout reshuffle and the new buttons β€” leaves the menu untouched rather than half-modifying it.

EditorAutoOpenHandler is a one-shot client tick handler armed by the Train Editor button. On every ClientTickEvent.Post while armed, it waits for mc.player, mc.level, mc.getConnection, and mc.gameMode to all be non-null, then counts down 40 ticks before dispatching /dungeontrain editor <category> — the category the picker chose, carried across the world load on the client because the server has no record of which tile was clicked — via mc.player.connection.sendCommand (the same path the chat screen uses for typed commands, so any server-side feedback lands in chat too). The settle delay matters: dispatching during the configuration→play phase transition was firing too early on some loads.

Files

Path Purpose
TitleScreenLayoutHandler.java Layout reshuffle + button installation
EditorAutoOpenHandler.java One-shot post-load editor dispatcher
EditorDevMode.java queueOnForNextStart() + ServerStartedEvent re-derive (gated on sourceTreeAvailable())
DevQuickWorldHandler.java launchFreshWorld(Screen) (visibility relaxed to package-private to share with the Train Editor button)
TitleScreenCreditsButton.java The icon column: Credits + Discord + Bilibili, one anchor computation
BilibiliIconButton.java The Bilibili mark, drawn programmatically
ClientLanguage.java isChinese() β€” the one definition of the wide zh gate
OfficialLinks.java Baked fallbacks + relay overlay for the Discord / Bilibili URLs
assets/dungeontrain/lang/en_us.json gui.dungeontrain.discord_button, gui.dungeontrain.bilibili_button, gui.dungeontrain.editor_button

Compatibility notes

  • No interaction with Sable / Valkyrien Skies. TitleScreen-only; no block/collision/entity touch points. Dedicated server is unaffected (@EventBusSubscriber(value = Dist.CLIENT)).
  • Robust to other mods modifying the menu. If any of the three vanilla buttons isn't found at Init.Post time, the handler logs a WARN and bails β€” neither the reshuffle nor the new buttons are installed. The player keeps the menu the other mod produced; they just don't get Train Editor / Discord.
  • The [Train Editor] chat lines are visible only after the world has loaded β€” the auto-open dispatch posts them to the in-world chat overlay, not the title screen.

Tuning

Constant File Effect
FALLBACK_DISCORD / FALLBACK_BILIBILI OfficialLinks.java Baked link fallbacks. Rotating either for shipped jars is a relay change (links.js DEFAULTS), not a mod patch β€” keep the two in lock-step.
GAP TitleScreenLayoutHandler.java Pixel gap between buttons in the row reshuffle (default 4).
DISPATCH_DELAY_TICKS EditorAutoOpenHandler.java Settle delay between "player ready" and command dispatch (default 40 ticks = 2s). Bump if commands fire before chunks finish loading.
⚠️ **GitHub.com Fallback** ⚠️