Lua function reference / Script modules

MovementModule

Waypoint routes, formation following, and location-based random movement.

Lua script module · Reviewed October 7, 2026.

MovementModule supplies shared Lua helpers for authored waypoint loops, formation following, and random movement selected by spawn location. Load it alongside NPCModule to give initialized NPCs movement, and DialogModule for player conversations.

This reference incorporates the local LUA-RANDOM-MOVEMENT.md and LUA-FORMATION-FOLLOW.md guides, checked against MovementModule and the native C++ handlers. Helper defaults and direct engine-call defaults are identified separately.

Loading and calling

require "SpawnScripts/Generic/MovementModule"

function spawn(NPC)
    -- Call the movement helper appropriate for this NPC here.
end

The file defines four global functions. It does not return a movement object, and there is no MovementModule(NPC) initializer. Loading it does not automatically start a route. NPCModule does not import it; require it explicitly.

Location maps are keyed by GetSpawnLocationID(NPC), not the NPC's database spawn ID, runtime ID, or placement ID. Pass a map explicitly when it is declared local in the calling script. The optional fallback globals cannot see that script's local variables.

All calls below use the NPC as the only spawn argument. The server's spawn callback is lowercase function spawn(NPC).

Evidence: server/SpawnScripts/Generic/MovementModule.lua:6–74; WorldServer/Lua/LuaFunctions.cpp:807–816; WorldServer/Zone/zoneserver.cpp:6031–6034.

Authored waypoint routes

AddWaypointRoute(NPC, route) appends an authored sequence of movement-loop points to the NPC. Use it when a script already has the route and does not need a location lookup.

Argument Requirement Meaning
NPC Required Valid NPC/spawn handle whose movement loop is modified.
route Route table, or nil for no action Dense array of {x, y, z, speed, delay} point arrays.

Each point contains these five numeric fields:

Point index Value Meaning
1–3 x, y, z World coordinates for the waypoint.
4 speed Movement speed sent to the engine.
5 delay Wait at that point, in seconds. Use 0 for no wait.
require "SpawnScripts/Generic/MovementModule"

local patrol = {
    {100, 0, 200, 2, 0},
    {110, 0, 200, 2, 3},
    {100, 0, 200, 2, 0},
}

function spawn(NPC)
    AddWaypointRoute(NPC, patrol)
end

Coordinates above are illustrative; replace them with valid coordinates for the NPC's zone. The helper uses ipairs, so holes end iteration. It forwards only the five listed point fields to MovementLoopAddLocation; callback, heading, and navigation-path arguments are not forwarded by this helper. It does not clear an existing loop, so repeated application appends more points. Return value: none.

Evidence: MovementModule.lua:34–49; WorldServer/Lua/LuaFunctions.cpp:1221–1244; WorldServer/Spawn/Spawn.cpp:4725–4738, :6112–6123.

Waypoint routes by location

ApplyWaypointRoute(NPC, waypointRoutesByLocationID) finds the NPC's route in a location-keyed map and passes it to AddWaypointRoute.

Argument Requirement Meaning
NPC Required NPC whose spawn location chooses the route.
waypointRoutesByLocationID Optional Map of [spawnLocationID] = route; falls back to global WAYPOINTS_BY_LOCATION_ID.
require "SpawnScripts/Generic/MovementModule"

local routes = {
    [10001] = {
        {100, 0, 200, 2, 0},
        {110, 0, 200, 2, 3},
    },
}

function spawn(NPC)
    ApplyWaypointRoute(NPC, routes)
end

Replace 10001 and the coordinates with the actual spawn location and route. A missing map or missing location entry does nothing. Return value: none. A map variable's name does not change the lookup: even a variable named WAYPOINTS_BY_SPAWN_ID is indexed by spawn location ID by this helper.

The reviewed SouthFreeport/SergeantNominus.lua supplies a local WAYPOINTS_BY_LOCATION_ID map explicitly, then calls ApplyWaypointRoute during spawn.

Evidence: MovementModule.lua:51–60; server/SpawnScripts/SouthFreeport/SergeantNominus.lua:14–18, :163–168.

Formation following

ApplyFormationFollow(NPC, formationFollowByLocationID) looks up the follower's spawn location and passes its formation configuration to the native FormationFollow function.

Argument Requirement Meaning
NPC Required Follower NPC handle.
formationFollowByLocationID Optional Map of [followerSpawnLocationID] = configuration; falls back to global FORMATION_FOLLOW_BY_LOCATION_ID.

A matching map entry uses these configuration fields:

Configuration field Helper default Meaning
leader Required in a matching entry Leader spawn handle or numeric leader reference.
pattern "behind" Pattern name or numeric pattern ID.
back 3.0 Distance behind the leader or preceding rank.
side 0.0 Lateral spacing.
speed 0 Engine default walking speed, currently 2.0.
catchup 8.0 Distance threshold for catch-up behavior.
maxcatchup 5.0 Speed for catch-up bursts.
slot 0 Automatic slot assignment; positive values request explicit slots.
leaderRefType refType, then "location_id" Interpretation of a numeric leader reference. refType is a fallback field name.

These defaults use Lua's or: nil or false selects the fallback, while numeric 0 is retained. The wrapper defaults numeric leaders to location IDs. Calling native FormationFollow directly without LeaderRefType instead defaults numeric leaders to database spawn IDs. Native catch-up defaults are 0/0, letting the engine derive them; this wrapper explicitly supplies 8.0/5.0 unless overridden.

The native call described by LUA-FORMATION-FOLLOW.md has this argument order. Its first five arguments are required; the remaining five are optional trailing arguments:

FormationFollow(NPC, Leader, Pattern, BackDistance, SideDistance,
                Speed, CatchupDistance, MaxCatchupSpeed, Slot, LeaderRefType)

Use the module to map a follower's location to that configuration:

require "SpawnScripts/Generic/MovementModule"

local followers = {
    [10002] = {
        leader = 10001,
        pattern = "inverse_v",
        back = 4.0,
        side = 2.0,
        speed = 0,
        catchup = 8.0,
        maxcatchup = 5.0,
        slot = 1,
        leaderRefType = "location_id",
    },
}

function spawn(NPC)
    ApplyFormationFollow(NPC, followers)
end

Here 10002 is the follower's location ID and 10001 is the leader's location ID; replace both with real values. The leader can be resolved when it becomes available. The helper returns without action if the map or follower entry is missing. It has no return value and does not report whether a leader has already resolved. A missing/invalid leader in a matched entry is rejected by the native code.

Pattern Aliases Numeric ID Shape
behind follow, column 0 Column behind the leader.
flank_left left 1 Behind and left.
flank_right right 2 Behind and right.
inverse_v wedge, v 3 Alternating left/right wedge.
staggered_column staggered 4 Column with alternating side offsets.
echelon_left — 5 Diagonal behind and left.
echelon_right — 6 Diagonal behind and right.

Names are matched case-insensitively by the native resolver; unknown names fall back to behind. Numeric leaders accept location_id (spawn_location_id, location), runtime_id (id, entity_id), or spawn_id. A leader spawn object uses its runtime ID and ignores the reference-type argument.

For a behind column, explicit slot 1 is closest to the leader and later slots follow behind it. In inverse_v, odd/even slots alternate sides. Use explicit positive slots when formation positions need to be predictable; 0 asks the engine to assign a slot.

Formation rebuilds are queued asynchronously; configuration does not mean the NPC moved before the Lua call returned. Enabling a valid formation configuration resets existing random movement configuration. Back/side distances and speed settings are clamped to nonnegative values by the engine. This is runtime movement configuration, not a database update.

Evidence: MovementModule.lua:6–32; LUA-FORMATION-FOLLOW.md; WorldServer/Lua/LuaFunctions.cpp:1333–1449; WorldServer/Spawn/Spawn.cpp:5261–5303. Stormhold/adefiledtempest.lua:13–39 demonstrates a location-keyed formation map.

Random movement by location

ApplyRandomMovementByLocation(NPC, randomMoveByLocation) selects a random-movement argument array by the NPC's spawn location and calls RandomMovement(NPC, ...) with its unpacked values.

Argument Requirement Meaning
NPC Required NPC whose location chooses the configuration.
randomMoveByLocation Required table [spawnLocationID] = {NegDist, PosDist, Speed, MinDly, MaxDly, PointCount, RefreshSeconds, Flying}; final three values are optional.
require "SpawnScripts/Generic/MovementModule"

local randomByLocation = {
    [10001] = {-10, 10, 2, 5, 12, 30, 600, 0},
}

function spawn(NPC)
    ApplyRandomMovementByLocation(NPC, randomByLocation)
end

Replace 10001 with the actual location ID. The helper returns false if the entry is missing or false, and true after invoking RandomMovement for a matching entry. true reports that the call was made, not successful path generation or native validation. This helper has no global-map fallback and does not guard against a nil map; pass a table, using {} for no entries. Keep each argument array dense so table.unpack/unpack can forward the positions correctly.

The helper calls RandomMovement only. It does not select RandomMovementFlight; call the latter directly for random vertical offsets.

Evidence: MovementModule.lua:62–74; server/SpawnScripts/Stormhold/alifeleech.lua:274–286.

Native random movement

The local LUA-RANDOM-MOVEMENT.md guide describes the engine functions underneath the location helper:

RandomMovement(NPC, NegDist, PosDist, Speed, MinDly, MaxDly,
               PointCount, RefreshSeconds, Flying)

RandomMovementFlight(NPC, NegDist, PosDist, NegHeight, PosHeight,
                     Speed, MinDly, MaxDly, PointCount, RefreshSeconds)

In the first call, PointCount, RefreshSeconds, and Flying are optional trailing arguments. In the flight call, PointCount and RefreshSeconds are optional.

Argument Meaning and current default
NPC The only spawn argument.
NegDist, PosDist Required numeric minimum/maximum X/Z offsets around the NPC's current movement center, for example -10, 10.
NegHeight, PosHeight Required numeric Y offsets for RandomMovementFlight only.
Speed Required numeric movement speed.
MinDly, MaxDly Required numeric waypoint delays, in seconds.
PointCount Default 20; below 1 uses the default, above 100 clamps to 100.
RefreshSeconds Default 600; 0 disables timed refresh, negatives become 0, above 86400 clamps to 86400.
Flying RandomMovement only; default 0, nonzero enables flying-safe movement around the current Y rather than random Y offsets. Use 1 to enable it.
-- Ground loop: 20 points, default 600-second refresh.
RandomMovement(NPC, -5, 5, 2, 8, 15)

-- Flying-safe loop around the current Y; disable timed refresh.
RandomMovement(NPC, -12, 12, 3, 4, 10, 25, 0, 1)

-- Random vertical offsets as well as horizontal offsets.
RandomMovementFlight(NPC, -10, 10, -2, 6, 3, 5, 12, 30, 600)

Choose one suitable movement setup for the NPC. Random path generation is queued asynchronously. Ground movement uses map best-Z data when available; swimming preserves water Y and flying paths clamp above available ground data. Collision checks can reject candidate points, so a request for points is not a guarantee that every candidate becomes part of the final loop. Placement changes rebuild enabled random movement around the new center.

Missing or nonnumeric required arguments are logged and rejected by the native functions. The optional numeric arguments use their defaults if omitted or nonnumeric. Native calls return no success flag. Use authored waypoint loops for a specific route, rather than relying on randomly generated points to visit exact coordinates.

Evidence: LUA-RANDOM-MOVEMENT.md; WorldServer/Lua/LuaFunctions.cpp:1246–1331; WorldServer/Spawn/Spawn.h:242–245; WorldServer/Spawn/Spawn.cpp:4746–4773, :4795–4840 and its random-path builder.

Combined NPC example

This authored example combines all three modules. As in the reviewed Stormhold/alifeleech.lua, it tries a location-specific random configuration, then falls back to authored waypoints when no random entry exists.

require "SpawnScripts/Generic/NPCModule"
require "SpawnScripts/Generic/MovementModule"
local Dialog = require "SpawnScripts/Generic/DialogModule"

local randomByLocation = {
    [10001] = {-10, 10, 2, 5, 12, 20},
}
local routesByLocation = {
    [10002] = {
        {100, 0, 200, 2, 0},
        {110, 0, 200, 2, 3},
    },
}

function spawn(NPC)
    NPCModule(NPC)
    if not ApplyRandomMovementByLocation(NPC, randomByLocation) then
        ApplyWaypointRoute(NPC, routesByLocation)
    end
end

function respawn(NPC)
    spawn(NPC)
end

function hailed(NPC, Player)
    FaceTarget(NPC, Player)
    Dialog.New(NPC, Player)
    Dialog.AddDialog("I keep watch along this route.")
    Dialog.AddOption("Goodbye.")
    Dialog.Start()
end

Location IDs and coordinates are illustrative and must be replaced. The fallback is triggered by a missing random-map entry, not by a later native validation error or asynchronous path failure. The respawn callback reuses initialization for the newly spawned NPC. Apply a route once per intended initialization to avoid appending duplicate waypoint points.

Definitions come from the reviewed Lua scripts and engine handlers. Server scripts and bindings are unchanged by this documentation update.