Skip to content

Latest commit

 

History

History
255 lines (192 loc) · 8.89 KB

File metadata and controls

255 lines (192 loc) · 8.89 KB

Bundling Custom UI with Your Mod

This guide explains how mods can override, extend, or fully replace the RmlUI menus and HUD.

How It Works

The engine uses a layered file search when loading any UI asset (RML documents, RCSS stylesheets, fonts). When RmlUI requests a file, the QuakeFileInterface checks directories in this order:

1. <mod_directory>/<path>    ← highest priority
2. <basedir>/<path>          ← fallback

This means a mod can override any UI file by placing a file at the same relative path inside its own directory. No configuration is needed — the engine finds the mod version automatically.

Quick Start

1. Set up your mod's quake.rc

// quake.rc — minimum
exec default.cfg
exec config.cfg
exec autoexec.cfg
stuffcmds

startdemos demo1 demo2 demo3
ui_show_when_ready

RmlUI menus and HUD are always active when the engine is compiled with USE_RMLUI (the default). No cvars needed to enable them.

2. Override only what you need

Create a ui/ tree inside your mod directory, mirroring the paths you want to replace:

mymod/
├── quake.rc
└── ui/
    └── rcss/
        └── main_menu.rcss       ← custom main menu style

Everything you don't override falls through to the base ui/ directory. You can override a single stylesheet, a single menu, or the entire UI.

3. Run

make run MOD_NAME=mymod
# or
./build/vkquake -game mymod

For a concrete reference, see the in-repo example mod: ui_lab/ (make run MOD_NAME=ui_lab).

Mod Directory Layout

A fully customized mod might look like this (all ui/ entries are optional):

mymod/
├── quake.rc                          # Startup script
├── progs.dat                         # Compiled QuakeC (game logic)
├── qcsrc/                            # QuakeC source (optional)
│   └── ...
└── ui/                               # Custom UI overrides
    ├── rml/
    │   ├── menus/
    │   │   ├── main_menu.rml         # Replaces base main menu
    │   │   ├── pause_menu.rml        # Replaces base pause menu
    │   │   └── credits.rml           # New menu (navigate to it from another menu)
    │   └── hud/
    │       └── hud.rml               # Replaces base HUD
    ├── rcss/
    │   ├── base.rcss                 # Replaces base styles (colors, fonts, etc.)
    │   ├── menu.rcss                 # Replaces menu layout styles
    │   ├── hud.rcss                  # HUD core + default HUD styles
    │   ├── centerprint.rcss          # Centerprint banner styles
    │   ├── notify.rcss               # Notify message styles
    │   ├── chat.rcss                 # Chat input styles
    │   ├── scoreboard.rcss           # Scoreboard overlay styles
    │   └── intermission.rcss         # Intermission/finale styles
    └── fonts/
        └── MyCustomFont.ttf          # Replaces a base font (same filename)

If you want the mod to appear in the in-game Mods menu, make sure the mod directory has at least one of: pak0.pak, progs.dat, csprogs.dat, maps/, or ui/.

Automatic Mod Branding

The {{ game_title }} data binding automatically displays your mod's directory name. If your mod directory is mymod/, the main menu title shows "MYMOD" — no hardcoding required.

<h1>{{ game_title }}</h1>  <!-- displays "MYMOD" -->

What You Can Override

Asset Type Base Path Override by placing in...
Menu documents ui/rml/menus/*.rml mymod/ui/rml/menus/*.rml
HUD document ui/rml/hud/hud.rml mymod/ui/rml/hud/hud.rml
HUD overlays ui/rml/hud/scoreboard.rml, intermission.rml mymod/ui/rml/hud/*.rml
Stylesheets ui/rcss/*.rcss mymod/ui/rcss/*.rcss
Fonts ui/fonts/*.ttf mymod/ui/fonts/*.ttf

Base Stylesheets

File Purpose
base.rcss Reset, typography, color variables, animations
menu.rcss Menu panels, navigation, layout
main_menu.rcss Main menu specific styles
hud.rcss HUD core (overlay, positioning, crosshair, level stats) + default HUD styles (weapon bar, health/armor/ammo corners, keys, powerups)
centerprint.rcss Centerprint banner animations and styles
notify.rcss Notify message line styles
chat.rcss Chat input overlay styles
scoreboard.rcss Scoreboard table overlay
intermission.rcss Intermission stats and finale text
widgets.rcss Form elements (sliders, checkboxes, dropdowns)

HUD

The default HUD is a clean, modern corner-based layout at ui/rml/hud/hud.rml:

  • Bottom-left: Health + armor (with armor-type color coding)
  • Bottom-center: Weapon bar (8 slots — not owned/owned/active states)
  • Bottom-right: Weapon label + ammo count + context-sensitive ammo reserves
  • Top-left: Powerup badges, level stats (kills/secrets), notify messages
  • Top-right: Key indicators (silver/gold)

The HUD links multiple RCSS files (hud.rcss, centerprint.rcss, notify.rcss, chat.rcss). Override any of them individually or replace the entire hud.rml for a fully custom HUD.

Writing Custom Menus

Minimal RML Menu

<rml>
    <head>
        <title>My Menu</title>
        <link type="text/rcss" href="../../rcss/base.rcss" />
        <link type="text/rcss" href="../../rcss/menu.rcss" />
    </head>
    <body data-model="game">
        <h1>{{ game_title }}</h1>

        <button class="btn-primary" onclick="new_game()">
            NEW GAME
        </button>
        <button class="btn" onclick="navigate('options')">
            OPTIONS
        </button>
        <button class="btn" onclick="quit()">
            QUIT
        </button>
    </body>
</rml>

Available Actions

Use these in onclick attributes:

Action Description
navigate('menu') Push a menu onto the stack (ui/rml/menus/<menu>.rml)
command('cmd') Execute a console command (e.g., command('map e1m1'))
close() Pop the current menu
close_all() Close all menus, return to game
new_game() Start a new game
quit() Quit the game
load_game('slot') Load a saved game
save_game('slot') Save the current game
cycle_cvar('name', n) Cycle a cvar value by n

Data Bindings

All RML documents have access to these bindings via data-model="game":

Game State (read-only, updated each frame): {{ health }}, {{ armor }}, {{ ammo }}, {{ shells }}, {{ nails }}, {{ rockets }}, {{ cells }}, {{ map_name }}, {{ level_name }}, {{ game_title }}, {{ game_time }}

Conditional rendering:

<div data-if="has_quad">QUAD DAMAGE!</div>
<div data-if="health &lt; 25">LOW HEALTH</div>

Cvar bindings (two-way, via data-model="cvars"):

<input type="range" data-value="sensitivity" min="1" max="20" step="0.5" />

See DATA_CONTRACT.md for the full list of available bindings.

RmlUI Constraints

A few things to keep in mind when authoring RML/RCSS:

  • rgba() alpha is 0-255, not 0-1 — e.g. rgba(255, 0, 0, 128) for 50% red. Hex-alpha also works (#FF000080)
  • No font-effect: glow() — use outline() or shadow() only
  • Logical operators are C-style — use &&, ||, ! (not and/or/not)
  • XML escaping in attributes&& becomes &amp;&amp;, < becomes &lt;
  • Use position: absolute for HUD overlay elements

See the /rmlui skill reference for workflow details and ../.claude/skills/rmlui/*.md for syntax examples.

Hot Reload for Development

While the engine is running, you can reload UI assets without restarting:

Console Command Effect
ui_reload Full reload — clears document cache, reloads all RML + RCSS
ui_reload_css Lightweight — reloads RCSS only, preserves DOM and data bindings

Workflow: edit your mod's RML/RCSS files, switch to the game, type ui_reload_css in the console.

UI-Related Cvars

Cvar Default Description
scr_uiscale 1.0 UI scale factor (0.5–3.0)

Example: Style-Only Override

The simplest customization — override just the main menu colors without touching any RML:

mymod/
├── quake.rc
└── ui/
    └── rcss/
        └── main_menu.rcss

Your main_menu.rcss is a complete replacement for the base file, so copy the original and modify it.

Example: Custom Main Menu

Replace the main menu layout entirely:

mymod/
├── quake.rc
└── ui/
    └── rml/
        └── menus/
            └── main_menu.rml

Your replacement main_menu.rml can link to base stylesheets (they'll resolve from the base ui/ directory since you didn't override them), or you can bundle your own.