forge
forge.Execute runs a named action against a client. It is the same action vocabulary the
desktop app's trainer uses, exposed to Lua so a plugin can reuse an action rather than
reimplementing it.
local ok, err = forge.Execute(action_type, params, client_id)
local ok, err = forge.Execute({ action = 'teleport', params = { x = 1, y = 2 } })
Returns (boolean, string|nil).
forge exists before using itThe global is only created when the host wires an action executor into the VM, and neither
shipped host does today — on the libwiz console and in the desktop app's trainer alike,
forge is nil and calling forge.Execute raises "attempt to index a non-table object".
Treat this page as the contract for the seam, not as an API you can rely on reaching. Anything you ship should either avoid it or guard:
if forge == nil then
return fallback_using_the_client_api()
end
Every action below has a direct equivalent on the client object or in a
libstrat strategy, except sync_position, speed_toggle, and collect_wisp,
which have no binding at all. Write against those directly and you have working code today.
forge.Execute('teleport', ...) is correct. forge:Execute('teleport', ...) passes the forge
table as the first argument, which the function reads as an options table with no action in
it — so it returns false, 'action or action_type is required' no matter what you asked for.
Actions
Movement
| Action | Params |
|---|---|
teleport | x, y, z? |
goto | x, y |
wait_zone | zone, timeout_ms? |
sync_position | leader_id?, offset_x?, offset_y?, spread_radius? |
sync_position is the interesting one — it moves a client to a leader's position with an
optional offset or scatter, which is the primitive behind follower behaviour.
Combat
| Action | Params |
|---|---|
combat_auto | strategy? |
combat_cast | CombatCardParams |
combat_discard | CombatCardParams |
combat_pass | — |
combat_flee | — |
combat_draw | — |
combat_willcast | CombatCardParams |
CombatCardParams: spell_template_id, spell_name, spell_display_name, spell_match
(exact/contains), target_type (self/ally/enemy), target_strategy, target_index,
require_castable.
combat_willcast tests whether a cast would resolve without performing it — useful for a
plugin that needs to know if a card is playable before committing to a plan.
Input and interaction
| Action | Params |
|---|---|
send_key | key, duration_ms? |
interact_npc | npc_name? |
collect_wisp | max_distance?, wisp_type?, min_mob_distance?, max_collect? |
auto_dialogue | see below |
speed_toggle | multiplier?, toggle? |
command | command |
collect_wisp is a complete wisp-collection routine in one call — it finds wisps of a type,
teleports to them, and respects mob clearance. If your recovery logic is doing nothing more
elaborate than that, use this instead of hand-rolling it:
forge.Execute('collect_wisp', {
wisp_type = 'health',
max_collect = 5,
min_mob_distance = 700,
}, c:GetID())
auto_dialogue params: interval_ms, timeout_ms, key, accept_quests, yes_no_choice
(yes/no/none), use_fast_skip.
Control flow
| Action | Params |
|---|---|
wait_condition | condition, timeout_ms?, plus condition-specific fields |
lua_script | script?, plugin?, args?, timeout_ms? |
Conditions for wait_condition: in_combat, not_in_combat, in_zone, health_below,
mana_below, has_entity, entity_nearby, is_loading, is_in_dialog, has_item,
quest_complete, lua_eval.
forge.Execute('wait_condition', {
condition = 'in_zone',
zone = 'Grizzleheim/GH_Hero',
timeout_ms = 30000,
}, c:GetID())
Extra fields depend on the condition: zone, threshold, entity_name, max_distance.
lua_script runs another plugin or a snippet. Composing plugins this way is possible but
usually not what you want — the called plugin runs in its own VM with its own state, so it is
closer to a subprocess than a function call.
Call shapes
forge.Execute('teleport', { x = 100, y = 200 })
forge.Execute('teleport', { x = 100, y = 200 }, c:GetID())
forge.Execute({ action = 'teleport', params = { x = 100, y = 200 }, client_id = c:GetID() })
Omitting client_id targets the active client — the host's default, or the first client it
knows about. The table form accepts action or action_type for the name; if both are
present, action_type wins.
params must be a table. Any other value is not an error, it is silently treated as no
parameters at all, so forge.Execute('teleport', 'x=1') runs a teleport to nowhere.
Errors
local ok, err = forge.Execute('teleport', { x = x, y = y }, c:GetID())
if not ok then
log_throttled('forge_tp', 'teleport failed: ' .. tostring(err))
end
Three failures come from the binding itself, before any action runs:
| Error | Cause |
|---|---|
action or action_type is required | Empty name, or a table with neither key — also what a : call produces |
no client available | No client_id given and the host knows of no client |
| anything else | The host's own executor rejected the action |
When no client is connected, pass client_id explicitly or use the table form. The positional
form's fallback re-reads its first argument as a client id, so with zero clients attached it
can hand your action name to the host as an id and fail with an error that mentions the
wrong thing.
An unknown action name returns false with an error rather than throwing, so a typo shows up
as a failed call rather than a crashed plugin — but it will not be caught until that line
runs. The direct client API is checked by the editor, which is another reason to prefer it.