Skip to content

A client mod like the built-in client settings features

Status: Implemented for a client-only mod with a config page, a HUD effect and a keybind. This guide uses the Mods screen’s Config page and a HUD layer. A page in the client’s Appearance screen (Appearance Pages), HUD elements the player can move (HUD Elements) and per-entity render parameters (Post Processing and Camera) are covered on their own pages.

Celestial ships built-in client features in its Client Settings screen (FullBright, block outline, item physics, shield indicators, …). This guide builds a feature of the same kind as a mod, Low Health Warning: a red edge on the screen when health is low, with a toggle key, off by default, and zero cost while off.

Language: Lua. side: client, sync: client_only. APIs: Client integration (mods screen, client settings, toasts, camera), HUD layers, Configuration.

1. Manifest

{
  "schema": 1, "id": "low_health", "version": "1.0.0", "name": "Low Health Warning", "license": "MIT",
  "api": 1, "side": "client", "sync": "client_only",
  "runtime": { "type": "lua", "entry": "scripts/main.lua" }
}

client_only means it works on any server: vanilla, Paper, and older versions through the compat layer.

2. Options: off by default

local config = cl.config.open("client")
config:define_bool("enabled", false)
config:comment("enabled", "Show a red edge when health is low")
config:define_i32("threshold", 6, 1, 19)
config:comment("threshold", "Health (in half hearts) below which the edge shows")

Off by default is the rule for visual tweaks: installing the mod alone must not change the image. The player opts in on the Config page.

3. The effect exists only while enabled

local tick_sub, hud, low = nil, nil, false

local function set_active(on)
  if on and not tick_sub then
    tick_sub = cl.on("tick.client_post", function()
      local p = cl.entity.client_player()
      low = p ~= nil and p.health < config:get_i32("threshold")
    end)
    hud = cl.hud.register_layer{
      id = "edge",
      visible = function() return low end,
      render = function(g)
        local w, h = g:width(), g:height()
        g:fill_gradient(0, 0, w, 12, 0x60FF0000, 0x01FF0000)
        g:fill_gradient(0, h - 12, w, h, 0x01FF0000, 0x60FF0000)
      end,
    }
  elseif not on and tick_sub then
    tick_sub:cancel(); tick_sub = nil
    hud:cancel(); hud = nil
    low = false
  end
end

set_active(config:get_bool("enabled"))
config:on_reload(function() set_active(config:get_bool("enabled")) end)

Colours are 0xAARRGGBB; an alpha of 00 counts as opaque, so the fade ends at 01. While the option is off there is no tick handler and no HUD layer: the mod costs nothing.

4. A toggle key

local key
cl.on("lifecycle.client_setup", function()
  key = cl.keybind.register{ id = "low_health:toggle", default_key = "key.keyboard.unknown", label = "Toggle low health warning" }
end)
cl.on("tick.client_post", function()
  if not key then return end
  while key:consume_click() do
    local on = not config:get_bool("enabled")
    config:set_bool("enabled", on)
    config:save()
    set_active(on)
    cl.client.action_bar(on and "Low health warning on" or "Low health warning off")
  end
end)

Unbound by default (key.keyboard.unknown): the player picks a key in Controls.

5. Checklist (the bar the built-in features meet)

  • [ ] Off by default; no visible change until the player enables it.
  • [ ] Zero cost while off (no subscriptions, no layers).
  • [ ] Work done per tick, not per frame; the frame only draws.
  • [ ] Works on any server (client_only).

Planned

A category in the Client Settings screen with a live preview, render hooks (entity tints, outlines), and camera and FOV events.