HOOZiDocs
Skip to content

gui

Menu UI + config writes (the only write path). After creating a container, keep the returned object reference and mount child elements through its methods.


Top-level Creation + Lookup

gui.tab(name) → Tab

Parameter: name : string — used as both id and label.

gui.sub_tab(parent, id, label) → Tab

Parameters: parent : Tab, id : string, label : string

gui.window(id, title, w, h, flags?) → Window

Parameters: id : string, title : string, w : number, h : number, flags? : int = 0

Combine flags from gui.WINDOW.* (bitwise OR / plain +). Common ones:

ConstantEffect
NoTitleBarNo title bar
NoResizeNo corner-resize
NoMoveNot draggable
NoScrollbar / NoScrollWithMouseNo scrollbar / no wheel scroll
NoCollapseNot collapsible
NoBackgroundTransparent (no fill/border)
AlwaysAutoResizeSize to content
NoSavedSettingsDon't persist pos/size
NoMouseInputs / NoInputsMouse passthrough / no input at all
NoFocusOnAppearing / NoBringToFrontOnFocusDon't steal focus on show/click
NoDecoration= NoTitleBar+NoResize+NoScrollbar+NoCollapse (typical borderless HUD)

Borderless draggable HUD: gui.window("hud", "", 250, 112, gui.WINDOW.NoDecoration + gui.WINDOW.NoFocusOnAppearing). Draw custom bars/graphics inside via Window:custom(id, proto, h) on_render(self,x,y,w,h) (window content-region screen coords, moves with the window). A display-only custom (no mouse hooks) does not capture input, so a borderless window stays draggable by its body.

gui.find(path) → GUIElement | nil

Parameter: path : string — full path (dot-separated, e.g. "aimbot.master").

Returns: a derived-type object (Checkbox / Slider / Combobox / Window / Groupbox / Tab / …). nil means the path does not exist.

gui.children(path) → table<GUIElement>

Parameter: path : string — container full path (Tab / Groupbox / Window / SettingsPopup).

Returns: an array of the container's direct child controls (type-transparent — each supports :get()/:set()/:set_hint()). Returns an empty table {} for non-containers or missing paths. Use it to iterate the controls under a container (e.g. for linked show/hide).

gui.get(path, fallback?) → value | fallback

Parameters: path : string, fallback? : any = nil

Internally = gui.find(path):get() with a nil check.

gui.set(path, value)

Parameters: path : string, value : any

Internally = gui.find(path):set(value).

gui.show(paths) / gui.hide(paths)

Parameter: paths : table<string> — list of control full paths.

gui.set_enabled(paths, state) / gui.set_visible(paths, state)

Parameters: paths : table<string>, state : bool


gui.is_visible() → bool

Whether the menu is currently open (synchronous query — equivalent to event.on("menu_toggled", fn) but without maintaining your own bool).

gui.list_hotkeys() → table<GUIElement>

Returns every Checkbox + KeybindControl that has an actual key bound (controls with vk = 0 are not included). Order matches the internal insert order, which matches the menu display order.

Returned elements are type-transparent (Checkbox / KeybindControl userdata), so you can call subclass methods directly:

  • :get_full_path() → string — use with input.is_active(path) to query current activation
  • :get_label() → string — display label
  • :get() → bool — current checkbox value

Pair with input.format(path) to get a "Ctrl+F1"-style display string.

lua
-- Custom hotkey HUD: list every hotkey-bound control + state + key
for _, elem in ipairs(gui.list_hotkeys()) do
    local path = elem:get_full_path()
    local key  = input.format(path)             -- "Ctrl+F1"
    local on   = input.is_active(path)          -- currently active?
    log.info((on and "[ON]  " or "[off] ") .. elem:get_label() .. "  " .. key)
end

Aligned with fatality's gui.GetHotkeyList().


gui.get_main_window() → table { x, y, w, h, visible }

Screen position, size, and visibility of the main menu ImGui window.

FieldDescription
x, yTop-left screen coordinates (pixels, same coord system as draw.*)
w, hCurrent displayed size (live during the fade animation)
visibleEquivalent to gui.is_visible()

When the menu is fully closed, x/y/w/h keep their last value and visible=false. The menu is draggable; x/y follow user drags (including across monitors).

lua
-- A docked floating window that follows the menu's right edge
local win = gui.window("dock", "Toolbar", 200, 400)
event.on("frame_update", function()
    local m = gui.get_main_window()
    if m.visible then
        win:set_pos(m.x + m.w + 10, m.y)
        win:show()
    else
        win:hide()
    end
end)

-- Draw ESP only when the menu is closed (avoids overlap)
event.on("frame_update", function()
    if gui.is_visible() then return end
    draw.text(...)
end)

Any menu control whose full_path contains one of .hidden. / .visible. / .knocked. / .teammate. segments is auto-detected as an ESP scenario field. A link icon (⛓️ linked / ⛓️‍💥 unlinked) appears to the left of the control; toggling it broadcasts the current value to the other 3 scenario paths. The link state persists in cfg.visual.esp_linked_fields and is saved per plan.

Example:

lua
local tab = gui.tab("MyESP")
local scenarios = {"hidden", "visible", "knocked", "teammate"}
for _, sc in ipairs(scenarios) do
    local g = tab:group(sc, sc, 0, 0, 0, 0)
    g:color_edit("tint", "Tint", 1, 0, 0, 1)
    g:slider_float("radius", "Radius", 1, 20, 5)
end
-- 4 paths "MyESP.<sc>.tint" / "MyESP.<sc>.radius" automatically get a link icon
-- Clicking ⛓️‍💥 → ⛓️ broadcasts the current scenario's value to the other 3 paths

Reading scenario values — two patterns:

lua
-- (A) Inside ESP draw callback: ctx.scenario is per-player (engine-resolved)
esp.add({
    id = "my_module",
    fn = function(ctx)
        local sc_name = ({[0]="hidden",[1]="visible",[2]="knocked",[3]="teammate"})[ctx.scenario]
        local tint = gui.get("MyESP." .. sc_name .. ".tint")
        return { text = "x", text_color = tint }
    end,
})

-- (B) Outside draw context: hardcode which scenario to read
local visible_tint = gui.get("MyESP.visible.tint")

There is NO global "current scenario" — the 4 scenarios are per-player state classifications, and N players can be in different scenarios simultaneously. esp.scenario is the menu preview index, not any player's scenario; use ctx.scenario (not esp.scenario) in draw callbacks.

Limitation: Lua-side link propagation only broadcasts at the moment the user toggles unlinked→linked. Subsequent edits to the source value do NOT automatically sync to the other 3 paths (real-time per-frame propagation is TODO). C++ BIND_SCENARIO_FIELD-bound controls don't have this limitation (every write goes through post_write auto-broadcast).


Weapon Configuration Read/Write

Weapon configuration is accessed through gui.set / gui.get. Two path shapes both edit the same underlying WeaponConfig:

Path shapeWhich weaponWhen to use
Menu path: aimbot.legit.fov.deadThe weapon currently selected in the menuMatches UI flow; copy directly from the "show component path" tooltip
Storage path: aimbot.weapon.<id>.aim.fov_deadThe specific <id> weapon (ignores UI selection)Batch-edit multiple weapons, run presets, or write while not alive

Menu tooltip: with "show component path" on, hovering a per-weapon control shows Storage: aimbot.weapon.<id>.<field> + Menu: aimbot.legit.<...>; right-click copies the storage path. Batch (multi-select) mode adds (batch: N weapons). Global (non-per-weapon) controls show only the single menu path.

lua
gui.set("aimbot.legit.fov.dead",  0.5)   -- current selected weapon: dead zone
gui.set("aimbot.legit.fov.hip",   12.0)  -- current selected weapon: hip FOV
gui.set("aimbot.legit.smoothadv.kp", 1.8)

Whichever weapon is selected in the menu is the one written. default selected → writes to default; r301 selected → writes to r301.

Two caveats:

  • Batch mode writes only the first weapon: with multiple weapons multi-selected in the UI, a Lua menu-path gui.set only writes to the first one in selected_weapon_ids — unlike dragging the slider in the UI, which truly batches. To batch from Lua, loop over storage paths instead.
  • @weapon_id suffix forces a specific weapon: aimbot.legit.fov.dead@r301 bypasses the UI selection by binding the groupbox to r301 for this call. Closer to the UI mental model than looping over storage paths when you want to touch a handful of weapons.

Storage paths (specific weapon)

Format: aimbot.weapon.<weapon_id>.<sub_path>

  • <weapon_id>: string weapon ID like "r301" / "flatline"; "default" is the baseline
  • <sub_path>: WeaponConfig field (dot-nested + 1-based [n] for arrays)
lua
-- Specific weapon: hard-code the ID (unaffected by UI selection)
gui.set("aimbot.weapon.r301.aim.fov_hip", 12.5)
gui.set("aimbot.weapon.flatline.aim.fov_hip", 12.5)
gui.set("aimbot.weapon.default.filter_tags[3]", true)

-- Nested field / array
gui.set("aimbot.weapon.r301.aim.pid.kp", 1.8)
gui.get("aimbot.weapon.r301.aim.max_dist_range[2]")

Field names follow the C++ SCHEMA; not enumerated here.

Write failure semantics: all three of these silently no-op — no Lua error, no config snapshot publish:

  • Weapon ID not added to armory (e.g. aimbot.weapon.unknown.aim.fov_hip) — does not silently fall back to default
  • Unknown field (typo, e.g. aimbot.weapon.r301.aim.fov_hipx)
  • Array index out of range / type mismatch (e.g. writing a string to a float field)

Reads (gui.get) the same: any of the above returns nil (or your fallback arg).

gui.get_active_override_path() → string

Returns the storage-path prefix of the config that actually drives the aimbot right now for the held weapon. Semantics match the aim thread's internal resolve_active_weapon:

Current stateReturns
Held weapon is in a group + group is in armory"aimbot.weapon.<group_id>"
Held weapon standalone + in armory"aimbot.weapon.<weapon_id>"
Held weapon not in armory (armory disabled for it)"aimbot.weapon.default"
ClientData snapshot unavailable (DMA disconnected / loading)""
lua
local p = gui.get_active_override_path()
if p ~= "" then
    gui.set(p .. ".aim.bone_target", 2)  -- writes to the actually-effective config
end

This differs from "menu selected weapon": it looks at the in-game held weapon (lp.weapon_enum resolved via enum_to_weapon_id + armory check), the menu selection doesn't affect it. The empty string is reserved for genuinely-no-state cases (dead, loading) — any alive state returns at least aimbot.weapon.default.

gui.get_selected_weapon() → string

Returns the weapon id currently selected for configuration in the weapon armory (not the held weapon — for that use gui.get_active_override_path()). Use it to address per-weapon Lua values via aimbot.weapon.<wid>.lua.<id>. Returns "default" when nothing is selected.

lua
local wid = gui.get_selected_weapon()
gui.set("aimbot.weapon." .. wid .. ".lua.my_gain", 1.5)

Notifications (gui.notify)

gui.notify is a sub-table; call its methods with ::

gui.notify:info(text, dur?) / :success / :warn / :error

Parameters: text : string, dur? : number = 4.0 (lifetime in seconds)

lua
gui.notify:info("hello")
gui.notify:success("Preset applied", 2.5)
gui.notify:warn("Distance too far", 1.5)
gui.notify:error("HTTP failed: " .. err, 3.0)
levelcoloruse for
infowhitenormal operation feedback
successgreenpositive confirmation
warnyellowsoft warning
errorrederror alert

Container Methods (shared by Tab / Window / Groupbox)

:group(id, label, x, y, w, h) → Groupbox

Parameters: id : string, label : string, x/y/w/h : number

:button(id, label, callback?) → Button

Parameters: id : string, label : string, callback? : function()

:checkbox(id, label, default) → Checkbox

Parameters: id : string, label : string, default : bool

:slider(id, label, min_v, max_v, def) → Slider (integer)

Parameters: id : string, label : string, min_v / max_v / def : int

:slider_float(id, label, min_v, max_v, def) → SliderFloat

Parameters: id : string, label : string, min_v / max_v / def : number

:range_slider_float(id, label, min_v, max_v, def_v1, def_v2, min_range?) → RangeSliderFloat

Parameters: 5 numbers + min_range? : number = 0

:range_slider_int(id, label, min_v, max_v, def_v1, def_v2, min_range?) → RangeSliderInt

Parameters: 5 ints + min_range? : int = 0

Parameters: id : string, label : string, items : table<string>, def : int (1-based default index; the binding layer converts to 0-based internally. gui.get/set reads/writes the internal field directly so that side is 0-based int)

(The usertype is still named Combobox — historical naming, used in the control-methods section below. Multi-select variant is :multi_dropdown.)

:input_text(id, label, def) → InputText

Parameters: id : string, label : string, def : string

:color_edit(id, label, r, g, b, a?) → ColorEdit

Parameters: id : string, label : string, r / g / b : number, a? : number = 1.0

:multi_dropdown(id, label, items, defaults?) → MultiDropdown

Parameters: id : string, label : string, items : table, defaults? : table<bool>

:keybind_control(id, label, def_key?, def_mode?) → KeybindControl

Parameters: id : string, label : string, def_key? : int = 0, def_mode? : int = 0

:live_table(id, headers, provider, height?) (Window / Groupbox only)

Parameters: id : string, headers : table<string>, provider : function() → table<row>, height? : number = 0

row shape: {cell1, cell2, ...} array, or {cells = {...}, color = {r,g,b,a}} table.

:tips(id, text, icon?, color?) (Window / Groupbox / SettingsPopup)

Parameters: id : string, text : string, icon? : string, color? : table<r,g,b,a>

:settings(id, label, icon?) → SettingsPopup

Parameters: id : string, label : string (text left of the gear, may be empty), icon? : string (gear glyph, default ⚙)

A cogwheel/collapsible container (fatality gui.Settings equivalent): shows label ⚙ on the row; clicking the gear opens a popup whose children render inside. See "Settings Gear Container" below.


Custom Controls (LuaControlProto-style)

container:custom(id, proto, height?) → CustomControl

Parameters:

  • id : string — control id
  • proto : table — control prototype holding the on_* lifecycle callbacks and initial_data
  • height? : number = 24 — control height in pixels

Returns a CustomControl userdata. The same proto may instantiate multiple controls; initial_data is deep-copied so instances do not share state.

All proto lifecycle fields are optional:

FieldSignatureTriggered
initial_datatableInitial instance data, deep-copied into self.data
on_renderfn(self, x, y, w, h)Every frame; the box is (x, y) → (x+w, y+h) in screen coordinates
on_first_renderfn(self)First frame (after persisted data has been loaded)
on_mouse_downfn(self, btn)Mouse pressed (0=left, 1=right, 2=middle, 3=x1, 4=x2) while hovered
on_mouse_upfn(self, btn)Mouse released (hover not required)
on_mouse_movefn(self, dx, dy)Hover; mouse position relative to top-left of the control
on_focusfn(self)Edge where the control becomes active (holds mouse)
on_blurfn(self)Edge where active is released
on_keyfn(self, imgui_key)Keyboard pressed while hovered (no-repeat)
on_resetfn(self)When self:reset() is called explicitly

CustomControl instance methods

MethodDescription
self.data : tableInstance data, read/write. Whole-replace or self.data.foo = x both work
self:lock_input()Mark input as locked (ImGui auto-locks when holding mouse; this is a hint)
self:unlock_input()Release the lock
self:is_hovered() → boolWhether the cursor was over the control last frame
self:is_focused() → boolWhether the control held the mouse last frame (active)
self:is_input_locked() → boolQuery the lock_input flag
self:get_height() → number / :set_height(h)Control height
self:reset()Trigger on_reset callback

Persistence

self.data is automatically saved into cfg.lua.<full_path> (same machinery as Lua-created checkboxes). Script reload / plan switch restores the last data. on_first_render fires after data is loaded so you can initialize from persisted state.

lua
-- Counter — left-click +1, right-click -1, count survives reload
local Counter = {
    initial_data = { count = 0 },
    on_render = function(self, x, y, w, h)
        local bg = self:is_hovered() and draw.rgba(0.25, 0.30, 0.40, 1)
                                       or draw.rgba(0.15, 0.18, 0.22, 1)
        draw.rect(x, y, x+w, y+h, bg, { filled = true, rounding = 3 })
        draw.text(x + 8, y + 8, draw.rgba(1, 1, 1, 1),
                  "Count: " .. tostring(self.data.count), { outline = true })
    end,
    on_mouse_down = function(self, btn)
        if btn == 0 then self.data.count = self.data.count + 1
        elseif btn == 1 then self.data.count = self.data.count - 1 end
    end,
}

local grp = gui.tab("misc"):group("g", "demo", 0, 0, 350, 0)
grp:custom("counter1", Counter, 28)

Full demo at out/Debug/scripts/custom_control_demo.lua (Counter / Pulse / Bar).


Settings Gear Container (SettingsPopup)

container:settings(id, label, icon?) → SettingsPopup creates a cogwheel/collapsible container (fatality gui.Settings equivalent): the row shows label ⚙; clicking the gear opens a popup whose children render inside. The gear and popup are drawn entirely by the C++ framework — Lua only creates the container and adds children.

Use it to fold "secondary / advanced settings" behind a gear: the main menu stays one row instead of spreading out many params (and you avoid per-frame set_visible juggling).

Child factories (same set as Groupbox; children get persistence / hotkeys / per-weapon writes automatically): button / checkbox / slider / slider_float / dropdown / input_text / color_edit / range_slider_float / range_slider_int / multi_dropdown / keybind_control / tips / custom

Instance methods:

MethodDescription
s:add_child(elem) / s:remove_child(elem) / s:clear_children() / s:remove_children_by_owner(path)child add/remove (same as other containers, except remove_child / clear_children return self for chaining)
s:set_gear_icon(icon) → selfchange the gear glyph
s:set_popup_width(px) → selfpopup logical width (default 260)
lua
local grp = gui.tab("misc"):group("g", "demo", 0, 0, 350, 0)
local adv = grp:settings("adv", "Advanced")          -- row shows "Advanced ⚙"
adv:slider("speed", "Speed", 0, 100, 50)
adv:slider_float("ratio", "Ratio", 0.0, 1.0, 0.5):set_format("%.2f")
adv:checkbox("smooth", "Smooth", true)
adv:set_popup_width(220)

Container Generic Methods

Shared between Tab / Groupbox / Window for low-level child management. Most scripts don't need these — control creation via :checkbox / :slider / :group is more concise. Use them for dynamic add/remove (hot-reload self-maintenance, conditional control creation at runtime).

:add_child(elem)

Manually attach a GUI element. No return value.

:remove_child(elem)

Remove one direct child. Takes the element object (not an id string). No return value.

:remove_children_by_owner(script_path)

Remove every child created by that owner. No return value. Script unload runs this automatically — manual calls are usually unnecessary.

:clear_children()

Remove every child.

Tab:is_lua_tab() → bool

Whether the tab was created by a Lua script via gui.tab(...) (as opposed to a built-in C++ tab). Use it when you want to avoid touching built-in tabs.


Window-only Methods

Window:is_open() → bool / Window:set_open(bool)

Window:bind_visible(checkbox) → Window

Parameter: checkbox : Checkbox — reference to any Checkbox object (returned by :checkbox(...)).

Each frame auto-syncs Window.open = checkbox.value, saving you from writing set_open inside a live_table provider.


Checkbox / Slider / Combobox Control Methods

Checkbox / Slider / SliderFloat Multi-hotkey

2026-05-25 UX redesign: Each Checkbox / Slider supports multiple hotkeys. Right-click the control to open a popup for managing them. The switch / slider's top-right corner shows the first hotkey plus a count (Ctrl+F1 +2). Each row in the popup has [key][mode][🗑]; Slider rows have an extra [⚙] to set the trigger value (dial slider back to that value).

lua
-- Checkbox multi-hotkey: any active → checkbox = true
local cb = group:checkbox("aim", "Aimbot", false)
cb:add_hotkey({ vk = 0x70, mode = 1 })                          -- F1 Hold
cb:add_hotkey({ vk = 0x71, mode = 0, modifier = 0x11 })          -- Ctrl+F2 Toggle
cb:clear_hotkeys()                                                -- clear

-- Slider multi-hotkey + per-key value: F3 writes slider to 50
local sl = group:slider("fov", "FOV", 0, 100, 30)
sl:add_hotkey({ vk = 0x72, mode = 0, value = 50 })               -- F3 Toggle → write 50
sl:add_hotkey({ vk = 0x73, mode = 0, value = 80 })               -- F4 Toggle → write 80

Modes: 0=Toggle / 1=Hold / 2=Always. Modifiers: 0x10=Shift / 0x11=Ctrl / 0x12=Alt.

Checkbox:set_keybind(vk, mode, modifier?)

Parameters:

  • vk : int — Main key virtual-key code
  • mode : int — 0=Toggle / 1=Hold / 2=Always
  • modifier : int? — Optional modifier key. VK_CONTROL (0x11) / VK_SHIFT (0x10) / VK_MENU (0x12, Alt). Omit or 0 for no modifier.

Example:

lua
cb:set_keybind(0x70, 0)             -- F1 alone, Toggle
cb:set_keybind(0x70, 1, 0x11)       -- Ctrl + F1, Hold

Checkbox:set_colors(table) / Checkbox:get_colors() → table

Custom palette for checkboxes that carry keybind state (current/inactive/hover/...).

:set_hint(text) → self

Available on: Checkbox / Slider / SliderFloat / RangeSliderFloat / RangeSliderInt / InputText / KeybindControl. Combobox / MultiDropdown / ColorEdit / Button do not have :set_hint.

:set_width(px) → self / :get_width() → number

Parameter: px : number — control pixel width; 0 or omitted = auto-fill the row.

A GUIElement base method available on all controls; only Slider / SliderFloat / Combobox honor it (others store but ignore). grp:slider("id","label",0,100,5):set_width(120).

:set_per_weapon(enable?) → self / :is_per_weapon() → bool

Parameter: enable? : bool = true

Store the control's value per weapon (the one currently selected in the weapon armory; Lua controls only). Switching the selected weapon shows that weapon's value; a weapon never set falls back to the default weapon's value. Put it last in the chain (returns the base element).

The value is stored at aimbot.weapon.<wid>.lua.<control id>; scripts can gui.get/set that path to address any specific weapon. Switching weapons shows the selected weapon's value; a weapon never set falls back to the default weapon's value.

Value-type controls only (checkbox / slider / slider_float / combobox / input_text); controls with a keybind or colors are not supported per-weapon. Control ids must be unique within the addressing namespace.

lua
local grp = gui.find("aimbot.legit.smoothadv")
grp:slider_float("lua_gain", "Lua gain", 0, 5, 1):set_format("%.2f"):set_per_weapon(true)

Slider:set_format(fmt) → Slider / SliderFloat:set_format / RangeSliderFloat:set_format / RangeSliderInt:set_format

Combobox:on_change(fn) → Combobox

Parameter: fn : function(idx_1based : int, name : string) — 1-based index + selected item string.

Combobox:add_item(item) → Combobox / add_items(items) / remove_item(idx_or_name) / clear_items()

Combobox:get_selected_name() → string / get_items() → table<string>

InputText:set_size(vec2)

Button:set_size(vec2) / set_icon(string)

ColorEdit:set_colors(table) / set_alpha(bool)

MultiDropdown:add_item(item, default?) / add_items(items) / remove_item(idx_or_name) / clear_items() / get_selected_names() → table<string>


GUIElement Base Methods (inherited by all controls)

:get_id() → string / :get_owner() → string / :get_label() → string / :set_label(str)

:is_enabled() → bool / :set_enabled(bool) / :is_visible() → bool / :set_visible(bool)

:get() → value / :set(value) / :has_value() → bool

Path-based unified accessor for the control's current value (equivalent to gui.get / gui.set).

:bind_weapon(...) / :get_target_weapon(...)

Control-level weapon binding (advanced).


Example

lua
local tab = gui.tab("MyTab")
local g   = tab:group("g", "My Group", 0, 0, 0, 0)

local cb = g:checkbox("enable", "Enable", false)
            :set_hint("Tick to turn the feature on")

local sl = g:slider_float("scale", "Scale", 0.0, 5.0, 1.0)
            :set_format("%.2fx")

local cb_theme = g:dropdown("theme", "Theme",
    { "Default", "Dark", "Light" }, 1)
    :on_change(function(idx_1based, name)
        log.info("Theme switched to: " .. name)
    end)

g:button("reset", "Reset", function()
    gui.set("MyTab.g.enable", false)
    gui.set("MyTab.g.scale",  1.0)
end)