The Dance clips.lua Table
clips.lua is the only Lua file in this pack you can read, and it is pure data.
escrow_ignore in the manifest lists five entries — clips.lua, README.md, CHANGELOG.md, LICENSE.md and docs/*.md. The last is a glob over three guides, so seven files in the download stay plain, readable text. clips.lua is the only Lua file among them. fxmanifest.lua is readable as a matter of course. Everything else is protected: server.lua and the eight .ycd files in stream/.
| Rows | 27 |
| Live entries | 27 |
| Commented out | 0 |
| Global it sets | Config.Clips |
| Loaded as | A client script |
| Size | About 9 KB |
The whole file is a header comment, two statements and a table:
Config = Config or {}
Config.Clips = {
-- 27 rows
}That is all it does. It declares no functions, registers no events, starts no threads and exports nothing. It is a list of names, and the names are its entire value.
The header comment is accurate
The comment block at the top of clips.lua documents the field names, says plainly that the resource "only streams the animations: it has no menu, command or exports", and gives the correct thirteen-argument TaskPlayAnim call. It matches the rows and it matches the build. Read it; it is not stale.
The three guides in docs/ are the same. The only defect is an unfilled [DISCORD LINK] placeholder in the customer guide — the real support link is discord.gg/w4Ew4k3Exn.
What reads it
Nothing.
The download is fxmanifest.lua, clips.lua, server.lua, stream/, three Markdown guides and three licence and changelog files. server.lua is one line:
print('^2[redmorrow_dance_24]^7 Dance animation pack started.')There is no client.lua, no config.lua and no html/ folder, which means there is no menu, no chat command, no key binding, no event and no export that consults the table. Every field below is metadata waiting for code you write, or for the emote menu you already run. Playing a dance covers the native call; Emote menus covers wiring the names into a menu.
One entry
Every row is a single-line table, flat, with the same nine keys in the same order:
{ id = 'samba_dancing', label = 'Samba Dancing', dict = 'redmorrow_dance_24@dance', clip = 'samba_dancing',
category = 'dance', duration = 18.2, mode = 'loop', rootMotion = false,
bodies = { male = 'redmorrow_dance_24@dance', female = 'redmorrow_dance_24@dance@f' } },Config.Clips is a plain array, so #Config.Clips is 27 and ipairs walks it in file order. Rows 1 to 3 are the three bonus dances and rows 4 to 27 are the 24 main ones, in the order the dance list numbers them. The order is not alphabetical and the two halves are not separated by anything you can test for except the dictionary name. Do not rely on position; look rows up by id.
Fields
| Field | Type | Means | Read by anything? |
|---|---|---|---|
id | string | The key you look a dance up by. Unique across the table | No |
label | string | A display name for a menu or list | No |
dict | string | The animation dictionary — the .ycd filename without the extension. Always the male version | This is the value you pass to RequestAnimDict |
clip | string | The clip name inside that dictionary | This is the value you pass to TaskPlayAnim |
category | string | A grouping key for a menu | No |
duration | number | Length in seconds | No |
mode | string | Suggested playback: 'loop' in every row here | No |
rootMotion | boolean | true would mean the clip moves the character. false in every row | No |
bodies | table | { male = dict, female = dict .. '@f' } | No |
Only dict and clip have any mechanical meaning, and that meaning comes from the .ycd files, not from this table. The other seven are labels for whatever interface you build.
Values that are the same in all 27 rows
| Field | Value | |
|---|---|---|
mode | 'loop' | There are no 'once' and no 'hold' entries |
rootMotion | false | No dance moves the character off its mark |
bodies | both keys present | male equals dict, female equals dict plus @f, in every row |
Fields the header documents and no row carries
| Field | Described as | |
|---|---|---|
walk | true = upper body only, so the player keeps walking | Not present on any row |
flags | Raw RDR3 TaskPlayAnim flags; overrides mode and walk | Not present on any row |
blendIn | Blend-in delta for full-body play, 2.0 being half a second | Not present on any row |
All three are legal to add if you write code that honours them. Nothing in the pack will. The mapping the header intends is the one in Playing a dance: mode 'loop' is flags 1, 'hold' is 2, 'once' is 0, and walk = true adds 8 + 16.
duration
A number the pack author wrote down, not something measured from the .ycd at runtime. The spread runs from 0.5 s to 27.867 s with a median of 8.833 s, and the 27 clips come to 299.3 s in total — the per-row figures are in the dance list.
Trust the game over the field
If a dance plays longer or shorter than duration claims, the field is wrong, not the clip. GET_ANIM_DURATION returns the real length. breakdance_1990_2 is the one to look at: it reads 0.5, the only entry under a second, and looped that is a fast repeating twitch rather than a dance.
id and the one name that does not match its clip
Twenty-six of the 27 rows have id equal to clip. One does not:
id | dict | clip |
|---|---|---|
jazz_dancing_2 | dnac@jazz_dancing | jazz_dancing |
jazz_dancing | redmorrow_dance_24@dance | jazz_dancing |
Those are two different dances that happen to share a clip name across two dictionaries. The bonus one was given the _2 suffix to keep the table addressable, which is why it is also the only row whose id and clip differ.
Key your lookups on id, or on dictionary and clip together
A menu or a database keyed on the clip name alone collides on jazz_dancing and will play whichever of the two it indexed last. id is unique across all 27 rows; the pair dict plus clip is unique too. Either is safe. The clip name on its own is not.
What you may change
Safe to change
| Field | Why it is safe |
|---|---|
label | A display string. Nothing matches on it |
category | A grouping string. Nothing matches on it |
mode | Advisory only — your own code decides what it means |
walk | Not present on any row; add it and honour it yourself |
duration | Advisory only. Nothing reads it |
id | Safe, as long as it stays unique and you update your own references |
The labels in this pack are already readable — Samba Dancing (4), Northern Soul Floor Spin — so the highest-value edit is the category field, covered below. You can also add fields of your own: a price, a job restriction, a rank gate. The table is data; nothing validates its shape.
Never change
| Field | Why |
|---|---|
dict | Must match the name baked inside the .ycd file |
clip | Must match the clip name inside that dictionary |
An RDR2 dictionary stores its own name. dict is therefore not a filename you are free to rename — it is a lookup key that has to agree with the asset, and renaming the file in stream/ breaks it just as surely as editing the string here.
Both mistakes fail silently
A wrong dict means the dictionary never loads. A wrong clip means it loads and nothing plays. Neither raises an error, in the server console or in F8. The character simply stands there. Put a timeout on your HasAnimDictLoaded wait and print your own line — see Playing a dance — because that line is the only warning you will get.
stream/ is protected by escrow in any case, so the .ycd files cannot be edited or renamed on disk. The risk is entirely in this file.
The bodies table
Every dance ships in two versions, one fitted to the male body and one to the female body, and bodies is where the pair is written down:
bodies = { male = 'redmorrow_dance_24@dance', female = 'redmorrow_dance_24@dance@f' }The invariant holds across all 27 rows without exception:
bodies.male | always equals the row's own dict |
bodies.female | always equals dict with @f appended |
| Clip name | identical in both dictionaries |
That is true of the 24 main dances, which share redmorrow_dance_24@dance, and of the three bonus dances, which each carry their own pair — dnac@chicken_dance and dnac@chicken_dance@f, and so on. The full mapping is in the dance list.
So dict .. '@f' is a correct rule here, for every row. Reading bodies.female is still the better habit: it costs nothing, it is what a future pack with a different naming scheme would carry, and it means the choice of body lives in one place in your code.
dict alone is the male version
A row's dict is the male dictionary, not a body-neutral one. If you ignore bodies entirely and pass dict to RequestAnimDict, every character on your server dances on the male skeleton. Both versions play on either body — the mismatched one just reads badly. Picking between them needs GET_META_PED_TYPE, which returns a number, and the comparison has a trap in it: see Playing a dance.
Reading the table from another resource
Config is a Lua global, and CFX gives every resource its own isolated Lua state. A global set inside rm_dance_24 exists only inside rm_dance_24. Your own resource can start this pack, depend on it and stream its animations, and still see Config.Clips as nil:
-- in your own resource: this is always nil
print(Config and Config.Clips)There is no export to ask through, because the pack registers none. And clips.lua is listed under client_scripts only, so even inside this resource the table exists on the client and not on the server — server.lua never sees it.
Two ways round it.
1. Copy the rows into your own resource
The reliable option, and the one to pick if you are feeding an emote system, a job script or a saloon scene. Read the values out of clips.lua or out of the dance list and write them into your own file:
local Dances = {
{ id = 'samba_dancing', dict = 'redmorrow_dance_24@dance', clip = 'samba_dancing' },
{ id = 'house_dancing', dict = 'redmorrow_dance_24@dance', clip = 'house_dancing' },
{ id = 'chicken_dance', dict = 'dnac@chicken_dance', clip = 'chicken_dance' },
{ id = 'jazz_dancing_2', dict = 'dnac@jazz_dancing', clip = 'jazz_dancing' },
}The pack still has to be started for the dictionaries to exist in the stream. Copying the table copies the names, not the animations.
2. Load clips.lua into a sandbox
Because clips.lua is listed in escrow_ignore, it is still a readable file on disk after escrow, which means LoadResourceFile can fetch its text and load can run it in an environment you control. The pack's own developer guide shows this pattern:
-- 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)
endThe env table is the chunk's entire world. Seeding it with Config = {} is what the Config = Config or {} line at the top of the file expects, and reading env.Config.Clips afterwards keeps the 27 rows out of your own globals. LoadResourceFile works on the client and on the server, so you can build the list on either side.
This works only because the file is data. A chunk loaded into an empty environment has no natives, no print and no require, so a file that called anything would error — and pcall would hand you an empty table. clips.lua assigns a literal and stops, which is exactly the shape the sandbox needs.
The first argument is the folder name
LoadResourceFile('rm_dance_24', ...) takes the resource name, and in CFX the resource name is the directory name on disk. The manifest's name field is redmorrow_dance_24 and that is metadata — it is why the startup line reads [redmorrow_dance_24] while the folder is rm_dance_24.
The same applies to server.cfg: the line is ensure rm_dance_24, matching the folder. If your server renamed the folder, change both. Installation has the detail.
Build an index if you are going to look rows up more than once:
local byId = {}
for _, c in ipairs(getDanceClips()) do
byId[c.id] = c
end
local entry = byId['northern_soul_spin_combo']
-- entry.bodies.female == 'redmorrow_dance_24@dance@f'Index on id, not on clip — see the one name that does not match its clip.
Which option to take
Copying the rows survives updates untouched and is the right default. The sandbox loader is worth it when you want the pack to stay the single source of truth, or when you are generating a menu rather than hand-writing one. Re-check it after an update, because a future version could change the file's shape; copied rows cannot break underneath you.
The category field is lopsided
The shipped grouping is uneven, and the capitalisation is inconsistent with it:
| Category | Rows |
|---|---|
dance | 24 |
Chicken Dance | 1 |
Jazz Dancing | 1 |
Dancing Twerk | 1 |
A menu built straight from category gets one group of 24 and three groups of exactly one, each named after the dance inside it. That is not useful navigation. The three bonus dances carry their own name as their category because they came from their own dictionaries, not because they belong apart.
Nothing reads the field, so rewriting it is free. Group by the thing your players will look for:
{ id = 'samba_dancing_1', category = 'Samba' },
{ id = 'northern_soul_spin', category = 'Northern Soul' },
{ id = 'chicken_dance', category = 'Silly' },
{ id = 'dancing_twerk', category = 'Silly' },The ids divide cleanly already: five Samba entries, four Northern Soul, four numbered Dancing, three Hip Hop Dancing, two Jazz Dancing, and nine one-offs. The shipped grouping, with its counts, is in the dance list.
Pick one capitalisation while you are in there. Mixing dance with Chicken Dance shows up in a sorted menu as two different conventions.
After an edit
Save
clips.luaand restart the resource. It is a client script read at load, so arefreshon its own is not enough:cfgrestart rm_dance_24Watch the console. The pack prints one line and that line is the whole of its server-side behaviour:
text[redmorrow_dance_24] Dance animation pack started.Restart whatever consumes the list. If you copied rows into an emote menu or your own script, that resource needs restarting too, and if you edited its config, after its own rules.
Play one dance in game. A silent failure looks identical to no failure, so look at the character rather than at the console.
A syntax error takes the whole resource down
clips.lua is the only client script, and a missing comma or an unclosed brace stops the resource from starting at all. The /assetpacks entitlement check runs before any Lua, so a licence-key problem and a Lua problem can look alike in the console. Troubleshooting separates the two.
Your emote menu keeps its own list
Editing clips.lua changes this pack's reference list and nothing else. It does not change your emote menu, your database or the copy you pasted into your own resource — those hold their own names, and a label you rename here will not appear in front of players until you rename it there as well. Emote menus and, for the database-backed route, RedM Emotes.
Keep a copy of your edited file outside the folder. Re-downloading the resource from the Cfx.re portal replaces clips.lua with the shipped version.
See also
| Page | |
|---|---|
| Dance list | All 27 dances, with dictionary, clip and length |
| Playing a dance | The thirteen-argument native call, start to stop |
| Emote menus | Putting the names in the menu you already run |
| Installation | The ensure line, the folder name, the licence-key check |
| Troubleshooting | Symptom, cause, fix |
| FAQ | Short answers, including what the pack does not do |
| Animation Basics | How RedM animations work in general |
| RedM | Where RedM differs from FiveM, flags included |
| 100+ Female Poses | The sibling pack — also assets-only, female only |
| 30+ Female Poses | The sibling pack that does ship a menu and exports |
Still stuck? discord.gg/w4Ew4k3Exn.