Skip to main content

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).

Check that forge exists before using it

The 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.

Called with a dot, never a colon

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

ActionParams
teleportx, y, z?
gotox, y
wait_zonezone, timeout_ms?
sync_positionleader_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

ActionParams
combat_autostrategy?
combat_castCombatCardParams
combat_discardCombatCardParams
combat_pass
combat_flee
combat_draw
combat_willcastCombatCardParams

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

ActionParams
send_keykey, duration_ms?
interact_npcnpc_name?
collect_wispmax_distance?, wisp_type?, min_mob_distance?, max_collect?
auto_dialoguesee below
speed_togglemultiplier?, toggle?
commandcommand

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

ActionParams
wait_conditioncondition, timeout_ms?, plus condition-specific fields
lua_scriptscript?, 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:

ErrorCause
action or action_type is requiredEmpty name, or a table with neither key — also what a : call produces
no client availableNo client_id given and the host knows of no client
anything elseThe 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.