Skip to content

An HTML/CSS HUD overlay

Status: Implemented (API 1.22). A HUD overlay written as an RmlUi document (RML markup and RCSS styles) with a data model the mod updates. The player places it in the HUD editor; it follows their Appearance. The complete mod is sdk/examples/html_hud_lua.

Reference: HTML/CSS Overlays and Panels, rml, HUD Elements.

1. The files

mana_hud/
  clight.mod.json
  scripts/main.lua
  assets/mana_hud/gui/mana.rml
  assets/mana_hud/gui/mana.rcss

2. The document

data-model="mod" binds the document to the variables the mod declares. {{ }} shows a value, data-style-width sets a style from an expression, data-for repeats an element for every list entry and data-if shows one only sometimes.

<rml>
<head>
    <link type="text/rcss" href="celestial:theme.rcss"/>
    <link type="text/rcss" href="mana.rcss"/>
</head>
<body data-model="mod" class="cl-root">
    <div class="box cl-hud-panel">
        <div class="row">
            <span class="cl-small">Mana</span>
            <span class="value cl-small">{{ mana }} / {{ max }}</span>
        </div>
        <div class="track"><div class="fill" data-style-width="(mana / max * 100) + '%'"></div></div>
        <div class="spells" data-if="spells.size > 0">
            <span class="spell cl-small" data-for="s : spells" data-class-ready="s.ready">{{ s.name }}</span>
        </div>
    </div>
</body>
</rml>

celestial:theme.rcss gives cl-hud-panel and the text classes the player’s style: the rounded HUD panel and the UI font in Fancy, the translucent box and the game font in Vanilla. Your own sheet only does the layout:

.box { display: block; width: 100%; height: 100%; padding: 3px 4px; }
.track { display: block; height: 3px; margin-top: 2px; background-color: #00000080; }
.fill { display: block; height: 3px; background-color: #3FA9F5; }
.cl-fancy .fill { background-color: #7FC8FF; }
.spell { margin-right: 4px; color: #a0a0a0; }
.spell.ready { color: #ffffff; }

3. Open it as an overlay

local doc
cl.on("lifecycle.client_setup", function()
  doc = cl.rml.open{
    path = "assets/mana_hud/gui/mana.rml",
    as_screen = false,                 -- a HUD overlay
    id = "mana", label = "Mana bar",   -- its name in the HUD editor
    anchor = "bottom_left", x = 4, y = 4,
    width = 110, height = 34,
    model = { "mana", "max", "spells[]" },
  }
  doc:set("max", 20)
  doc:set("mana", 20)
  doc:set("spells", { { name = "Blink", ready = true }, { name = "Heal", ready = false } })
end)

The overlay is a HUD element of your mod: the player moves it, scales it and hides it in the HUD editor (it is listed under your mod’s name), and F1 hides it. It never takes input and draws under every screen.

4. Update only what changes

Set a value when it changes, not every frame: only the parts of the document bound to that variable update.

local mana, ticks = 20, 0
cl.on("tick.client_post", function()
  ticks = ticks + 1
  if ticks % 20 == 0 and mana < 20 then
    mana = mana + 1
    doc:set("mana", mana)
  end
end)

5. What a document may load

Only files of your mod (relative to the document), resource ids for images (mana_hud:textures/gui/star.png) and celestial:theme.rcss. URLs, absolute paths, .., scripts and event attributes are refused when the document opens: cl.rml.open raises permission_denied with the reason. Logic stays in your mod: buttons in screens and panels call actions you list (actions = { "cast" }, data-event-click="cast('Heal')", on_action = function(doc, action, name) ... end).

6. The same document as a panel or a screen

Leave as_screen at true for a screen of its own, or pass parent = <one of your screens> with a box to show it as a panel inside that screen. Screens and panels take input; the data model and the actions work the same way.