Skip to content

Config and script UI

API Description
aqua.config.get(key) bool / int / float / {r,g,b,a} / string (auto-creates)
aqua.config.set(key, value) true if accepted; false for incompatible types or failure to create an editable profile
aqua.config.list([prefix]) { {key=, value=, displayName?}, ... }
aqua.config.save() SaveIfDirty
aqua.config.override(key, value [, priority=75]) Set this script’s temporary layer for a registered key; returns false for unknown keys or incompatible values
aqua.config.clear_override([key]) Release this script’s layer for one key, or all its layers when omitted

Prefer keys under Scripts.<yourName>.*.

default is a clean-slate profile. The first actual setting change automatically creates and selects an editable new config profile (numbered when that name is taken). New keys written through aqua.config.set use their type’s zero/empty value as the default; register a UI control first when a different default is needed. Reading or registering a key without changing its value does not create a profile. Registering a control can replace the implicit zero default created by an earlier read; saved values and actual edits are preserved.

Moving the menu (Window.X/Y) while default is selected only changes its position for the current session; it does not create a profile or save changes into default. Menu position continues to save normally in editable profiles.

Temporary overrides last until released, or until the script stops, errors or unloads. Higher priority wins (integer 0..200); the latest update wins ties. Releasing a winning layer reveals the next script layer or the latest user value. get/list read effective values, while set, user edits, save and profile duplication operate on saved values underneath. Overrides survive profile switches while the owning script keeps running; they never enter profile JSON or create a profile. Register a UI control first for a script setting that needs an explicit default/type.

Config-override priorities are separate from aim/action priorities. The default is 75, but anti-aim’s 50 and ragebot’s 100 do not participate in config arbitration. Each key retains one layer per script. A lower-priority layer stays stored beneath the winner; updating a layer replaces that script’s value/priority and its tie order. Clearing the winner reveals the next layer, unlike command requests that already lost.

For example, script A overrides a key at 75 and script B at 100: B’s value is read. Clearing B reveals A’s value; clearing A reveals the current user/profile value.

Register controls once while the script runs (typically at top-level). They are cleared on Stop/Run and appear in the selected script’s OPTIONS view. Registration copies descriptions to the menu; it does not draw immediately or invoke Lua from the render thread. Repeated registration appends repeated rows, so use aqua.ui.clear() when rebuilding the options.

aqua.ui.card(title, key?) starts a new titled card. Following controls belong to it until the next card call; no closing call is needed. An optional bool config key adds a feature switch to the header, defaults to true if newly declared, and dims the card body when off. The script must check that key to disable its own behavior. Saved preferences survive registration. Controls before the first explicit card go in a card titled with the script’s display title. Cards flow through two columns when there is room and one column in a narrow workspace.

label remains wrapped display text, and separator() remains a hairline inside the current card. Neither starts a card. Existing scripts can adopt titled cards without changing the config keys used by their controls.

Every control except label, value and separator stores its value in the config key you pass, so it is saved per profile and readable with aqua.config.get(key). value rows are the exception: their second argument is a row id, the text lives only in the menu, and aqua.ui.set_value updates it. Use them for live readouts (hit counts, the current target) - writing those through aqua.config.set would mark the profile dirty and rewrite it every save.

Tooltips, subtitles and conditions are modifiers: call them right after the control or card they belong to (see the second table). Controls with config keys appear in menu search under <script title> > <card>, including their tooltips.

API Description
aqua.ui.card(title [, key]) Start a titled card; optional bool key adds a header feature switch
aqua.ui.checkbox(label, key [, default=false]) Toggle; registers config key
aqua.ui.switch(label, key [, default=false]) Same bool key as checkbox, drawn as a label with a switch on the right
aqua.ui.slider(label, key, default, min, max [, opts]) Slider. opts = { int = true, step = 5, format = "%d ms" }: int stores an int key, step snaps drag and typed values, format is a printf format with exactly one %d/%i (int) or %f/%e/%g (float) conversion; %% is a literal percent. A bad format raises an error
aqua.ui.color(label, key [, {r,g,b,a} [, presets]]) Color swatch (config float[4]). presets = { {r,g,b,a}, ... } (max 8) adds one-click swatches; pass nil as the default to keep white
aqua.ui.dropdown(label, key, items [, defaultIndex=0]) Combo; items = {"A","B",...}; config stores 0-based int
aqua.ui.segmented(label, key, items [, defaultIndex=0]) Side-by-side choice buttons for 2-5 short items; same storage as dropdown
aqua.ui.multiselect(label, key, items [, defaults]) Wrapping chips, any number selected (max 31 items). Config stores an int bitmask: item i (0-based) is on when bit.band(v, bit.lshift(1, i)) ~= 0. defaults is a bitmask or a table of 0-based indices
aqua.ui.text(label, key [, default="" [, maxLength=64]]) Single-line text field (config string, max 256). Saved while typing
aqua.ui.keybind(label, key [, defaultCode=UNBOUND]) Key capture button; config stores KeyState int (aqua.keys.*)
aqua.ui.button(label, key [, icon="play"]) Momentary press. Sets the bool config key true; the script polls it and sets it back to false (no cross-thread Lua callback). icon is a name from aqua.ui.icons, or "none"; an unknown name raises an error
aqua.ui.label(text) Display-only text row
aqua.ui.value(label, id [, text]) Read-only label + value row for live stats. id is NOT a config key: nothing is saved
aqua.ui.set_value(id, text) -> bool Update a value row (string or number). Cheap enough for every frame; false when no row has that id
aqua.ui.separator() Thin horizontal rule
aqua.ui.clear() Drop fields for this script (re-register after)
aqua.ui.icons Array of the icon names button accepts

Modifiers apply to the control or card declared just before them. They raise an error when nothing was declared yet or the last call was separator().

API Description
aqua.ui.tooltip(text [, meta]) Hover tooltip; meta is an optional small mono line ("default on"). On a card it sits on the title. Script tooltips also show in menu search
aqua.ui.description(text) Grey subtitle: under the label for checkbox / switch, a caption under every other row, the first line of a card body
aqua.ui.visible_if(key [, value]) Hide the row (or the whole card) unless config key equals value; without value, unless it is truthy (true, non-zero, non-empty). Hidden rows also leave menu search
aqua.ui.enabled_if(key [, value]) Same test, but the row (or card) stays visible, greyed out and locked

Conditions compare exactly: a float key equals a number only when it is that exact value, so condition on bools, dropdown indices or strings.

aqua.ui.card("Demo options", "Scripts.demo.Enabled")
aqua.ui.slider("Scale", "Scripts.demo.Scale", 1.0, 0.25, 3.0)
aqua.ui.color("Tint", "Scripts.demo.Tint", {0.3, 0.7, 1, 1})
aqua.ui.switch("Outline", "Scripts.demo.Outline", true)
aqua.ui.tooltip("Draws a dark edge so the text stays readable on bright maps.", "default on")
aqua.ui.card("Controls")
aqua.ui.dropdown("Mode", "Scripts.demo.Mode", { "Off", "Enemies", "All" }, 1)
aqua.ui.tooltip("Which players the overlay follows.")
aqua.ui.keybind("Toggle", "Scripts.demo.Key", aqua.keys.F6)
aqua.ui.slider("Delay", "Scripts.demo.Delay", 100, 0, 500, { int = true, step = 10, format = "%d ms" })
aqua.ui.enabled_if("Scripts.demo.Mode", 2)
aqua.ui.button("Do the thing", "Scripts.demo.Go", "bolt")
aqua.ui.value("Hits", "hits", "0")
aqua.ui.card("Filters")
aqua.ui.multiselect("Show on", "Scripts.demo.Parts", { "Head", "Chest", "Legs" }, { 0 })
aqua.ui.segmented("Size", "Scripts.demo.Size", { "S", "M", "L" }, 1)
aqua.ui.text("Prefix", "Scripts.demo.Prefix", "[demo]", 16)
aqua.ui.visible_if("Scripts.demo.Mode", 2) -- only while Mode = "All"
aqua.ui.color("Accent", "Scripts.demo.Accent", nil, { {1, 0.3, 0.3, 1}, {0.3, 0.7, 1, 1} })
local hits = 0
local function selected(mask, index) -- 0-based, like dropdown indices
return bit.band(mask, bit.lshift(1, index)) ~= 0
end
function on_draw()
if not aqua.config.get("Scripts.demo.Enabled") then return end
local mode = aqua.config.get("Scripts.demo.Mode") or 0
if aqua.input.key_pressed(aqua.config.get("Scripts.demo.Key") or 0) then
print("toggle key")
end
if aqua.config.get("Scripts.demo.Go") == true then
aqua.config.set("Scripts.demo.Go", false) -- consume the press
hits = hits + 1
aqua.ui.set_value("hits", hits) -- numbers are accepted too
end
local parts = aqua.config.get("Scripts.demo.Parts") or 0
if selected(parts, 0) then
-- "Head" is ticked
end
-- ...
end