styles
Register custom looks for HUD elements: style spec, element functions and their row data, the style ctx, style settings, errors, lifetime and style handles.
A script gives a HUD element a look of its own. The element keeps every decision - which rows it shows, in which order, with which tone, numbers and timing - and hands the style a read-only snapshot of them each frame; the style draws it. The user picks a style per element: in the right-click menu of the element's HUD window (Style) and in the element's own settings ("..."). Without a style chosen, or while it cannot draw, the element draws its built-in look.
local handle, reason = styles.register(spec)Returns a style handle, or nil and the reason (styles: …). Call it from the script body, a callback
or a timer; inside a style function it returns nil, "styles: register is not available inside a style function". The
style reaches the Style lists on the next frame.
Style spec
| Field | Type | Meaning |
|---|---|---|
id | string | Required: 1–32 characters of a-z, 0-9 and _, unique within the script. The saved id is lua/<file name without .lua>/<id>. |
name | string | The caption in the Style lists; defaults to id. |
settings | table | The style's own settings (below). |
<element> | function | One per element the style draws, keyed by its name: indicators = function(ctx, data) ... end. The keys present are the elements the style supports; at least one is required. A function under a key that names no element is refused (styles: unknown element 'radar'), so a style written for a newer build fails loudly. |
styles.elements lists the element names this build draws: indicators, status_bar, spectator_list, hit_log,
proximity_warning.
A script registers 8 styles at most, and all scripts together 32; a removed style stops counting at the next registration.
The reasons a spec is refused, each after the styles: prefix, in the order they are checked:
register is not available inside a style function, a script registers 8 styles at most,
the scripts register 32 styles at most, id is 1-32 characters of a-z, 0-9 and _, style '<id>' is already registered, unknown element '<name>', a style draws at least one element (indicators = function(ctx, data) ... end),
then a settings reason or one of Style settings' own. Only string keys holding
functions name elements; other keys are ignored. A spec that is not a table raises.
Element functions
fn(ctx, data) draws the element's whole window, (0, 0) its top-left corner, in the HUD's reference pixels (1080p;
the HUD scales them), and returns its size: return w, h (both above 0). The HUD window places, snaps, frames and
drags a block of that size, exactly as it does the built-in look. Returning false draws nothing this frame; any other
return draws nothing and is reported once per load. Drawing outside the returned box is not clipped.
The function runs once per frame while the element is on and the style is chosen for it, and while the menu is open
with nothing live it runs with the element's built-in preview data (ctx.preview is true) and draws at the preview's
opacity. Switched off, the element calls nothing.
data.items are the rows in reading order, top to bottom, as the built-in look shows them at the window's anchor (a
window anchored to the bottom grows upward, so its first row arrives last). The tables are reused frame to frame; read
them during the call. data.items is set again before each call and holds only that call's rows, so a style that
replaces or extends it sees the element's rows again next time. Every row carries:
| Field | Meaning |
|---|---|
alpha | 0..1: the row easing in when it appears and out after it is gone, as the built-in rows do. |
age | Seconds since the row first showed. |
leaving | true while a row the element no longer lists fades out; it is still in data.items. |
indicators rows: id (safe_mode, bomb, defuse, jump_bug, slow_walk, auto_peek, duck_peek,
double_tap, min_damage, force_shot), key, icon (one glyph of the HUD icon font, draw it with
{ font = 'icons' }; '' for none), label, value, slab (strings, '' when absent), number (the figure value
shows: bomb and defuse seconds, Min Damage; or nil), tone (accent, danger, warning, success, active,
muted, strong), color (the tone in the HUD palette), progress (0..1 or nil), pulse, active (false for a
dimmed bind; with Hide Inactive on, live ones never arrive, while the preview and a row fading out may still show them).
status_bar rows, one per cell in the user's Items order, left to right wherever the bar sits: id (brand, user,
ping, fps, cpu, clock), text (the brand, the licence name, HH:MM; '' for a reading), value (a reading's
number, or nil), unit (MS, FPS, CPU; '' for a text cell), suffix (% after CPU, + past 999 FPS, else ''), fraction
(0..1, the meter's fill, or nil), verdict (good, fair, poor; nil for CPU and the text cells) and
verdict_color. The cells neither enter nor leave: alpha is 1 and age 0. While the menu is open over a bar with nothing
selected, the preview is the brand cell.
spectator_list rows, the viewers in arrival order: key, name, initial (the placeholder letter for a player
without a Steam account, such as a bot; '' otherwise), avatar (the Steam picture once it has loaded, else nil; draw it with
ctx:texture), mode (eye: first person; chase: third person) and is_local (the local player, watching after
death). data.target is who they watch: { name = ..., is_local = ... } (is_local: the local player alive and
watched; name may be ''). The preview is one row, the local player: the name convar
and the current Steam account's avatar (initial '', mode eye).
hit_log rows, newest nearest the window's anchor (first at a top anchor): key, kind (hit dealt, hurt taken,
miss, mismatch), name (the target or the attacker), hitgroup (head, chest, …; nil for none), damage and
health_left (nil for a miss), hit_chance (a Rage line's, or nil), reason (a miss's or a mismatch's, as the
screen words it, or nil), planned ({ hitgroup = ..., damage = ..., hit_chance = ... } for a miss or a mismatch, else nil), parts
({ { text = ..., role = 'text' | 'strong' | 'emphasis' | 'muted' }, ... }: the built-in line's wording and the role
each piece is coloured by), text (the parts joined), icon, tone, color (as the built-in line), life (seconds
on screen from arrival: 5). A line arrives with age = 0, eases in over the HUD's entry animation, is leaving from
age >= life while it eases out, then leaves; 8 lines at most, past that the oldest leaves early. A style with its own timing reads age
and life and ignores alpha. The preview is three hits and a miss.
proximity_warning is a point element: fn(ctx, warning) runs once per warning per frame - every warning the
Grenade ESP's Proximity Warning would draw, after its Max Distance, Damage and Offscreen decide which exist and where -
with (0, 0) at the place the built-in timer would sit: the landing point or the fire on the screen, or its place at
the screen's edge. Nothing to return; false hides that warning. The style is chosen in the warning colour's picker
(Style, last), its settings under it. warning: kind (he, flash, molotov, incendiary), source
(grenade: a predicted landing; fire: a burning inferno), seconds and fraction (the countdown, or nil),
threat (nil with Damage off and for a flashbang, else { level = 'safe' | 'caution' | 'danger', caption = ..., damage = ... }: the
caption the built-in timer shows and the whole points it names), distance (metres from the eye), offscreen and
heading (degrees clockwise from up towards the warning, nil on the screen), icon (the game's HUD icon for the
grenade once it has loaded, a hud_texture_t for this call, or nil) and icon_aspect. ctx.color is the picker's
colour, ctx.options { damage, offscreen }, and ctx.reach the room in reference pixels around (0, 0) that the
edge placement keeps on the screen (56). There is no preview: a warning is pinned to the world. 32 warnings per frame
go through styles; past that they draw built-in.
A point style has one more method: ctx:builtin() draws the element's built-in look (the timer, with its arrow off
the screen) at its place in the call's drawing - under what is drawn after it, over what was drawn before. In a list
element's call it raises ctx:builtin is proximity_warning's.
Style ctx
| Field | Meaning |
|---|---|
ctx.element | The element name. |
ctx.preview, ctx.editing | Preview data; the menu is open. |
ctx.anchor | The window's anchor: top_left, top, top_right, left, center, right, bottom_left, bottom, bottom_right. |
ctx.align | left, center or right: how rows align at that anchor. |
ctx.time, ctx.frame_time | The HUD's clock in seconds (item.age counts in it) and the last frame's length. |
ctx.scale | Screen pixels per reference pixel. 1 / ctx.scale is one device pixel. |
ctx.settings | The style's settings for this element, by id. Read-only: assigning one, or a ctx field, raises style ctx is read-only; pairs(ctx.settings) sees nothing. |
ctx.palette | The live HUD palette: accent, danger, warning, success, active, muted, strong, background, text, text_muted. |
ctx.color | The element's own colour where it has one, else ctx.palette.accent. |
ctx.options, ctx.colors | The element's display switches and its colours: indicators options.hide_inactive; status_bar options.compact, options.glass, colors.accent, colors.background, colors.text (and ctx.color is the bar's Accent); proximity_warning options.damage, options.offscreen (and ctx.color is the picker's colour). |
ctx.reach | proximity_warning only: the room in reference pixels around (0, 0) that the edge placement keeps on the screen (56); nil otherwise. |
ctx belongs to its call: a method called afterwards raises style ctx used outside its draw call. Its
methods are the ESP ctx's (ctx:rect, ctx:rect_outline, ctx:rect_filled_fade, ctx:line, ctx:circle, ctx:arc,
ctx:polygon, ctx:poly_line, ctx:text, ctx:text_size, ctx:texture; see Render callback),
with lengths, sizes and text sizes in reference pixels. Text defaults to the HUD's face (Plex Sans) at size 12 with no
effect; style.font takes the same names as on an ESP ctx.
ctx:bar and ctx:project are the ESP's and raise here (ctx:bar is an ESP element's, not a style's). One call records at most 4096 shapes and 64 KiB of text, as an
ESP element's; all style calls of a frame together at most 16384 shapes - a call that would take the frame past that
draws the built-in look, and so does every later call that frame, reported once.
A style has one method of its own:
ctx:backdrop(pos, size, rounding?)The HUD's backdrop blur (the one behind its own pills) shows through that rectangle, under everything the call draws,
in one blur pass with the call's other backdrops; draw a translucent fill over it for a frosted panel. While the window
is a preview, the blur fades with it. A call asks for 8 backdrops at most and a frame for 16 over every style; past
that a backdrop draws no blur. On an ESP element's ctx, backdrop and builtin raise (ctx:backdrop is a style's, not an ESP element's).
ctx:texture draws a script's own texture_t and a host texture a style is handed (item.avatar, of type
hud_texture_t). A host texture draws only in the call that handed it: one kept for a later call raises a host texture draws only in the call that handed it.
Style settings
The ESP settings schema without picker and a slider's wheel (true for either is refused: styles: setting '<id>': wheel and picker are the ESP's), plus elements = { 'indicators' }: the
elements the setting applies to, every element the style draws by default (styles: setting '<id>': elements is a table of element names, styles: setting '<id>': elements names an element the style does not draw); a setting with a parent applies to no
element its parent does not (styles: setting '<id>': its elements must be among its parent's). A style takes 64
settings at most (styles: a style takes 64 settings at most). Each element keeps its own values. The rows
show under the Style row's "...", in the window's right-click menu and in the element's settings; for
proximity_warning, under the Style row of the warning colour's picker.
Errors and lifetime
- A function that raises is reported with its traceback as
style '<id>' (<element>): …, and that element draws its built-in look from the same frame until the script reloads; the Style list marks the style(error). Other elements using the style are unaffected. The Hit Log's lines on screen go to the built-in log with the life they have left. - The choice and the settings are saved with the profile under the element. Unloading or reloading the script, or
handle:remove(), draws the built-in look and keeps them: the list shows the style as(not loaded), and registering the sameidagain, in this session or after a restart, gives them back. Delete on the Scripts tab forgets them; the element returns to Built-in. Each element keeps the choices and settings of its 16 most recently chosen styles.
Style handle
| Member | Meaning |
|---|---|
handle.id | The style's id. |
handle:remove() | Takes the style out of the lists and the screen; its choice and settings are kept, as on unload. A call after the same id was registered again does nothing: a handle removes only the registration it came from. |
tostring(handle) | hud_style<id>. The type is hud_style_t. |
Ready to load: a pill look for Indicators, features/lua/examples/hud_style_pill.lua;
a titled panel for the Spectator List, the Status Bar and Indicators with a Blur setting,
features/lua/examples/hud_style_compact_panel.lua; Hit Log
lines that slide in and out on their own timing, features/lua/examples/hud_style_slide_hitlog.lua;
and a dot for each Proximity Warning, optionally over the built-in timer,
features/lua/examples/hud_style_dot_proximity.lua.
