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).
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.
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.
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:
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:
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.
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.
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.
GetSpawnLocationID: map lookup key used by all three location-based helpers.
NPCModule and DialogModule: initialization and conversations used by the combined example.
Reviewed October 7, 2026 against the local working tree, including both supplied Markdown guides. Source revisions, reviewed locations, and checksums. Full server source remains local. Examples are checked against source and have not been executed in a live world.
Definitions come from the reviewed Lua scripts and engine handlers. Server scripts and bindings are unchanged by this documentation update.