Scripting 30+ Dances
This pack is animation assets and nothing else. There is no menu, no chat command, no key binding, no events, no exports and no config — the manifest declares no scripts of any kind. Started, it streams two .ycd files into the game and waits.
Nothing dances until something asks for a clip: your own client code, or the emote menu your server already runs. 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 | 34 live rows. Nothing is commented out |
| Dictionaries | 2 — redmorrow2@dance and redmorrow2@dance@f |
| Mode | Every row is loop. No once, no hold |
| Root motion | None. No dance moves the character off its spot, the moonwalk included |
| Flags | No row sets flags or walk. You choose the flag word |
| blendIn | Set on 6 rows, at 2.0. See Floor moves |
The count is not short
"30+" means 34. All 34 rows in clips.lua are live, both dictionaries are present in stream/, and every clip name resolves in both of them. The full table is in the dance list.
No scripts means no startup line
fxmanifest.lua has no client_scripts and no server_scripts entries at all. Two things follow, and both surprise people who have installed other animation packs:
- The pack prints nothing of its own. Your server console shows the ordinary
Started resource redmorrow2line and that is the whole of it. A missing[redmorrow2]banner is not a fault — there is no code to print one. clips.luais never loaded.Config.Clipsdoes not exist at runtime, on the client or on the server, and clients never download the file. It is a reference document that happens to be valid Lua.
stream/ is picked up automatically by CFX without a manifest entry, which is why a resource with no scripts works at all.
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, blendIn)
if not loadDict(dict) then return false end
TaskPlayAnim(PlayerPedId(), dict, clip, blendIn or 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.
That is all the code the pack needs. There is no resource of ours to talk to, no event to trigger and no export to call — the two dictionaries are in the game the moment redmorrow2 starts, and these natives are the entire interface.
Where the clip list lives
Because the manifest never loads clips.lua, there is no global to read, in your resource or in any other. Two ways to get the 34 rows into your code:
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. The ids and clip names are in the dance list and the rows themselves in The clips.lua table.
Read
clips.luaoff disk on the server. The file is listed inescrow_ignore, so it stays readable as plain text next to the protectedstream/folder.LoadResourceFileis server-side only, so read it on the server, then hand the list to clients over an event:lua-- server.lua in your own resource (lua54 'yes') -- Returns redmorrow2's Config.Clips, or an empty table. local function getDanceClips() local src = LoadResourceFile('redmorrow2', 'clips.lua') if not src then return {} end local env = { Config = {} } local chunk = load(src, '@redmorrow2/clips.lua', 't', env) if not chunk or not pcall(chunk) then return {} end return env.Config.Clips or {} end local danceClips = getDanceClips() RegisterNetEvent('mydances:requestClips', function() TriggerClientEvent('mydances:clips', source, danceClips) end)lua-- client.lua in the same resource local danceClips = {} RegisterNetEvent('mydances:clips', function(clips) danceClips = clips end) CreateThread(function() TriggerServerEvent('mydances:requestClips') end)Rename the
mydances:*events to fit your resource. Loading the chunk with its ownenvkeeps the file'sConfigassignment out of your globals. This is the option that follows the pack through updates.
One name, used everywhere
The folder is redmorrow2 and the manifest name is redmorrow2. The same string goes in ensure redmorrow2, in LoadResourceFile('redmorrow2', ...) and in the dictionary prefix. If your server renamed the folder, change the ensure line and the LoadResourceFile argument together — the dictionary names are baked into the .ycd files and do not change.
The thirteen arguments
RDR3's TaskPlayAnim takes thirteen parameters.
TaskPlayAnim(ped, dict, clip, blendIn, blendOut, duration, flags, startPhase,
phaseControlled, ikFlags, p10, taskFilter, p12)
TaskPlayAnim(PlayerPedId(), 'redmorrow2@dance', 'moonwalk', 4.0, -4.0, -1, 1, 0.0, false, 0, false, 0, false)| # | Argument | Above | What it does |
|---|---|---|---|
| 1 | ped | PlayerPedId() | Who plays it |
| 2 | dict | redmorrow2@dance | The dictionary, male or female |
| 3 | clip | moonwalk | The clip name inside it |
| 4 | blendIn | 4.0 | How fast it blends on. Higher is snappier, 1000.0 is instant. 2.0 for the six floor moves |
| 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 |
Four 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. Lower the blend-in toward 2.0 when the clip starts on the ground, which is exactly what clips.lua does for six rows — that is Floor moves. A slow blend-in also reads badly on the four clips under a second and a half, which are over before the 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 reads the field at runtime, 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 34 rows in this pack are mode = 'loop' and no row sets walk or flags, so 1 is the correct flag for every dance unless you want something else.
local MODE_FLAGS = { loop = 1, hold = 2, once = 0 }
-- entry: one row out of Config.Clips
local function flagsFor(entry)
if entry.flags then return entry.flags end
local flags = MODE_FLAGS[entry.mode] or 1
if entry.walk then flags = flags + 8 + 16 end
return flags
endThat helper returns 1 for all 34 rows today. It is worth keeping anyway, because it is the piece that keeps working if you add rows of your own with a different mode.
RDR3 flag values are not GTA V's
A flag word copied out of a FiveM script will not mean the same thing here. The familiar loop-upper-body numbers from GTA V resources do not produce that behaviour in RedM. Use the table above, or read the values from the RedM platform notes.
Upper-body play looks wrong on these
The 1 + 8 + 16 = 25 combination is genuinely useful on hand gestures and smoking animations. These are not that. All 34 are full-body moves and 20 of them are breakdance, so playing one upper-body-only leaves the legs standing still while the torso spins — the character looks broken rather than dancing. Use 1 and accept that the player stops walking.
Pick the male or female version
Every dance ships twice, and the rule has no exceptions anywhere in the pack:
| Body | Dictionary |
|---|---|
| Male | redmorrow2@dance |
| Female | redmorrow2@dance@f |
All 34 clips are in both, under identical clip names. In every row bodies.male equals dict and bodies.female equals bodies.male with @f appended. There are no per-dance dictionaries to look up — see Two dictionaries, that is all.
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 neither false nor 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.
Get 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.
Because the two dictionary names differ only by the @f suffix, dict .. '@f' is a safe derivation in this pack if you copied bare dictionary and clip pairs rather than whole rows. Pass whole rows to dictFor when you have them and let it read bodies, so your code survives a future version that breaks the pattern.
Floor moves
Six dances begin on the ground rather than standing, and clips.lua gives each of them blendIn = 2.0 — roughly half a second — instead of the 4.0 everything else uses. A slower blend-in makes the character ease down instead of snapping to the floor.
| ID | Length |
|---|---|
crossleg_freeze | 2.033 s |
flair_1 | 1 s |
breakdance_footwork_1 | 3.167 s |
breakdance_footwork_3 | 4.567 s |
breakdance_footwork_to_freeze | 3.667 s |
breakdance_footwork_to_idle | 9.6 s |
All six are in the breakdance group. blendIn is the only optional field any row in this pack sets — nothing sets walk and nothing sets flags.
If you are looping over rows, read the field and fall back:
-- Argument 4 of TaskPlayAnim for this row.
local function blendFor(entry)
return entry.blendIn or 4.0
end
-- entry: one row out of Config.Clips
local function playEntry(entry)
local ped = PlayerPedId()
local dict = dictFor(entry, ped)
if not loadDict(dict) then return false end
TaskPlayAnim(ped, dict, entry.clip, blendFor(entry), -4.0, -1, flagsFor(entry), 0.0, false, 0, false, 0, false)
current = { dict = dict, clip = entry.clip }
return true
endIf you hand-typed a table of dictionary and clip pairs instead, keep the six ids in a set and look the blend-in up:
local FLOOR_MOVES = {
crossleg_freeze = true,
flair_1 = true,
breakdance_footwork_1 = true,
breakdance_footwork_3 = true,
breakdance_footwork_to_freeze = true,
breakdance_footwork_to_idle = true,
}
local function blendForId(id)
return FLOOR_MOVES[id] and 2.0 or 4.0
endKeying that set on the id is safe here, and so is keying a dance table on the clip name: in all 34 rows id equals clip, no id is repeated, no clip name is repeated and no label is repeated. There is nothing to derive and nothing to disambiguate. The same is not true of the 27-dance pack, which has a clip name used by two different dances.
Getting the blend-in wrong is cosmetic, not fatal. The six still play at 4.0 — they just arrive hard.
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 a dance refuses to come up, check the dictionary name first, in the F8 console:
print(DoesAnimDictExist('redmorrow2@dance@f'))false means the name is wrong or redmorrow2 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 3 MB of animation files the first time they join and the files are cached after that. With no scripts on either side, the pack itself costs 0.00 ms in resmon forever.
Stopping
Every clip in this pack loops 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 /dance2 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. There are only two, so this handler is the whole of it:
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
RemoveAnimDict('redmorrow2@dance')
RemoveAnimDict('redmorrow2@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 /dance2 command
A client script for your own resource — not for redmorrow2, which is escrow-protected and has no script files to add to. It uses loadDict, playDance, stopDance, isFemale and blendForId from above.
-- client.lua in your own resource (lua54 'yes')
-- /dance2 <id> e.g. /dance2 moonwalk
-- /dance2 stop
local Dances = {
moonwalk = true,
bellydancing = true,
can_can = true,
hokey_pokey = true,
twist_dance = true,
breakdance_uprock = true,
breakdance_swipes = true,
crossleg_freeze = true,
tut_hip_hop_dance = true,
slide_hip_hop_dance = true,
}
RegisterCommand('dance2', function(_, args)
local id = args[1]
if not id or id == 'stop' then
stopDance()
return
end
if not Dances[id] then
print(('[dance2] no such dance: %s'):format(id))
return
end
CreateThread(function()
local dict = isFemale(PlayerPedId()) and 'redmorrow2@dance@f' or 'redmorrow2@dance'
playDance(dict, id, 1, blendForId(id))
end)
end, false)/dance2 hokey_pokey plays it, /dance2 stop clears it, /dance2 nonsense says so in the F8 console instead of failing quietly. The id goes straight into argument 3 as the clip name, which is only correct because id equals clip in all 34 rows of this pack.
Ten dances are listed there and the pack has 34. Rather than retyping the rest from a table, copy the rows out of clips.lua and drive them through playEntry — the file ships readable for exactly this reason.
Two things the command above deliberately does not do, because a real menu has to:
- Group them. Every row carries
category = 'dance', a single flat category for all 34, so a menu built fromcategorygets one undifferentiated list. The 20 breakdance / 6 hip hop / 8 party split exists only as prose, and it is exact — 20 + 6 + 8 = 34, every clip in exactly one group. Apply it in your own config or rewritecategory. See The clips.lua table. - Mind the short clips.
flair_1at 1 s,moonwalkat 1.033 s,wave_hip_hop_dance_1at 1.167 s andbreakdance_readyat 1.433 s are moves rather than routines, and on a loop they read as a repeating beat. Nothing is wrong with them; they just want a different label in a menu thanbellydancingat 25.367 s.
Where the two strings come from
Both strings for every dance are tabulated with their lengths in the dance list, and the rows themselves are broken down field by field in The clips.lua table.
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.
Running the 27-dance pack alongside this one is fine: its dictionary names are entirely different, so neither shadows the other and both sets of clips are available at once. The same goes for the pose packs — 100+ Female Poses is assets-only like this one, and 30+ Female Poses ships a menu of its own.
Installation, the licence-key requirement and the /assetpacks dependency are on Installation. What you may and may not do with the animations is set out in the customer guide inside your download. Still stuck after Troubleshooting and the FAQ? Ask in Discord.