Skip to Content

Script UI

Build a script's settings UI: ui.panel, ui.tab, ui.find, ui.section, control builders, node methods, control values, built-in tabs, locales and menu_object_t.


All nodes created by a script belong to it and are removed on unload. Labels are displayed as supplied and are not translated.

UI containers

ui.panel

ui.panel(label?, tab_name?)

Creates the script's settings card in its own sub-tab under Scripts. label defaults to "Settings".

The first call creates the card and sub-tab. Later calls return the same card and update its title or sub-tab name if the corresponding argument is supplied. Scripts that never call ui.panel have no settings sub-tab.

It works however the script was loaded (autoload, the Scripts tab, MCP's lua_load_script) and whether or not the menu or the Library was ever opened: the Scripts tab is built on demand, and a file written into the scripts folder after the Library last listed it is listed by this call. lua_eval's scratch state gets a sub-tab of its own, MCP Scratch, removed by lua_eval's reset.

Returns: node, or nil, reason. The reason says what to do:

  • ui.panel: the script's file is no longer in the scripts folder; put it back and reload the script — the file was deleted or moved after the script loaded;
  • ui.panel: the menu does not exist yet; call ui.panel again from a callback (events.render) once it does — no main window (not reachable in the game, where the menu exists before any script loads);
  • ui.panel: the Scripts tab could not build this script's sub-tab; reload the script.

Sub-tab naming and lifecycle:

  • Name: tab_name, then metadata @name, then file name.
  • Names over 32 bytes are truncated at a UTF-8 boundary and end with an ellipsis.
  • Duplicate names receive a (file.lua) suffix.
  • The sub-tab is removed on unload, including load failure. Reload rebuilds it and restores its selection if it was open.
  • The Library card's Open settings button selects it.
  • Control IDs and saved values are independent of the sub-tab name.
  • Script sub-tabs are not exposed through ui.find or locales.menu.

ui.tab

ui.tab(id, label?)

Adds a top-level menu tab with the code icon. Each call creates another tab. Add sections through tab:section(...).

Returns: node, or no values before the main window exists.

ui.find

ui.find(path...)

Finds a built-in tab, sub-tab, section or card for adding script-owned controls. It does not return built-in control values; use ui.find_object for those.

Returns: node, or nil, reason. See Adding controls to built-in tabs.

ui.find_object

ui.find_object(path)

Finds any menu object, including built-in controls, to read or write its value. ui.find gives you a container to add your own controls into; ui.find_object gives you a built-in control's value.

path is one table. Its first segment must be locales.brand; every later segment is a locales.* value, a caption string (compared with the caption in the current menu language) or a caption hash. A locales.menu entry is not accepted, and segments of any other type are skipped. Containers that only group sections, such as Ragebot's Aimbot sub-tab, may be left out of the path.

Returns: menu_object_t, or nil if no object exists at that path.

-- 'Hit Log' is the English caption; with the menu in another language, pass that language's caption.
local hitlog = ui.find_object({ locales.brand, locales.visuals, locales.hud, locales.combat, locales.feedback, 'Hit Log' })
if hitlog and hitlog:get_type() == 'checkbox' then
    print('Hit Log is', hitlog:get() and 'on' or 'off')
end

See menu_object_t for the value mapping and the object's methods. Wrong value types raise from o:set(value).

ui.section

ui.section(tab_path, label, side?)

Creates a script-owned section in a built-in tab.

Returns: node, or nil, reason.

side accepts "left", "right", "full" (default), or the corresponding values 0, 1, 2. Other values raise.

ui.is_open()

Returns: whether the menu is open.

Builders

Call builders on a node, for example panel:checkbox(...). Controls are also containers: a control's context_group creates its row's overflow popup.

Builders take a stable id first, except label, separator and disabled_group. Labels default to the ID where optional. Pass nil to retain the default label while supplying later arguments.

IDs are persistence keys. Reusing an ID with another value type raises ui: id '<id>' already names a control of another type. Reusing it with the same type creates another widget for the same value; the new default is ignored and combo items are replaced.

Structural builders

node:section(id, label?, side?)
node:header(id, label?)
node:header(id, label, default)
node:context_group(id, label?)
node:disabled_group(disabled)
node:label(text)
node:separator()
BuilderResult
sectionSection node in a tab or sub-tab; side uses the values above.
header without defaultCard node containing rows.
header with non-nil default, including falseBoolean control: a card with an on/off switch.
context_groupNode for the owning row's overflow popup.
disabled_groupNode whose children are disabled while the argument is truthy.
labelFixed-text node.
separatorSeparator node.

Value builders

node:checkbox(id, label?, default?)
node:slider_int(id, label, default, min, max)
node:slider_float(id, label, default, min, max)
node:combo(id, label, items, default?)
node:multicombo(id, label, items, default_mask?)
node:list(id, label, items, default?, has_expand?)
node:multilist(id, label, items, default_mask?, has_expand?)
node:color_edit(id, label?, default?)
node:input_string(id, label?, default?)
node:input_int(id, label?, default?)
node:input_float(id, label?, default?)
node:hotkey(id, label?, mode?)

Each returns a control.

BuilderValue / default behaviour
checkboxBoolean; default uses Lua truthiness, so 0 means enabled.
slider_int, input_intInteger.
slider_float, input_floatNumber.
combo, listZero-based selected index.
multicombo, multilistBit mask; bit i selects item i.
color_editTable {r, g, b, a}; default white.
input_stringString.
hotkeyActive-state boolean. Mode is "toggle" (default) or "hold".

items is a table of strings; numbers are accepted as text. Other item types raise list items must be strings.

button

node:button(id, label?, fn?)

Creates a button node. fn() runs on click. A failing handler is disabled until reload and reported as ui button: ....

Returns: node, not a value control.

Node methods

Available on nodes and controls. Methods return the node for chaining unless stated otherwise. Access to a removed node raises ui node was removed, except for the documented control-value and repeated-removal behaviour.

MethodBehaviour
n:tooltip(text)Sets hover text for controls, headers, labels and groups.
n:format(fmt)Slider/input printf format with one conversion, such as '%.1f s' or '%d px'; trailing text is a suffix.
n:max_count(count)Visible rows before scrolling: combo default 10; also applies to lists.
n:text_size(size)input_string capacity: at most size - 1 bytes; default size 64.
n:disabled(flag)Sets the disabled state and replaces disabled_if.
n:visible(flag)Animates visibility without leaving a gap. Raises for tabs, sections and popups.
n:visible_if(fn)Replaces the visibility predicate; evaluated once per frame. Truthy means visible. On error, disables the predicate and leaves the node visible.
n:disabled_if(fn)Disables the node while the per-frame predicate returns a truthy value.
n:order(index)Ascending sort key among siblings.
n:remove()Removes the node and descendants at the start of the next frame; returns no values. Repeated removal is a no-op.

All builders are also available as node methods. Removal retains control values and binds; rebuilding the same IDs restores them.

Control values

control:get

control:get()

Returns: the value type listed under Value builders.

control:set

control:set(value)

Sets the value immediately without calling on_change.

  • Integer and numeric controls reject non-numbers.
  • Colour controls accept any supported colour form.
  • Masks require an integer; -1 selects all 64 bits. For bits 32..63, use arithmetic or 64-bit cdata rather than 32-bit operations on ordinary numbers.
  • For hotkeys, sets the active state. A hold hotkey's state is overwritten by the physical key on the next frame.

control:on_change

control:on_change(fn)

Adds fn(value). It runs at the start of the next frame after a change through the menu, a key bind, a hotkey transition or a configuration load. Multiple changes in one frame produce one call. Handlers cannot be removed; a failing handler is disabled.

get and set remain available after a control's widget is removed. Users can right-click controls to configure key binds, which are saved with the value.

Adding controls to built-in tabs

ui.find and ui.section accept paths as separate segments, a table, or a locales.menu entry. The leading locales.brand is optional.

Each segment may be a locales.* value, numeric locale ID or currently displayed caption. Prefer locale IDs: English captions do not match a menu displayed in another language.

local section = assert(ui.find(locales.menu.visuals.world.esp))
section:checkbox('custom_enabled', 'Custom overlay', false)

Placement rules:

  • Rows belong to sections or cards; sections belong to tabs or sub-tabs.
  • Tabs that display sub-tabs, such as Ragebot and Visuals, require a target sub-tab.
  • Ragebot's Aimbot and Movement sub-tabs are skipped during lookup. For example, ui.find(locales.ragebot, locales.general) resolves Aimbot's General section.
  • A context group must belong to a script-owned row, not a built-in container.
  • New nodes appear at the start of the next frame, after built-in rows. Use order to change their position.
  • Injected control values are saved in the script's settings, not the built-in tab's settings.

Built-in nodes cannot be modified or removed. Modifiers raise ui: a built-in menu node cannot be changed or removed; use ui.find_object for its values.

Lookup errors return nil with one of these reasons:

ui.find: expected a path of locales.* values, hashes or names
ui.find: no menu object at that path
ui.find: not a built-in tab, section or card; read or write a control through ui.find_object

Invalid placement returns nil with one of these reasons:

ui: a tab holds sections only; add the control to one of its sections or cards
ui: a section goes into a tab or a sub-tab
ui: this tab shows its sub-tabs; add the section to one of them
ui: a context group opens from a script's own row, not from a built-in container
ui: the node could not be built

locales

locales.<name> values are menu caption IDs of type locale_t. String conversion returns locale_t( <hex> ).

Available names:

brand                 ragebot                legitbot
visuals               player                 world
camera                hud                    skins
misc                  voice                  general
settings              esp                    environment
combat                crosshair              movement
utility               lobby                  information
feedback              scoreboard             killfeed
world_other           grenades               scene
atmosphere            world_style            shots
voice_dj_sound        local_player           viewmodel
post_processing       main                   colored_models
local_colored_models  enemy                  subject
lua                   players                utility_general

locales.current() returns the menu language as a stable token: "en", "ru" or "zh", or nil when the menu's current language is none of those three. It is read at each call, so it follows a language switch. Use it instead of inferring the language from a translated caption.

local greeting = ({ en = 'Hello', ru = 'Privet', zh = 'Ni hao' })[locales.current()] or 'Hello'

locales.menu exposes the menu tree read when the script loads. Keys use lowercase English captions with other characters replaced by _. Entries contain:

FieldValue
idCaption ID.
pathTable of path segments.
kind"tab", "sub_tab", "section" or "card".

Entries also contain their children. Example path: locales.menu.visuals.world.esp.

Returned by ui.find_object.

MethodReturns / effect
o:get(), o:get_value()Current value; mapping below.
o:set(value), o:set_value(value)Sets the value; no return values. Wrong types raise, except colour conversion is lenient.
o:get_name()Caption in the current language.
o:get_path()Path such as Root/Nixware/….
o:get_type()checkbox, slider, input, combo, list, toggle_group, color_edit, hotkey, object or invalid.
o:is_visible(), o:is_disabled(), o:is_valid()Boolean.

Value mapping: checkbox → boolean; slider/input → number or string; combo/list → index; multi-selection → mask; colour → color; hotkey → active state. Other object kinds return nil.

A menu_object_t is a raw pointer. Look it up again rather than retaining it across frames.

Writing Misc → Utility → General → MCP Server (checkbox) with o:set(false) switches the MCP bridge off like a click does. Run through MCP's lua_eval, that closes the agent's own connection before the eval's answer is sent. MCP's set_setting refuses this control for that reason; from Lua it stays writable.

On this page