Skip to content

A HUD overlay

Status: Implemented. Mod layers are drawn after the vanilla HUD (ordering among vanilla layers, and moving or replacing vanilla layers, are Planned).

Goal: a small panel in the top-left corner with the player’s coordinates and the number of rubies they carry, hidden while F1 is on (automatic) and when the player has none.

Language: Lua, with C++ at the end. APIs: HUD layers, Screens, widgets and 2d drawing.

1. Cache on the tick, draw on the frame

A HUD layer runs every frame (hundreds of times a second); the values change at most once per tick (20 times a second). So compute on the client tick and only draw in the layer:

local lines = nil

cl.on("tick.client_post", function()
  local p = cl.entity.client_player()
  if not p then lines = nil return end
  local n = p:count_item("my_mod:ruby")
  if n == 0 then lines = nil return end
  local pos = p.pos
  lines = {
    ("XYZ: %d %d %d"):format(math.floor(pos.x), math.floor(pos.y), math.floor(pos.z)),
    ("Rubies: %d"):format(n),
  }
end)

cl.hud.register_layer{
  id = "ruby_panel",
  visible = function() return lines ~= nil end,
  render = function(g)
    local h = #lines * 10 + 4
    g:fill(2, 2, 110, 2 + h, 0x80000000)               -- half-transparent black
    for i, line in ipairs(lines) do
      g:text(line, 5, 5 + (i - 1) * 10, 0xFFFFFF, true)
    end
  end,
}

2. What makes this cheap

  • visible returns false when there’s nothing to show, so render isn’t even called.
  • The tick handler does the counting; the frame only draws two lines of text.
  • Without any registered layer, the HUD hook costs one load per frame.

3. C++

class PanelMod final : public clight::Mod {
    std::string coords_, rubies_;
    bool show_ = false;
public:
    void on_client_setup() override {
        clight::on<clight::ev::TickClientPost>([this](cl_ev_tick&) {
            clight::Entity p = clight::entity::client_player();
            const int32_t n = p ? p.count_item("my_mod:ruby") : 0;
            show_ = n > 0;
            if (!show_) return;
            const cl_vec3 v = p.pos();
            coords_ = std::format("XYZ: {} {} {}", int(std::floor(v.x)), int(std::floor(v.y)), int(std::floor(v.z)));
            rubies_ = std::format("Rubies: {}", n);
        }).detach();
        clight::hud::add_layer("my_mod:ruby_panel", [this](clight::Gui g, float) {
            g.fill(2, 2, 110, 26, 0x80000000);
            g.text(coords_, 5, 5, 0xFFFFFF, true);
            g.text(rubies_, 5, 15, 0xFFFFFF, true);
        }, {.visible = [this] { return show_; }});
    }
};
CLIGHT_MOD(PanelMod)

Planned

Placing a layer between vanilla layers (above = "minecraft:food"), hiding a vanilla layer to replace it, and wrapping a vanilla layer to move or tint it.