Playing a Dance
This pack streams animations and nothing else. There is no menu, no chat command, no key binding, no events and no exports. Started, it loads 8 .ycd files into the game and prints one line in the server console:
[redmorrow_dance_24] Dance animation pack started.After that, nothing dances until something asks for a clip — your own client code, or the emote menu you already run. This page is the first of those. For the second, see Emote menus.
What you are driving
A dance is two strings, a dictionary and a clip, and clips.lua is where both come from.
| Dances | 27 live rows — 24 main, 3 bonus |
| Mode | Every row is loop. No once, no hold |
| Root motion | None. No dance moves the character off its spot |
| Bodies | A male and a female version of all 27 |
| Flags | No row sets flags or walk. You choose the flag word |
The count is not short
"24+" means 24 main dances plus 3 bonus ones. All 27 rows in clips.lua are live — nothing is commented out — and every dictionary a row names is present in stream/. The full table is in the dance list.
The minimal working script
Three steps, in this order, every time: ask for the dictionary, wait until the game confirms it loaded, then start the task.
-- Call from a thread: it waits while the dictionary streams in.
local function loadDict(dict)
if HasAnimDictLoaded(dict) then return true end
if not DoesAnimDictExist(dict) then
print(('[dance] no such dictionary: %s'):format(dict))
return false
end
RequestAnimDict(dict)
local deadline = GetGameTimer() + 5000
while not HasAnimDictLoaded(dict) do
if GetGameTimer() > deadline then
print(('[dance] dictionary never loaded: %s'):format(dict))
return false
end
Wait(0)
end
return true
end
local current -- { dict, clip } of what is playing
local function playDance(dict, clip, flags)
if not loadDict(dict) then return false end
TaskPlayAnim(PlayerPedId(), dict, clip, 4.0, -4.0, -1, flags or 1, 0.0, false, 0, false, 0, false)
current = { dict = dict, clip = clip }
return true
end
local function stopDance()
if not current then return end
StopAnimTask(PlayerPedId(), current.dict, current.clip, 2.0)
current = nil
endloadDict calls Wait, so it has to run inside a thread — wrap the call in CreateThread if you are coming from a command handler or an NUI callback.
DoesAnimDictExist is the guard worth keeping. It returns false for a typo and for a dictionary whose resource is not started, and it costs nothing.
Where the code goes
Config.Clips is a global inside the rm_dance_24 resource. CFX gives every resource its own Lua state, so your resource cannot read that table directly. There is no export and no shared file to require. Two options:
Copy the rows you need into your own resource. This is the normal choice. Your code stays yours and a pack update cannot overwrite it.
Read
clips.luaoff disk into a sandbox.clips.luais listed inescrow_ignore, so it stays readable as plain text. Load it with its own environment and take the table out:lua-- Returns rm_dance_24's Config.Clips, or an empty table. local function getDanceClips() local src = LoadResourceFile('rm_dance_24', 'clips.lua') if not src then return {} end local env = { Config = {} } local chunk = load(src, '@rm_dance_24/clips.lua', 't', env) if not chunk or not pcall(chunk) then return {} end return env.Config.Clips or {} end for _, c in ipairs(getDanceClips()) do print(c.id, c.label, c.category, c.duration) endYour resource needs
lua54 'yes'in its manifest for this. Use it to build a menu that follows the pack through updates.
LoadResourceFile takes the folder name
The argument is rm_dance_24, the name of the folder in resources/. The server console prints [redmorrow_dance_24] because that is the name field in the manifest, which is metadata — it is not the resource name. If your server renamed the folder, change the argument to match. Same rule applies to the ensure line; see Installation.
The thirteen arguments
RDR3's TaskPlayAnim takes thirteen parameters.
TaskPlayAnim(ped, dict, clip, blendIn, blendOut, duration, flags, startPhase,
phaseControlled, ikFlags, p10, taskFilter, p12)| # | Argument | Above | What it does |
|---|---|---|---|
| 1 | ped | PlayerPedId() | Who plays it |
| 2 | dict | from clips.lua | The dictionary |
| 3 | clip | from clips.lua | The clip name inside it |
| 4 | blendIn | 4.0 | How fast it blends on. Higher is snappier, 1000.0 is instant |
| 5 | blendOut | -4.0 | How fast it blends off. Negative is the normal form |
| 6 | duration | -1 | Milliseconds, or -1 for no time limit |
| 7 | flags | 1 | The flag word. See Flags |
| 8 | startPhase | 0.0 | Where in the clip to begin. 0.0 is the first frame |
| 9 | phaseControlled | false | Leave it |
| 10 | ikFlags | 0 | Leave it |
| 11 | p10 | false | Leave it |
| 12 | taskFilter | 0 | Leave it |
| 13 | p12 | false | Leave it |
Three of those are worth understanding and the rest you can copy as written.
Blend in and blend out. 4.0 and -4.0 give a quick, clean transition into and out of the dance. Drop the blend-in toward 1.0 if you want the character to ease into the step; that reads well on the long clips like samba_dancing_1 at 27.867 s and badly on breakdance_1990_2 at 0.5 s, which is over before a slow blend finishes.
Duration -1. No time limit. Combined with the loop flag, the dance runs until something stops it. A positive number here is a millisecond count, so 10000 dances for ten seconds and then releases the character.
The duration field in clips.lua is not what goes in argument 6. It is metadata the pack author wrote in seconds, nothing in the resource reads it, and the game is the authority — GetAnimDuration gives the real figure. Breakdown in Lengths.
Flags
These are RDR3 eScriptedAnimFlags values.
| Flag | Value | Effect |
|---|---|---|
LOOPING | 1 | Loop the clip |
HOLD_LAST_FRAME | 2 | Stay on the last frame |
NOT_INTERRUPTABLE | 4 | Cannot be interrupted |
UPPERBODY | 8 | Upper body only |
SECONDARY | 16 | Play in the secondary task slot |
ABORT_ON_PED_MOVEMENT | 32 | Stop when the ped moves |
Common combinations:
| Use | Flags |
|---|---|
| Loop the dance — what every row in this pack is built for | 1 |
| Play once | 0 |
| Play once and hold the last frame | 2 |
| Loop upper body only, so the player can walk while dancing | 1 + 8 + 16 = 25 |
The mode field in clips.lua maps to a flag word: loop is 1, hold is 2, once is 0. With walk = true you add 8 + 16. All 27 rows in this pack are mode = 'loop' and no row sets walk, so 1 is the correct flag for every dance unless you want something else.
RDR3 flag values are not GTA V's
A flag word copied out of a FiveM script will not mean the same thing here. GTA V's 49 is a familiar loop-upper-body number in FiveM resources and it does not produce that behaviour in RedM. Use the table above, or read the values from the RedM platform notes.
Pick the male or female version
Every dance ships twice. The female dictionary is always the male name plus @f, with identical clip names inside — true for all 27 rows, including the three bonus dances. In every row bodies.male equals dict and bodies.female equals dict with @f appended, with no exceptions.
Both versions play on either body. The matching one fits the skeleton better, so decide from the ped:
-- true when the ped uses the female body.
-- GET_META_PED_TYPE: 0 = male, 1 = female. Compare the number — 0 is truthy in Lua.
-- Falls back to IS_PED_MALE, which RedM may return as 0/1 instead of a boolean.
local function isFemale(ped)
if GetMetaPedType then
local ok, t = pcall(GetMetaPedType, ped)
if ok and t == 1 then return true end
if ok and t == 0 then return false end
end
local male = IsPedMale(ped)
return not (male == true or (type(male) == 'number' and male ~= 0))
end
-- The dictionary to play for this ped: the matching version, else the other one.
local function dictFor(entry, ped)
if entry.bodies then
local want = isFemale(ped) and 'female' or 'male'
return entry.bodies[want] or entry.bodies.male or entry.bodies.female
end
return entry.dict
endTwo traps are stacked in that helper and both bite quietly.
GET_META_PED_TYPE returns a number, and 0 is truthy in Lua. if GetMetaPedType(ped) then is true for a male ped as well as a female one, because 0 is not false or nil. Compare against the number, as above.
IsPedMale may hand you 0 or 1 instead of false or true. if IsPedMale(ped) then is then true for a female ped, since 0 is truthy again. The fallback above accepts either shape, which is why it tests for both a boolean and a non-zero number.
Got it wrong and nothing errors. Every character dances; half of them dance the wrong version, which on screen looks like a slightly ill-fitting animation rather than a bug.
If you copied bare dictionary and clip pairs rather than whole rows, dict .. '@f' is a safe derivation for this pack — all eight .ycd files follow that rule. Deriving a clip name from an id is not safe; see below.
Why the wait is not optional
A clip asked for from a dictionary that never loaded fails silently. No console error, no warning, no T-pose. The character stands there, and your code has no way to tell, because TaskPlayAnim returns nothing either way.
This is cause number one of "the dance does nothing".
The deadline in loadDict exists so the failure reports itself. Without it, while not HasAnimDictLoaded(dict) do Wait(0) end spins forever on a typo and the dance never arrives, which looks exactly like the silent failure the wait was supposed to prevent.
When one dance refuses to come up, check the dictionary name first:
print(DoesAnimDictExist('redmorrow_dance_24@dance@f'))false means the name is wrong or rm_dance_24 is not started. true with a motionless character means the dictionary loaded and the clip name is wrong — the game found the dictionary and nothing inside it matched. More symptoms in Troubleshooting.
A dictionary only occupies memory once it is played, which is why the request step exists at all. Players download roughly 4 MB of animation files the first time they join and the files are cached after that.
Stopping
The pack's clips loop forever on flag 1, so a stop path is not optional.
-- Stop the one clip you started.
StopAnimTask(ped, dict, clip, 2.0)The fourth argument is the blend-out speed and it must be greater than 0. Pass 0.0 and the clip does not blend out; the call does nothing useful and the character keeps dancing.
StopAnimTask needs the dictionary and clip you started with, which is why playDance above remembers them in current. For a stop command that does not track state, clear the tasks instead:
local ped = PlayerPedId()
ClearPedSecondaryTask(ped) -- only needed if you used the SECONDARY flag
ClearPedTasks(ped, true, false) -- stops everything the ped is doingCalling both is harmless when only one applies, which makes it the safe default for a /dance stop. If you played with SECONDARY (flag 16, part of the walk combination 25), ClearPedTasks alone can leave the clip running in the secondary slot — clear that slot too.
To confirm what is actually playing:
local function isDancing()
return current ~= nil and IsEntityPlayingAnim(PlayerPedId(), current.dict, current.clip, 3)
endRelease dictionaries you have finished with:
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
RemoveAnimDict('redmorrow_dance_24@dance')
RemoveAnimDict('redmorrow_dance_24@dance@f')
end)Another script's animation will clear yours
Starting an animation clears the ped's tasks. If an emote resource is running alongside your command, its next emote wipes your dance and your dance wipes its emote. Stop the other one deliberately rather than letting the two fight — see Animations.
A worked /dance command
A client script for your own resource. It uses loadDict, playDance, stopDance, isFemale and dictFor from above.
-- client.lua in your own resource (lua54 'yes')
local Dances = {
dancing = { dict = 'redmorrow_dance_24@dance', clip = 'dancing' },
samba_dancing = { dict = 'redmorrow_dance_24@dance', clip = 'samba_dancing' },
house_dancing = { dict = 'redmorrow_dance_24@dance', clip = 'house_dancing' },
jazz_dancing = { dict = 'redmorrow_dance_24@dance', clip = 'jazz_dancing' },
chicken_dance = { dict = 'dnac@chicken_dance', clip = 'chicken_dance' },
jazz_dancing_2 = { dict = 'dnac@jazz_dancing', clip = 'jazz_dancing' },
dancing_twerk = { dict = 'dnac@dancing_twerk', clip = 'dancing_twerk' },
}
RegisterCommand('dance', function(_, args)
local id = args[1]
if not id or id == 'stop' then
stopDance()
return
end
local entry = Dances[id]
if not entry then
print(('[dance] no such dance: %s'):format(id))
return
end
CreateThread(function()
local dict = entry.dict
if isFemale(PlayerPedId()) then
dict = dict .. '@f'
end
playDance(dict, entry.clip, 1)
end)
end, false)/dance house_dancing plays it, /dance stop clears it, /dance nonsense says so in the F8 console instead of failing quietly.
Appending @f is safe here because every one of the eight dictionaries follows that rule. If you copied whole rows out of clips.lua, pass them to dictFor instead and let it read bodies — that keeps working if a future version adds a dance that breaks the pattern.
Seven dances are listed there and the pack has 27. Copy the rows you want out of clips.lua rather than retyping them from a table — the file ships readable for exactly this reason.
Key your table on id, not on the clip name
The Dances table above is keyed on id deliberately. jazz_dancing_2 and jazz_dancing are two different dances that both use the clip name jazz_dancing, in dnac@jazz_dancing and redmorrow_dance_24@dance respectively. A menu keyed on clip name alone silently keeps one and loses the other.
That is the only row in the pack whose id differs from its clip, and the only clip name used twice. Key on id, or on dictionary and clip together. Details in the 3 bonus dances.
The three bonus dances are the other thing a loop over the list has to handle: they do not live in the main dictionary. chicken_dance, jazz_dancing_2 and dancing_twerk each carry their own dnac@ pair, so read dict and bodies from the row instead of assuming redmorrow_dance_24@dance.
Where the two strings come from
Both strings for every dance are in The clips.lua table, and tabulated with their lengths in the dance list. The lopsided category field — one group of 24 and three groups of one — is worth a look before you build a menu from it; see Categories.
Once you have the pairs, dropping them into an emote system is usually less work than maintaining your own command. Emote menus covers the common ones, and RedM Emotes takes a dictionary and clip per row — see three ways to add an emote.
Still stuck after Troubleshooting? Ask in Discord.