OPEN_SEQUENCE_TASK / TASK_PERFORM_SEQUENCE: pass 0 as ped inside the sequence, capture the return as the sequence ID, then OPEN→CLOSE→PERFORM→CLEAR
Context
You want a ped to chain tasks — walk to coords, play anim A, then anim B, then clear. Calling TaskGoToCoord / TaskPlayAnim etc. sequentially makes each one cancel the previous, so the naive serial-call approach doesn't work. OPEN_SEQUENCE_TASK / TASK_PERFORM_SEQUENCE is the right primitive, but every part of how to call it is non-obvious — the native docs only show the C signature.
The full pattern
local sequence = OpenSequenceTask(math.random(1000)) -- ANY int. Return value IS the sequence ID.
TaskFollowNavMeshToCoord(0, x, y, z, 1.0, -1, 1.0, 4, heading) -- ped = 0, NOT PlayerPedId()
TaskPlayAnim(0, dict, "enter", 1.0, 8.0, -1, 0, 0.0, false, false, false)
TaskPlayAnim(0, dict, "base", 1.0, 8.0, -1, 0, 0.0, false, false, false)
CloseSequenceTask(sequence)
TaskPerformSequence(actualPed, sequence) -- NOW the real ped
ClearSequenceTask(sequence) -- always clear, sequence handle pool is small
while GetSequenceProgress(actualPed) ~= -1 do Wait(100) end -- -1 == finished
The non-obvious bits
1. Ped arg inside the sequence is 0, not the real ped
While the sequence is open, all task natives that take a ped (TaskFollowNavMeshToCoord, TaskPlayAnim, TaskPickupCarriableEntity, TaskGoToCoordAnyMeans, TaskTurnPedToFaceCoord …) get 0 as the ped. The 0 means "whichever ped this sequence is eventually performed on." Pass PlayerPedId() here and the task fires immediately on the player AND fails to add to the sequence — sequence ends up empty.
2. The int* parameter is a write-back, not an input
OPEN_SEQUENCE_TASK(int* taskSequenceId) — the C signature is a pointer the native writes the new ID into. In Lua you pass any int (math.random(1000) is the convention, 0 works too); the bridge captures the written-back value and returns it. Always capture the return value as the sequence ID — it has nothing to do with what you passed.
3. Order: open → tasks → close → perform → clear
- Add tasks only between
OpenSequenceTaskandCloseSequenceTask. TaskPerformSequenceonly works afterCloseSequenceTask.ClearSequenceTaskreleases the slot. Without it the sequence pool fills up and futureOpenSequenceTaskreturns garbage. Safe to clear right afterTaskPerformSequence— the task data is copied to the ped, the sequence keeps running.
4. Tracking completion: GetSequenceProgress(ped) == -1 means done
While running, GetSequenceProgress(ped) returns the current task index (0, 1, 2, …). On completion it returns -1. Also useful for hooking phase changes — kd_stable's saddle cutscene swaps cameras and props when progress advances to specific indices.
5. Two synchronized peds → two sequences
For coordinated multi-entity animations (e.g. man fitting horseshoes on a horse), open two sequences, fill each with the matching track, close both, then call TaskPerformSequence(ped1, seq1) and TaskPerformSequence(ped2, seq2) back-to-back — they start in the same frame and stay in lockstep. (Used in kd_stable sceneChangeHorseShoes.)
What does NOT work
TaskPerformSequencebeforeCloseSequenceTask→ silently does nothing.- Real ped instead of
0inside sequence → tasks fire immediately, sequence empty. - Skipping
ClearSequenceTask→ leak the sequence handle. - Mixing tasks for two peds in one sequence → only the first ped's tasks bind.
Bonus: not networked
Sequences and the tasks within them are local-only. To run a sequence on a remote-owned ped, NetworkRequestControlOfEntity first and wait for ownership before calling TaskPerformSequence.
Tags: task, sequence, ai, animation, ped, coordination
Category: natives
Source: research-agent-rdr2
Created: 2026-05-01T11:59:11.744Z