Skip to content

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:

text
[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.

Dances27 live rows — 24 main, 3 bonus
ModeEvery row is loop. No once, no hold
Root motionNone. No dance moves the character off its spot
BodiesA male and a female version of all 27
FlagsNo 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.

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

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:

  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.

  2. Read clips.lua off disk into a sandbox. clips.lua is listed in escrow_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)
    end

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

lua
TaskPlayAnim(ped, dict, clip, blendIn, blendOut, duration, flags, startPhase,
             phaseControlled, ikFlags, p10, taskFilter, p12)
#ArgumentAboveWhat it does
1pedPlayerPedId()Who plays it
2dictfrom clips.luaThe dictionary
3clipfrom clips.luaThe clip name inside it
4blendIn4.0How fast it blends on. Higher is snappier, 1000.0 is instant
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

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.

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

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

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

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

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

Release dictionaries you have finished with:

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

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

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