Skip to content

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.

Dances34 live rows. Nothing is commented out
Dictionaries2 — redmorrow2@dance and redmorrow2@dance@f
ModeEvery row is loop. No once, no hold
Root motionNone. No dance moves the character off its spot, the moonwalk included
FlagsNo row sets flags or walk. You choose the flag word
blendInSet 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.

Asset pack — no scripts No framework 34 dances

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 redmorrow2 line and that is the whole of it. A missing [redmorrow2] banner is not a fault — there is no code to print one.
  • clips.lua is never loaded. Config.Clips does 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.

lua
-- 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
end

loadDict 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:

  1. 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.

  2. Read clips.lua off disk on the server. The file is listed in escrow_ignore, so it stays readable as plain text next to the protected stream/ folder. LoadResourceFile is 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 own env keeps the file's Config assignment 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.

lua
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)
#ArgumentAboveWhat it does
1pedPlayerPedId()Who plays it
2dictredmorrow2@danceThe dictionary, male or female
3clipmoonwalkThe clip name inside it
4blendIn4.0How fast it blends on. Higher is snappier, 1000.0 is instant. 2.0 for the six floor moves
5blendOut-4.0How fast it blends off. Negative is the normal form
6duration-1Milliseconds, or -1 for no time limit
7flags1The flag word. See Flags
8startPhase0.0Where in the clip to begin. 0.0 is the first frame
9phaseControlledfalseLeave it
10ikFlags0Leave it
11p10falseLeave it
12taskFilter0Leave it
13p12falseLeave 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.

FlagValueEffect
LOOPING1Loop the clip
HOLD_LAST_FRAME2Stay on the last frame
NOT_INTERRUPTABLE4Cannot be interrupted
UPPERBODY8Upper body only
SECONDARY16Play in the secondary task slot
ABORT_ON_PED_MOVEMENT32Stop when the ped moves

Common combinations:

UseFlags
Loop the dance — what every row in this pack is built for1
Play once0
Play once and hold the last frame2
Loop upper body only, so the player can walk while dancing1 + 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.

lua
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
end

That 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:

BodyDictionary
Maleredmorrow2@dance
Femaleredmorrow2@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:

lua
-- 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
end

Two 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.

IDLength
crossleg_freeze2.033 s
flair_11 s
breakdance_footwork_13.167 s
breakdance_footwork_34.567 s
breakdance_footwork_to_freeze3.667 s
breakdance_footwork_to_idle9.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:

lua
-- 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
end

If you hand-typed a table of dictionary and clip pairs instead, keep the six ids in a set and look the blend-in up:

lua
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
end

Keying 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:

lua
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.

lua
-- 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:

lua
local ped = PlayerPedId()
ClearPedSecondaryTask(ped)       -- only needed if you used the SECONDARY flag
ClearPedTasks(ped, true, false)  -- stops everything the ped is doing

Calling 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:

lua
local function isDancing()
    return current ~= nil and IsEntityPlayingAnim(PlayerPedId(), current.dict, current.clip, 3)
end

Release dictionaries you have finished with. There are only two, so this handler is the whole of it:

lua
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.

lua
-- 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 from category gets 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 rewrite category. See The clips.lua table.
  • Mind the short clips. flair_1 at 1 s, moonwalk at 1.033 s, wave_hip_hop_dance_1 at 1.167 s and breakdance_ready at 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 than bellydancing at 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.

Documentation for RedMorrow. Scripts are licensed per server — redistribution is not permitted.