# Lua in RGXbot: a course starting with your first script

You'll learn three places to use Lua, write your first condition, prepare an options form, and connect it to a route. Below the course you will find a full catalog of functions, aliases, variables and setting paths available in RGXbot. Content status: November 12, 2026. This is its own subset of Lua, so ready-made scripts from other bots require customization.

## 1. Choose a place of execution

**Action** runs recurring tasks. On the Action tab, click **Add**, enter a name, enable the action and set an interval in milliseconds, for example 1000. Paste the code, use **Test Lua**, then **Save** in Action. **Start** starts the module. **File → Save** saves the configuration to XML. Saving code and starting the module are separate steps.

**Waypoint Action** executes the code at a given point in the route. In the waypoint editor, select the type Action and enter the code. Use it to check supplies, switch tabs, or single sequence NPCs. Prepare labels before triggering the jump. Cyclic Action itself has no waypoint position.

**Script configuration → Lua / JSON definition** creates a settings form. Declare controls in this editor; it does not control the game. Use **Apply**, then **File → Save**. Form code is separate from Action code.

Do not paste the entire course into one field. Each block is a separate example; the block marked "Form" goes only to the form definition.

## 2. The first script without game commands

This example only calculates the values. Paste it into a new action and select **Test Lua** with the character loaded. No scheduled commands is the correct result here. Do not use `print()`: the interpreter does not support it.

```lua
-- $... to odczyty RGXbot. local to własna zmienna.
local minimumCap = 150
local lowCapacity = $cap < minimumCap
local healthPercent = $hppc
local missingMana = max(0, $maxmp - $mp)
```

`local` stores a value only during the current invocation. The next Action cycle starts the code again. `$hp` is a health-point count, while `$hppc` is a percentage. Assigning a value to a variable does not heal the character. Reads in Test Lua require the appropriate client context.

## 3. Conditions, nil and units

`if` starts a condition, `elseif` checks another possibility, and `else` handles the remaining cases. Each block ends with `end`. Compare with `==`, assign with `=`, and write inequality as `~=`. Combine conditions with `and`, `or`, `not`. Only `false` and `nil` are false; `0` is true.

```lua
local state = "gotowy"
if $stamina == nil then
    state = "brak odczytu staminy"
elseif $stamina < 1200 then
    state = "stamina ponizej 20 godzin"
else
    state = "stamina wystarcza"
end
```

Stamina has a unit of minutes: 1200 is 20 hours. `wait(1000)` represents one second and `spellcooldown()` returns seconds. Never compare an unknown reading `nil` with a number. Statuses `$poisoned` and similar may also be `nil`. No data means no knowledge, not a safe state or a zero balance.

## 4. First cyclic rule

The following code plans to stop modules upon death or when stamina is below the sample limit. First, view the plan in Test Lua, then select the threshold for your own session. The built-in panel stop conditions still apply; do not replace them with the script itself. `stopbot()` ends the current script.

```lua
if $dead then
    stopbot("Smierc postaci")
end
if $stamina ~= nil and $stamina < 1200 then
    stopbot("Limit staminy: ponizej 20 godzin")
end
```

**Lua test shows a plan and does not execute commands in the client.** Does not prove a successful stop, spell cast, or purchase. Only after checking the plan, run the appropriate module and confirm the result in the panel and the game. In the absence of stamina, the example does not make decisions based on this reading.

## 5. A spell with resource control

Choose a known spell, vocation and mana cost for your character. Below is an example condition for a character that knows `exura`. The 70% threshold and 20 mana cost are example settings. Do not run competing healing rules together without checking their configuration.

```lua
if not $dead and not $pz and $hppc < 70 and $mp >= 20 then
    if spellcooldown("exura") == 0 then
        spell("exura")
    end
end
```

`spell()` is a command and returns `nil`; do not write `if spell(...) then`. Evaluate HP and mana after execution. The reads refresh after the command, but the local variable retains the previously calculated value. For potions use `useself()` and `itemcooldown()`, and for runes on the target also check `$target ~= 0`.

## 6. A form for multiple characters

Paste the block below **into the form definition**, use Apply and check the fields in Script configuration. ID 268 is an example supply; adjust the ID, quantities and label name to your own route. The form does not buy potions by itself.

```lua-setup
addmenu("return", "Powrot i zapasy")
addcheckbox("return_enabled", "Kontroluj powrot", true)
addspinbox("min_cap", "Minimalny cap", 150, 0, 10000)
addtextbox("return_label", "Etykieta powrotu", "refill")
addsupply("mana", "Mana potion", 268, 50, 300)
```

Then create a **refill** label on the route and add waypoint Action with the code below. `needsrefill()` takes into account return enablement, minimum cap and enabled stock minimums. `supplyneed()` counts the miss against the target, not the minimum. Counters include equipment and open containers.

```lua
if needsrefill() then
    gotolabel(getuseroption("return_label"))
end
```

`gotolabel()` terminates script: no further lines are executed. Set the options to **From Script** or **Custom for Character** so that the common XML has different thresholds for different characters. Save the profile and XML, then check for reload.

## 7. Order, waiting and loops

Commands execute in order. `wait()` pauses the current script; `pausewalking()` pauses walking. With supported optional input, the shared Healer can run while waiting. Avoid long, infinite loops: use the Action interval for regular checks. Loops are limited to 10,000 iterations and nesting to 32 levels.

```lua
local attempt = 0
while attempt < 3 do
    attempt = attempt + 1
    if $target ~= 0 then
        break
    end
    wait(200)
end
```

Don't automatically retry a purchase or transfer after an unknown result. `submitted` means the input is sent and `confirmed` means the observer confirms the effect. Remember `inputactionid()` before `wait()` if you are tracking a specific operation: healing may change the last record.

## 8. NPC, bank i depozyt

First, lead your character to the correct NPC on the same floor. `npcsay("hi")` starts conversation; please check `npcisopen()`, `npcname()` and `npcmessage()` response before proceeding. Trade needs an open offer, money, space and the right name of the item. Buying and selling support at most 100 pieces per call.

`checkbank()` asks for the balance, and `bankbalance()` reads it. Do not add `npcyes()` after `bankwithdraw()`: confirmation is handled automatically. `depositall()` deposits coins in the bank. `depositer()` stores loot in the depot, while `deposititems()` stores all stacks of the specified IDs. `stashcount()` requires an open Supply Stash read.

The reference below contains full signatures and separate examples for banking, trading, ammunition, transfers, depot and Stash. Container numbers are current window IDs; do not store them permanently across sessions. Item slots in containers start at 0.

## 9. Reconnect and execution control

`reconnect()` must be the first instruction of the cyclic Action if it is to work without reading the PLAYER. Uses the reconnect settings and the same character selected in the panel. Does not bypass death, 2FA or limits; error stops further code. In a waypoint, the call only works when the engine executes that waypoint. Does not start the route automatically after disconnection.

```lua
reconnect()
local character = $character
local currentHealth = $hp
```

After each new script, check the syntax through Test Lua, read the plan, do one controlled test, and observe the result. In case of error, use log Action and Diagnostics. Keep the function name, arguments, and error message. An unknown function usually means a typo, a different case, or a piece of full Lua that this interpreter does not support.

## 10. What you know and where to look next

You can select context, read `$...`, check `nil`, create conditions, control entities, build a form, and direct a route. The API directory below includes all runtime function names, math aliases, form declarations, and variables. Search for e.g. `$stamina`, `buyitems`, `addspinbox` or `priority.targeting`. For each group, also read the requirements and restrictions, not just the signature itself.

The interpreter does not support `function`, `for`, `repeat`, table creation, `pairs`, `ipairs`, `print`, `require`, or file, network, and system libraries. The exception to the lack of tables are ready-made range values ​​from the JSON form: their fields `from` and `to` can be read. Use examples compatible with RGXbot.


# Lua in RGXbot - Functions and Examples

Complete RGXbot API reference. Covers Action and Action waypoints; form declarations use a separate context.

## How scripts execute

Commands in a running script execute in order. After a command, the engine refreshes the state: the next `itemcount()` or `$hp` uses a fresh read. Local variables keep their own values until that invocation ends. They do not retain state between separate Action script runs.

**Lua test shows the plan, without executing commands in the client.** Does not simulate purchasing or treatment results. Therefore, the condition after planned purchase may give a different result than during actual execution with status refresh.

Reads return numbers, strings or logical values. Commands such as `spell()`, `buyitems()`, and `checkbank()` return `nil` to Lua code; the result of the operation is checked with the next read, and errors are included in the logs. Do not write `if buyitems(...) then`: this is not a success function.

Commands use the selected input. `memory` means optional keyboard and mouse simulation; it does not switch the default `native` backend. UI reads and some window operations require a running client and an available memory controller, regardless of where the script runs.

### Re-login: `reconnect()`

```lua
reconnect()
```

No arguments: re-logs the **same character** selected in the panel. Uses the existing login controller and system account vault. It does not log out a character that is already properly online and does not restart the game client. Further script instructions execute only after a live character with the correct name is confirmed; they then refresh the readings from the new session. The result for Lua is `nil`, as with other control commands.

In the **Action** module, place `reconnect()` as the **first instruction** so it can run even when PLAYER reads are unavailable. A condition using `$hp` or `$name` before it still requires a character read. In Action waypoints, the function is available when the engine executes that waypoint; it does not automatically start the waypoint after a disconnection.

The function respects the panel's reconnect settings and limits the entire wait to a maximum of 10 minutes. Stopping or changing the client, backend, or selected character interrupts the operation. 2FA, death, incompatible character, missing account details or exceeded limit stops further instructions; they do not bypass security measures and do not automatically reset the attempt counter. Error Action requires an explicit retry. Statuses are visible in the **Reconnect…** window and the script log. Native mode maintains native gameplay controls; only login uses the existing UI bridge.

## Syntax and units

Supported are `local`, assignments, `if/elseif/else/end`, `while/do/end`, `break`, `return`, comments `--` and `--[[ ... ]]`, strings in both types of quotation marks, numbers, `true`, `false`, `nil`, `+ - * / % ^` actions, `== ~= < <= > >=` comparisons, `..` and `and/or/not` concatenation. Semicolons are optional. Function names are case-sensitive. Only `false` and `nil` are false: the number `0` is true.

Custom definitions `function`, `for/repeat` loops, tables, `pairs/ipairs`, `print`, `require`, file, network and system libraries are not supported. `local` does not provide the full scope semantics of complete Lua. Loop has a limit of 10,000 iterations and nesting has a limit of 32 levels.

| Value | Unit/range |
|---|---|
| `wait`, `pausewalking`, `$hunt_time`, `$target_time`, `$stand_time` | milliseconds |
| `spellcooldown`, `itemcooldown`, `$food` | seconds |
| HP/mana in percent | 0–100 |
| Item ID | integer 1–65535 |
| x/y coordinates and z floor | x/y 0–65535, z 0–15 |
| open container number | current window ID, usually 0–255; not the position on the screen |
| container slot | an index of 0 |
| depot box number | 1–21 |
| quantity in item commands | usually 1–100000; buy/sell maximum 100 per call |

In signatures, `[argument]` means an optional argument; do not type the square brackets in code. A `nil` container in `moveitem()` means equipment. Protected reads, including blessings, cooldowns and banking, raise an error for an unknown state. isitemontile() only checks the snapshot: a missing tile gives false.

Equipment slots: 1 head, 2 neck, 3 backpack, 4 armor, 5 hand/shield, 6 hand/weapon, 7 legs, 8 boots, 9 ring, 10 secondary slot, 11 Store Inbox. Arrows and bolts are refilled into the quiver, not directly into slot 10.

## Character variables

| Variables | Meaning |
|---|---|
| `$name`, `$level`, `$exp` | name, level, experience |
| `$character` | character recognized and assigned to the panel; identity also used for relog |
| `$hp`, `$maxhp`, `$hppc` | HP, maximum HP, HP percentage |
| `$mp`, `$maxmp`, `$mppc` | mana, maximum mana, mana percentage |
| `$cap` | available carrying capacity |
| `$x`, `$y`, `$z` | world position |
| `$posx`, `$posy`, `$posz` | item aliases |
| `$soul`, `$speed` | soul, client speed |
| `$stamina` | current stamina in minutes: 0–2520; `nil` when reading is not correct |
| `$food`, `$hungry` | food counter; hungry is true with counter 0 |
| `$pz`, `$pzblock` | Protection Zone and PZ lock |
| `$target`, `$followed` | ID of the attacked / followed target; 0 means none |
| `$attacked` | whether the ID of the attacked target is set, not "is someone hitting me" |
| `$dead`, `$deathcount` | death and death counter in the current death policy session |
| `$route` | name of the active route tab |
| `$statusknown`, `$statusflags` | read availability and raw state mask; mask is `nil` if no data is available |
| `$poisoned`, `$paralyzed`, `$burning`, `$hasted`, `$bleeding`, `$drunk`, `$mana_shield` | boolean or `nil` if the agent does not provide a mask |
| `$hunt_time`, `$target_time`, `$stand_time` | time of hunting, current target and standing still |

`$stamina` works in Cavebot waypoints and Lua actions. `1200` means 20 hours, and `2520` is a full 42 hours. The value refreshes with the script context, including after `wait()`. A missing read does not mean zero.

```lua
if $stamina ~= nil and $stamina < 1200 then
  gotolabel("rest")
end
```

Not every context has an active waypoint, target or hunting timer. Unavailable start times produce 0. The `$...` variables describe reads; assigning your own value does not change the character's state in the game.

## Reads and calculations

### Items, map and position

`inputstatus([id])`, `inputaction([id])`, and `inputactionid([id])` read the status, name, and operation ID of an optional input. Without an argument, they apply to the last operation. Missing record (also in native) gives `nil`. The history has a maximum of 64 entries, separately for the controller; the record does not contain arguments, typed text, or error details.

The states are `started`, `submitted`, `completed`, `confirmed`, `failed`, `cancelled`, `uncertain`. `submitted` means input was sent; `completed` means the function finished without separate confirmation of the effect. `failed` and `cancelled` describe execution ending, not undoing earlier events. `confirmed` requires an explicit observer of the effect. ListedLoot and transfers inside `Game.deposit_all` assign this state only after checking exact changes and the same character session. A depot sequence has a separate record for each transfer, not one confirmation for the entire sequence. Banking and trading in optional input also assign `confirmed` when their observer checks the balance/cash or items/amount respectively and the handler finishes in the same session. A function response alone does not promote the status. `moveitem()` does not automatically start such an observer.

```lua
local operation = inputactionid()
local status = inputstatus(operation)
local action = inputaction(operation)
```

The ID allows you to check an older transfer after another action. Results are consistent across the Lua snapshot and refreshed after dispatch. They are not intended to automatically repeat unconfirmed purchases or transfers.

`actionbarcount()` returns the number of recognized non-empty actionbar entries. `hasitemhotkey(id)` checks whether an item has a keyboard shortcut assigned to it, and `hasspellhotkey("formuła")` does the same for the exact spell formula (insensitive to case and extra spaces). No hash gives `false`; missing or failing to read the client is a script error. An entry without a shortcut can still be click-activated by the entry module. The shortcut itself does not confirm item inventory, spell availability, target, or cooldown.

```lua
-- Te zapytania tylko czytają konfigurację bieżącego klienta.
local entries = actionbarcount()
local healthBound = hasitemhotkey(266)
local healBound = hasspellhotkey("exura")
```

Continue to use `useself()`, `usetarget()` or `spell()` to execute. Maintains target/cooldown control and priority of recognized buttons before formula entry. The bar reading is shared within the Lua snapshot and refreshed after the action; names or abbreviations are not remembered globally between clients.

| Function | Result and action |
|---|---|
| `itemcount(id [, lokalizacja])` | number in equipment and open containers; the location filter matches part of the name without case |
| `groundcount(id, x, y, z)` | the sum of confirmed numbers of physical items in the read field |
| `isitemontile(id, x, y, z)` | presence of ID in the current map snapshot; does not indicate range of use or lifting ability |
| `canshoot(x, y, z)` | `true/false` for known local firing line; unknown state reports an error |
| `getfacing()` | `"north"`, `"east"`, `"south"` or `"west"` |
| `islocation([zakres])` | whether the character is near the current Action waypoint on the same floor; default range 0; returns false outside such a waypoint |
| `getlurelocation(nazwa)` | whether the named location exists; returns a bool, not coordinates |
| `getoption(nazwa)` | script option value or nil if absent |

`itemcount()` does not search closed backpacks or the bank. Open corpse or deposit windows may enter the total sum; use the name filter when you need a specific source. Container numbers can be reused after closing: do not hardcode them between sessions.

### Cooldowny, okna, NPC, bank i Stash

| Function | Result and requirements |
|---|---|
| `spellcooldown(formuła)` | self and spell group cooldown remaining, in seconds |
| `itemcooldown()` | common multi-use cooldown remaining, in seconds |
| `windowcount()` | number of visible dialogue/menu roots, not number of all sidebars |
| `windowexists(tytuł)` | whether a window with the exact title given is visible |
| `npcisopen()` | whether the NPC conversation is visible |
| `npcname()` | name from an open conversation or an empty string |
| `npcmessage()` | last recognized NPC response, no time/name prefix; blank string when none |
| `npccontains(tekst)` | whether the last reply contains the text, case-insensitive |
| `bankbalance()` | last confirmed balance in the entry context; do checkbank() first |
| `stashcount(id)` | quantity in the available open Supply Stash read; a closed Stash means unknown state |

`bankbalance()` does not ask the banker by itself. An unknown state is not a zero balance. Player messages are not NPC replies. `canshoot()` is a conservative assessment of the local map, not a guaranteed hit.

### Death and blessings

| Function | Result |
|---|---|
| `isdead()` | bool: correct max HP and current HP ≤ 0 |
| `deathcount()` | session death counter like `$deathcount` |
| `hasblessing(nazwa_lub_id)` | whether the character has the indicated blessing |
| `hasblessings()` | whether he has at least one of the seven ordinary blessings |
| `hasallblessings()` | whether he has all seven ordinary blessings |
| `blessingscount()` | number of regular blessings, excluding Adventurer and Twist |
| `blessings()` / `blessingnames()` | names of owned blessings as one comma-separated string |

| Customer ID | Name assumed by hasblessing |
|---|---|
| 1 | Adventurer's Blessing |
| 2 | Twist of Fate |
| 4 | Wisdom of Solitude |
| 8 | Spark of the Phoenix |
| 16 | Fire of the Suns |
| 32 | Spiritual Shielding |
| 64 | Embrace of Tibia |
| 128 | Heart of the Mountain |
| 256 | Blood of the Mountain |

Names can omit the `The` prefix and are case-insensitive. These are not sequential indices 1–9: use names or exact IDs from the table. An unavailable blessing read raises an error rather than falsely reporting none.

### Character states

| Function | Result |
|---|---|
| `statusknown()` | bool: Whether the snapshot contains a valid state mask |
| `statusflags()` | number: raw 32-bit mask, also with unrecognized bits |
| `hascondition("poisoned")` | bool: whether the indicated state is active |
| `conditions()` | text: active names separated by commas; empty when no conditions are active |
| `ispoisoned()`, `isparalyzed()`, `isburning()` | abbreviations for poisoning, paralysis and arson |
| `ishasted()`, `isbleeding()`, `isdrunk()` | shortcuts for haste, bleeding and drunkenness |
| `iselectrified()`, `iscursed()`, `ismanashielded()` | abbreviations for electrification, curse and magic shield |

Names for `hascondition`: `poisoned`, `burning`, `electrified`, `drunk`, `mana_shield`, `paralyzed`, `hasted`, `in_fight`, `drowning`, `freezing`, `dazzled`, `cursed`, `party_buff`, `pz_block`, `protection_zone`, `bleeding`, `hungry`. Accepted aliases: `poison`, `burn`, `fire`, `energy`, `paralyze`, `paralysed`, `haste`, `manashield`, `pz`, `pzblock`.

All functions except `statusknown()` raise an error when the read is unknown. An unsupported name is also an error. A missing `status` field in an old agent means unknown state, while explicit `status=0` means no active conditions. `$hungry` still uses the food counter; check the hunger icon with `hascondition("hungry")`. Unknown high bits are preserved only in `statusflags()`. The bit map comes from the PlayerStates enum in the [OTClient source](https://github.com/edubart/otclient/blob/master/src/client/const.h). Fully confirming every effect in the current client version requires observing each one live.

```lua
if statusknown() and ispoisoned() and $level >= 10 and $mp >= 30 then
    if spellcooldown("exana pox") == 0 then
        spell("exana pox")
    end
end
```

```lua
if statusknown() and hascondition("paralyzed") and $mp >= 10 then
    if spellcooldown("exura infir ico") == 0 then
        spell("exura infir ico")
    end
end
```

Such scripts schedule regular spells through a selected input. Merely sending a spell does not remove the effect. The built-in Condition Healer confirms the disappearance of a flag in the same session, uses shared cooldowns and a priority queue. It does not add any active rule by default.

### Mathematics and drawing

| Function | Result |
|---|---|
| `min(a, ...)` / `math.min(a, ...)` | minimum of 1–32 numbers |
| `max(a, ...)` / `math.max(a, ...)` | maximum of 1–32 numbers |
| `abs(x)` / `math.abs(x)` | absolute value |
| `floor(x)` / `math.floor(x)` | rounding down |
| `ceil(x)` / `math.ceil(x)` | round up |
| `random()` / `math.random()` | a number from 0 inclusive to 1 only |
| `random(maximum)` / `math.random(maximum)` | integer from 1 to maximum, both inclusive |
| `random(minimum, maximum)` / `math.random(minimum, maximum)` | total number in a closed range |

## Control commands

### Movement, combat, spells and items

| Function | Behavior |
|---|---|
| `move(kierunek)` | movement consistent with the selected input mode |
| `turn(kierunek)` | rotation without walking; only north/east/south/west |
| `facetarget(id)` | turn toward the active target |
| `attack(id)` | select the attacked target; 0 cancels your own targeting |
| `follow(id)` | following a goal; 0 cancels |
| `stop()` | stop using a supported hotkey/Esc in keyboard and mouse mode; does not click the Stop button |
| `cancel()` | cancel the current action |
| `loot()` | configured quick loot via selected input |
| `hotkey(skrót)` | e.g. `"F1"` or `"Ctrl+F2"`; uses the current client assignment |
| `spell(formuła)` | spell, with cooldown control and preference for recognized action bar |
| `spelltarget(formuła, id [, zasięg])` | spell on active target; default range 3, range 1–7 |
| `useitem(id)` | item use |
| `useself(id)` | use on your own character |
| `ensureaol([maksymalna_cena])` | keeps the established AoL or obtains and installs the missing one; limit 1–70000 gp, default 70000 |
| `usetarget(id, cel)` | using a rune/item on an actively attacked target on the map |
| `useinventory(slot, id)` | using an item from your inventory |
| `usecontainer(kontener, slot, id)` | using an item from an open container |
| `useworld(x, y, z, id)` | use of an object in the world field |
| `moveitem(id [, ilość, źródło, slot_źródła, cel, slot_celu])` | transfer; nil means equipment; specify the full source and destination |
| `pickupitem(id, x, y, z, kontener [, ilość, slot])` | pick up into a backpack; defaults to the largest available stack, slot selected automatically |
| `refillammo(id [, minimum, ilość, kołczan])` | replenishment below the minimum to the specified quantity; defaults to 50/100 and the only open quiver |

Movement directions: `north`, `east`, `south`, `west`, `ne`, `se`, `sw`, `nw`. `move()` is not an arbitrary coordinate command; the route is marked by waypoints. After setting the target, the status is checked by `$target`.

`spelltarget`, `facetarget` and `usetarget` require a valid active target. Out of range, target change, or blocked/unknown firing line interrupts appropriate use. The mere consumption of a rune or mana does not prove the damage dealt. Match the spell to the character's profession, level and learned skills.

The defaults for `moveitem()` are quantity 1, source/target nil and slots 0. This does not automatically search for the source; most often all six arguments are needed. If the quantity is greater than the source stack, the command may be rejected. Do not retry in a transfer loop with an unknown result. Replenishing ammo requires an open quiver and a supply in open backpacks. For several quivers, give his number. The function does not buy ammunition, does not open the quiver and does not equip it.

### Chat, NPCs and trading

| Function | Behavior |
|---|---|
| `chat_on()` / `chaton()` | enable typing in chat |
| `chat_off()` / `chatoff()` | disabling chat typing |
| `say(tekst)` | ordinary local message |
| `npcsay(tekst)` | send a message to the correct NPC conversation window |
| `npcdialog(akcja)` | action `yes`, `no`, `bye`, `trade` or `close` |
| `npcyes()` / `npcno()` | reply using the yes/no button |
| `npctrade()` | open trading |
| `npcbye()` / `npcclose()` | say goodbye / close the window |
| `buyitems(nazwa, ilość)` | purchase 1-100 pieces; checking the price and both status changes |
| `sellitems(nazwa, ilość)` | sale 1-100 pieces with item and money confirmation |

Use lowercase letters for NPCs. `say("hi")` can start a conversation, but send further replies via `npcsay()`. The `hotkey()` hash does not check the intent of a specific assignment; prefer semantic commands for spells and potions. `npcsay("hi")` and `npcsay("hello")` also work on a fresh client without the NPCs tab: the greeting itself then uses Local Chat. Other messages require an existing conversation or NPCs channel. Sending a greeting does not prove a response: check `npcisopen()`, `npcname()` and `npcmessage()` before proceeding. The NPC must be within conversation range on the same floor. On client 15.33.124f6c Native sends responses via the typed method of the NPC open window controller. Requires a current conversation and the same NPC within range. It doesn't use keyboard focus or the old protobuf setter. Gustavo's answer to "goblins" was confirmed in-game. In keyboard and mouse mode, the items you need must be accessible through a recognized action bar, inventory, or open windows.

### Bank, depozyt i Supply Stash

| Function | Behavior |
|---|---|
| `checkbank()` | asks the banker about the balance; then read bankbalance() |
| `bankdeposit([kwota])` | deposit an amount or all available coins |
| `bankwithdraw(kwota)` | paycheck; the module itself sends yes after the appropriate question and confirms the balance/coins |
| `depositall()` | depositing all coins into **bank**, alias bankdeposit() behavior |
| `opendepot()` | opening a nearby depository |
| `opendepotbox([numer])` | opening the deposit box; default 1 |
| `deposit(id [, skrzynka, ilość])` | store an item; defaults to box 1, quantity 1 |
| `withdraw(id [, skrzynka, ilość])` | retrieve into a backpack; defaults to 1 and 1 |
| `depositer([nazwa, depot(x,y,z), box(n)])` | putting aside the loot specified in the Looting configuration from all stacks |
| `findfreedepot([depot(x,y,z)])` | choose a free reachable tile in front of a locker and open your own depot |
| `reachnpc(nazwa [, dystans])` | approach the current position of an NPC with the exact name; default distance 3 tiles; range 1–3 |
| `deposititems(id, ... [, nazwa, depot(x,y,z), box(n)])` | store all stacks of the specified IDs |
| `box(n)` | box marker for depositer/deposititems, n=1–21 |
| `depot(x,y,z)` | position marker for high-level put away |
| `openstash()` / `closestash()` | open / close Supply Stash |
| `retrievestash(id [, ilość])` | download; default quantity 1 |
| `stowitem(id [, ilość])` | store in Stash; defaults to 1 |
| `stash(nazwa_plecaka)` | high-level Stash support for designated open backpack |

`depositall()` is for the bank; `depositer()` stores loot in the depot — they are different functions. Do not manually add `npcyes()` after `bankwithdraw()`: confirmation is already handled and checked. A large quantity in `deposit/withdraw` does not automatically loop through all containers; use `depositer` and `deposititems` for all stacks. These operations require depot access and the correct open backpacks. Coordinates alone do not bypass walls, distance or interaction requirements.

`findfreedepot()` checks the fresh map and characters, moves directly to the access square, then checks for occupancy again and opens the deposit. The optional item limits the search to the neighborhood of a given deposit. If there is no free station, it repeats readings for up to 30 seconds and then pauses the sequence. Does not confirm deposit or purchases without success. In a live script, it can be followed by a call to `depositer(box(1))`.

### Click on Look

| Tutorial function Native | Behavior |
|---|---|
| `buttonexists(napis)` | observing clear dialogue buttons |
| `clientbutton(napis [, tytuł_okna])` | clicking the button selected after the text and optional window title |
| `tutorialhint()` | current tutorial tip; blank text means no hint |
| `tutorialhintcontains(tekst)` | matches the current hint, case-insensitive |
| `tutorialclick(oczekiwana_wskazówka [, przycisk])` | click the current tutorial highlight; left/right button |
| `tutorialdrag(oczekiwana_wskazówka)` | dragging the only visible item to the empty target indicated by the tutorial |
| `tutorialuse(oczekiwana_wskazówka)` | clicking the highlighted tool, then the second tutorial objective, e.g. using a rope |
| `classiccontrols()` | set Classic Controls in the client, Apply and reopen options to verify saving; requires Native, closed dialogs and no monsters on screen |
| `choosevocation(nazwa)` | select and confirm a vocation in the tutorial window; Confirm must belong to that vocation's card |

Dragging selects the source from the item read in the slot, not the position of the hint text. Multiple destinations, two occupied slots or a missing item abort the operation. The mouse is released even after an error. These functions use the agent's existing Qt bridge and do not move the system cursor or switch input to memory.

In Native mode, `buttonexists("Start")` reads visible client dialog buttons and `clientbutton("Start")` selects one unambiguous button after its text. Does not select action bar items or click fixed coordinates. No read, multiple matching buttons, death or session change abort the operation. Simply sending a click does not confirm that you have completed the tutorial; read the next screen/item before the next action. Memory mode does not automatically switch to this operation Native.

| Function | Behavior |
|---|---|
| `opencontainer(id)` | opening the equipped backpack from the default slot 3; to search inside use openitem() |
| `openitem(id_lub_nazwa [, źródło, nowe_okno, indeks])` | searching and opening; default new window=true, index=1; source is name or ground(x,y,z) |
| `ground(x,y,z)` | position marker for openitem() |
| `closecontainer(numer)` | close the specified open container |
| `containerup(numer)` | open the parent container in this window |
| `minimizecontainer(numer)` / `maximizecontainer(numer)` | set the window state; repeating it does not toggle it back |
| `closewindows([limit])` | Esc for dialogs/menus, default 6, maximum 8; does not automatically close the death window |
| `lookinventory(slot, id)` | Look at an equipped item |
| `lookcontainer(numer, slot, id)` | Look in a specific container |
| `lookworld(x,y,z)` | Look on top of the world box |
| `lookcreature(id)` | Look at the specified character/NPC/monster |
| `deathdialog([akcja])` | defaults to revive; character_list opens the character list |

Look waits for a new customer response; in Lua it does not return description text. No fresh result is a no-retry error. Closing/up/collapsing the container checks the actual identity of the window. `containerup()` with no parent available is not automatically treated as a success.

### Command aliases

In addition to table names, `npc_dialog()`, `use_item()`, `use_self()`, `use_inventory()`, `use_container()`, `use_world()`, `move_item()`, and `open_container()` work, with the same arguments as their compact counterparts.

## Route and configuration

### Script configuration and character profiles

The **Script configuration** tab contains options and supplies, the single backpack rule editor and a separate Lua definition editor. After editing, click **Apply**. Cavebot and Action use the same values on the next read; restarting the route is unnecessary. **File → Save** stores the complete configuration in XML: the form definition (`setup_code`) and default option values (`script_api.options`). Each option can use **From script** or **Custom for character**. Only explicitly selected overrides go into the selected character profile, preserving XML defaults. **Save as…** creates a copy without modifying the previous script. `setsettings()` and **Apply** change the current configuration; saving XML requires **Save** or enabling automatic saving in File. Selecting another script does not carry over the previous script's options.

The form definition is performed offline. The following declarations, local variables, conditions, limit loops, and basic calculations are allowed. Game commands, client reads, `wait()` and setting changes are rejected in this editor. Control declarations are not intended for waypoints or circular Action.

The **Lua / JSON definition** editor also accepts a JSON form in the style of WindBot User Options: a single group, an array of groups or comma-separated groups. Supported types are `group`, `lineedit`, `checkbox`, `combobox`, `spinbox`, `doublespinbox` and `rangespinbox`. Groups have `children`, `column` (1 or 2) and optional `checkable`. The `description` fields provide tooltips; text fields support `placeholder`, and numeric fields support `prefix` and `suffix`. A group's checkbox disables editing of its children; the script should check the group's value before performing related actions. This is not an importer for entire WindBot scripts.

`rangespinbox` stores two bounds as `[od, do]`; a single `value` sets both bounds equally. `getuseroption("wait_spawn")` returns a table with `from` and `to` fields, for example:

```lua
if getuseroption("wait_spawn") then
  local bounds = getuseroption("wait_spawn")
  wait(random(bounds.from, bounds.to))
end
```

The **Insert Sample JSON** button contains a set of basic controls. The JSON form and its values ​​are stored in the same XML and profiles as the Lua form. Changing the format does not trigger any game commands.

| Declaration in the form editor | Behavior |
|---|---|
| `addmenu(klucz, tytuł)` | create or select a group/menu for subsequent controls |
| `addcheckbox(klucz, etykieta, domyślna)` | checkbox with a true/false value |
| `addspinbox(klucz, etykieta, domyślna, minimum, maksimum [, krok])` | number field, default step 1; for fractions, enter a step smaller than 1 |
| `addtextbox(klucz, etykieta, domyślna)` | text field |
| `addcombobox(klucz, etykieta, domyślna, wybory)` | list; selections are text, e.g. `"safe\|normal"` |
| `addlabel(tekst)` | description in the current menu |
| `addsupply(klucz, etykieta, id, minimum, cel)` | four fields `supplies.KLUCZ.enabled/id/minimum/target`; the minimum must not exceed the target |
| `addbackpack(nazwa, id [, rodzic, przeznaczenie])` | adds a missing rule to the existing backpack manager; does not overwrite a saved rule with the same name; purpose: loot/gold/supplies/other |

Default backpack rules are added once per character configuration. Later renaming or deleting a rule in a profile does not recreate the old rule from the template.

Control keys must be unique; ASCII letters, numbers, underscore, period and dash are allowed. Maximum of 200 controls, 30 menus and 50 backpack rules per definition. Definition example (paste into **Lua Definition**):

```lua-setup
addmenu("return", "Powrót i zapasy")
addcheckbox("return_enabled", "Kontroluj powrót", true)
addspinbox("min_cap", "Minimalny cap", 150, 0, 10000)
addtextbox("return_label", "Etykieta powrotu", "refill")
addsupply("mana", "Mana potion", 268, 50, 300)
addmenu("hunt", "Polowanie")
addcombobox("hunt_mode", "Tryb", "normal", "safe|normal")
addlabel("Dostosuj zapasy i nazwy plecaków do postaci.")
addbackpack("Backpack", 2854, "", "supplies")
```

| Function available in Cavebot and Action | Result / behavior |
|---|---|
| `getuseroption(klucz)` | alias of getoption(); returns the saved value or the form default |
| `setuseroption(klucz, wartość)` | alias setoption(); validates the type and scope of the declared control |
| `getsettings(ścieżka)` | read a form option or actual settings from the table below |
| `setsettings(ścieżka, wartość)` | value recording; execution refreshes the context before the next command |
| `getsetting(ścieżka)` / `setsetting(ścieżka, wartość)` | aliasy getsettings/setsettings |
| `caplow()` | whether `$cap` is below the min_cap option; a missing threshold means 0 |
| `supplycount(klucz)` | count of the item ID from addsupply(); equipment and open containers |
| `supplyneed(klucz)` | how much is missing from the target quantity, not less than 0 |
| `supplieslow()` | whether any enabled stock is below the minimum |
| `needsrefill()` | return_enabled i (caplow lub supplieslow); brak return_enabled oznacza true |

| Getsettings/setsettings path | Value |
|---|---|
| `setup.min_cap`, `setup.return_label`, `setup.supplies.mana.target` | form options; options/useroptions prefixes are equivalent; the key itself is also allowed |
| `navigation.pass_players_outside_pz`, `navigation.ignore_players_path`, `navigation.enter_echo_raids` | true/false, actual navigation configuration |
| `looting.enabled`, `looting.quick_loot`, `looting.eat_food`, `looting.auto_echo_loot`, `looting.own_kills_only` | true/false, existing LootConfig |
| `containers.Backpack.keep_open`, `containers.Backpack.item_id`, `containers.Backpack.parent`, `containers.Backpack.purpose` | properties of an existing rule called Backpack; also the remaining ContainerRule fields except name |
| `priority.targeting`, `priority.looting`, `priority.cavebot` | priority 1–10; lower wins |

You may use `/` instead of `.` as a separator. An unknown module path, missing backpack rule or incorrect type is an error. This is not full compatibility with arbitrary historical WindBot paths. The functions do not change input configuration or login data, or start GUI modules. `looting.enabled` changes module configuration, not its start switch. Likewise, reading supplies does not buy items or search closed backpacks. Purchases/retrievals require explicit Lua code on the route.

Example of waypoint Action using the form:

```lua
if needsrefill() then
    gotolabel(getuseroption("return_label"))
end
```

Example of changing settings during execution:

```lua
setsettings("setup.min_cap", 200)
setsettings("navigation.enter_echo_raids", false)
setuseroption("hunt_mode", "safe")
```

In Test Lua, setting changes are only a plan; actual values ​​remain unchanged. Saving a profile and applying a form do not perform game actions.

The form pattern is based on the manufacturer's [WindBot Cavebot User Options](https://www.tibiawindbot.com/features.html) mechanism. RGXbot uses its own declaration syntax and settings paths.

### Route and options control

| Function | Behavior |
|---|---|
| `gotolabel(etykieta [, sekcja])` | jump to a labeled waypoint; ends the current script |
| `gototab(nazwa)` | switching the route tab; ends the current script |
| `reachgrounditem(id [, dystans])` | approach a visible object; default distance 1, range 0–18; ends the script |
| `wait(ms)` | current script wait, 0–3600000 ms |
| `stopbot([powód])` | stops all modules and ends the script; optional reason up to 200 characters |
| `pausewalking(ms)` | pause of walking only, 0–3600000 ms |
| `setoption(nazwa, wartość)` | writing script option; is not an arbitrary call to GUI settings |
| `setlocation(nazwa, x,y,z)` | save a named location |
| `setlurelocation(nazwa, x,y,z)` | location save alias |
| `addspecialarea(nazwa, x,y,z)` | adding a named field excluded from the route |
| `removespecialarea(nazwa)` | deleting such a field |
| `setpriority(moduł, priorytet)` | targeting/looting/cavebot priority, number 1–10; lower wins |

In optional control mode, `wait` leaves the client available for treatment. During pause, the scheduler checks the same character session and supports shared Healer enabled; the next statement is still waiting for the end of the pause. Cancellation, death, or change of the session interrupts further instructions from this script. Treatment may change the last input operation during `wait`: to track a previous transfer, keep its `inputactionid()` before pausing.

Technical key `cavebot` stands for the route module. Locations and options supports runtime scripts; are not local Lua variables. Jump features require an existing route/tag. `getlurelocation()` only checks for the presence of a name. `openitem()` and `stash()` also end the current script. In the Test Lua plan, the `findfreedepot/depositer/deposititems` operations end the plan, while in the live execution they can continue after the confirmed operation.

## Examples

Each block below is a separate example. Adjust item IDs, container numbers, spells, labels and coordinates for your character.

### Knighta healing with mana and cooldown control

```lua
if not $dead and $hppc < 70 and $mp >= 10 then
  if spellcooldown("exura infir ico") == 0 then
    spell("exura infir ico")
  end
end
```

For permanent healing, use primarily Healing rules. The example shows a function call; the spell must be available to this character.

### Potion and mana replenishment

```lua
if $mppc < 40 and itemcount(268, "bag") > 0 then
  if itemcooldown() == 0 then
    useself(268)
  end
end
```

### Rune on the active target

```lua
if $target > 0 and not $pz then
  if itemcount(21352) > 0 and itemcooldown() == 0 then
    usetarget(21352, $target)
  end
end
```

ID 21352 means Lightest Missile Rune in the current directory. For another rune, check the ID and requirements. The position and line of fire are checked by the action.

### Food and explicit chat mode

```lua
if $hungry and itemcount(3578) > 0 then
  useitem(3578)
end
chat_off()
```

### Bank: checking and safe withdrawal

```lua
checkbank()
if bankbalance() >= 100 then
  bankwithdraw(100)
end
npcclose()
```

Stand by the banker. The withdrawal itself confirms `yes`; do not add a second confirmation. `bankdeposit()` deposits all coins without argument.

### Buy missing potions

```lua
local brakuje = 5 - itemcount(266, "bag")
if brakuje > 0 then
  buyitems("health potion", min(brakuje, 100))
end
```

Stand near the correct seller with an available offer, money and space. A purchase does not automatically travel to a shop. The following separate example sells one vial only after a read confirms ownership:

```lua
if itemcount(285, "bag") > 0 then
  sellitems("empty potion flask", 1)
end
```

### NPC conversation

```lua
say("hi")
if npcisopen() and npcname() == "Aurelia" then
  npctrade()
end
```

Do not replace `npcsay()` with plain `say()` in subsequent replies. `npccontains("balance")` checks the response, not the history of its own statements.

### Completing the quiver

```lua
refillammo(3447, 50, 100)
```

Requires exactly one open quiver. If there are several, select the current ID:

```lua
refillammo(3447, 50, 100, 2)
```

### Move one item into equipment

```lua
moveitem(3338, 1, 0, 2, nil, 6)
```

The example assumes the Bone Sword in container 0, slot 2. Slot 6 is the weapon hand. The source state and actual window number must be determined before running. Do not run this repeatedly when the result of the first operation is not confirmed.

### Saving loot and downloading from Stash

```lua
opendepotbox(2)
deposititems(3031, 3272, box(2))
```

```lua
openstash()
local brakuje = 5 - itemcount(266, "bag")
if brakuje > 0 and stashcount(266) >= brakuje then
  retrievestash(266, brakuje)
end
closestash()
```

### Containers and Look

```lua
maximizecontainer(0)
lookinventory(6, 3338)
lookcontainer(0, 0, 285)
minimizecontainer(0)
```

The example requires a vial in slot 0 of container 0. You can separately use `containerup(0)`, `closecontainer(0)`, `lookworld($x, $y, $z)` or `lookcreature($target)` with a positive and visible ID.

### Return to a label when supplies are low

```lua
if itemcount(266, "bag") < 2 or $cap < 40 then
  gotolabel("depo")
end
```

The route must have the label `depo`. This is a route switch, not an independent combat escape system and not a guarantee of a safe route.

### Checking blessings and death

```lua
local liczba = blessingscount()
local twist = hasblessing("Twist of Fate")
local komplet = hasallblessings()
local nazwy = blessings()
```

```lua
if isdead() then
  deathdialog("revive")
  return
end
```

For an automatic response after death, prefer the death policy settings. A script does not change its death limit or its decision to resume.

### Options, field lock and random pause

```lua
setoption("tryb_testowy", true)
setlocation("baza", $x, $y, $z)
addspecialarea("pole_do_ominiecia", $x + 1, $y, $z)
setpriority("targeting", 1)
pausewalking(random(200, 400))
```

Remove the block later with `removespecialarea("pole_do_ominiecia")`. Randomness in this example affects time; mouse movement and walking behavior are configured separately in the optional input module.

### A loop with an explicit limit

```lua
local i = 0
while i < 3 do
  i = i + 1
  if $dead then
    break
  end
  wait(100)
end
```

Don't create an endless healing or trading loop. The Action script can be called periodically by the module and the state is refreshed between actions.

## Errors and limitations

- Unknown cooldown, blessy, balance or Stash is a read error, not a zero.
- Wrong number of arguments, ID 0, non-integer number and unknown function name
are rejected. Exceptions such as `attack(0)` are described separately.
- Altered window, missing item, unconfirmed transfer or purchase
require condition checking. Don't repeat the operation blindly.
- The Lua test does not check item availability, spell learning, or result
server. The final result is confirmed by readings during live performance.
- Opening backpacks, trading, and Look may require visible controls
inside the client, also when the client itself runs on a hidden virtual screen.

Related descriptions: [ranged combat](RANGED_INPUT.md), [healing scheduler](HEALING_SCHEDULER.md), [input module](INPUT_EXTENSIONS.md).
