RedM Emotes Configuration
Every key in config.lua, what it accepts, and the exact lines to change for the setups people actually run.
| File | config.lua in the resource root |
| Loaded as | a shared_script — the same table is read client and server side |
| Applying changes | restart rm_emotes. There is no config reload command |
| Escrow | Listed in escrow_ignore, so it stays fully editable in both editions |
| Not in this file | Chair settings live in shared/seats.lua, prop and shared emotes in shared/extras.lua |
Two rules for the whole file
Do not delete a top-level table
RM.Command, RM.SlashCommand, RM.Keys and RM.Showcase are read by literal path with no guard — RM.Keys.open.active, RM.Showcase.enabled. Removing the table raises a Lua error at resource start. To switch a feature off, set its flag to false and leave the table in place.
Several booleans are tested against literal false
RM.Adjust.enabled, RM.Adjust.ghost, RM.Adjust.raycast, RM.Adjust.gizmo, RM.Adjust.freezeOnConfirm, RM.Showcase.inMenu and RM.Showcase.levelCamera use ~= false or == false.
Deleting one of these keys enables the feature. Always write false explicitly; never comment the line out.
General and appearance
| Key | Default | What it does |
|---|---|---|
RM.Lang | 'English' | Selects the locale table for menu text and notifications. Must match a name in locales/*.lua. An unknown name silently falls back to English at every read site |
RM.Ui.layout | 'grand' | Default menu layout. 'grand' is the full dashboard, 'compact' the smaller rail. Any other value is coerced to 'grand' |
RM.Ui.layout is a default, not a lock
The menu has a Grand/Compact switch and a player's choice is stored in their own browser storage. Once a player uses that switch, your value no longer applies to them.
Server-side log and audit strings are hard-coded English and ignore RM.Lang.
Commands
| Key | Default | What it does |
|---|---|---|
RM.Command.enabled | true | Registers the menu command |
RM.Command.name | 'emotes' | /emotes. No chat autocomplete is registered for it |
RM.SlashCommand.enabled | true | Registers the quick-play command and its suggestion. Also controls whether cards show the /e <name> hint |
RM.SlashCommand.name | 'e' | No argument opens the menu; stop/c stops; random plays a random one; anything else is a slug lookup |
stop, c and random are reserved by the slug allocator and can never be used as an emote command. They are hard-coded and cannot be translated or extended.
Admin access and audit
| Key | Default | What it does |
|---|---|---|
RM.AdminAce | 'rm_emotes.admin' | ACE object checked with IsPlayerAceAllowed |
RM.Admins | { 'steam:11000014864548d' } | Alternative grant. Each entry is compared verbatim against every identifier the player has |
RM.Webhook | '' | Discord webhook for the admin audit log. '' disables it |
Change RM.Admins before you go live
The shipped file contains a real sample Steam identifier. Any server that leaves it in place grants full create/edit/delete access, plus visibility of every hidden and restricted emote, to whoever owns that account.
RM.Admins = {}RM.Admins is read once and cached for the lifetime of the resource — editing it requires a resource restart, not a reconnect.
There is one admin level. No tiers, no per-action ACE: the same flag unlocks viewing, creating, editing and deleting.
Framework and notifications
| Key | Default | What it does |
|---|---|---|
RM.Framework | 'auto' | 'auto' probes vorp_core, then rsg-core, then redem_roleplay, falling back to standalone |
RM.NotifyMode | 'auto' | Which notification pipe player notices use |
Accepted RM.Framework values: 'auto', 'vorp', 'rsg', 'redemrp', 'standalone'. The value is not validated — an unrecognised string behaves exactly like standalone.
Detection runs once at resource start and matches a resource state containing start, so both starting and started count. A core that starts after this resource is never re-detected.
RM.NotifyMode values:
| Value | Emits | Notes |
|---|---|---|
'auto' | follows the detected framework | |
'vorp' | vorp:TipRight | title and error styling ignored |
'rsg' | RSGCore:Notify | passes error or primary |
'redemrp' | redem_roleplay:Tip | title and error styling ignored |
'ctrl' | ctrl:cAvancedNotify | receives kind, title and message. Needs a resource that handles it |
'text' | chat:addMessage | plain chat line, works everywhere |
'none' | nothing | suppressed entirely |
| anything else | chat:addMessage | unknown strings fall through to chat |
Notification duration is hard-coded at 3000 ms on the vorp, rsg, redemrp and ctrl branches. The chat fallback (text, standalone, unknown values) passes no duration — how long the line stays is up to your chat resource.
Keys and hotkeys
| Key | Default | What it does |
|---|---|---|
RM.Keys.open.active | true | Runs the poll thread for the open key. false removes the key entirely |
RM.Keys.open.control | 0x4CC0E2FE (B) | Control hash checked each frame |
RM.Keys.cancel.active | true | Poll thread for the stop key |
RM.Keys.cancel.control | 0x7DA48D2A (X) | Ignored while seated, because X is the stand-up prompt |
RM.Keys.cancel.forceStop | true | Also calls ClearPedTasksImmediately |
RM.BindListener | 'raw' | Where per-emote saved binds come from |
RM.ShiftHotkeys | true | SHIFT+1 to SHIFT+5 for the first five starred emotes |
RM.Keys.*.control must be a numeric hash
The value goes straight to IsControlJustPressed and input names are never resolved here. 'INPUT_OPEN_SATCHEL_MENU' will not work — unlike RM.Adjust.keys, which does accept names.
RM.BindListener values:
| Value | Behaviour |
|---|---|
'raw' | Built-in raw keyboard listener. Single letters and digits only. No dependencies |
'ctrl' | Binds come from incoming ctrl_basic:keyPress events instead |
'both' | Behaves identically to 'raw'. The raw listener runs whenever the value is not exactly 'ctrl', and the keyPress handler only dispatches binds when it is exactly 'ctrl' — so there is no double-fire and no extra coverage |
Any unrecognised string behaves as 'raw'.
Fixed in code and not configurable: only single letters and digits can be bound; F1–F12, Alt, Backspace, Escape and X are permanently rejected; the SHIFT hotkey count is fixed at five and the modifier cannot be changed; and an incoming ctrl_basic:keyPress carrying F5 always opens the menu, regardless of RM.Keys.open.active or RM.Command.enabled.
The SHIFT+1..5 order is the order the server returns saved emotes in, which is unordered — it is not a slot list the player can sort.
Adjust mode
All keys in RM.Adjust.keys accept either a RedM input name (hashed at runtime) or a raw control hash.
| Key | Default | What it does |
|---|---|---|
RM.Adjust.enabled | true | Master switch. Also gates the command and the gizmo keymapping |
RM.Adjust.command | 'adjustanim' | Chat command that starts adjust mode. A falsy value registers none |
RM.Adjust.moveStep | 0.015 | Metres per frame while a nudge key is held |
RM.Adjust.rotateStep | 1.2 | Degrees per frame while a rotate key is held |
RM.Adjust.maxOffset | 5.0 | Maximum horizontal nudge away from the placement base — the aim point while raycast placement is still following the crosshair, otherwise the spot where adjust started. It does not cap total distance from the start when raycast = true; aimDistance does |
RM.Adjust.maxHeight | 1.5 | Maximum up or down nudge, metres |
RM.Adjust.ghost | true | Shows a translucent double instead of moving the real character. If the model fails to stream it falls back to moving the player |
RM.Adjust.ghostAlpha | 180 | Ghost transparency, 0–255 |
RM.Adjust.ghostModel | '' | Explicit ghost model. '' uses the RM.Showcase model for the player's gender |
RM.Adjust.ghostClone | false | Clone the player's own character. Renders as detached clothing on VORP and RSG MP characters — leave this false |
RM.Adjust.raycast | true | Aim placement: the preview follows the crosshair until the player nudges or drags. Requires ghost = true |
RM.Adjust.aimDistance | 25.0 | Maximum aim-placement distance, metres |
RM.Adjust.freezeOnConfirm | true | Keeps the character frozen on the confirmed spot so they cannot slide off a ledge |
RM.Adjust.gizmo | true | Enables the drag handles, /rm_gizmo and its keymapping |
RM.Adjust.gizmoKey | 'G' | Key registered for the gizmo toggle |
Changing gizmoKey later only affects new players
It is registered through RedM's keymapping system, so anyone who already played with the old value keeps it until they rebind in the RedM settings.
RM.Adjust.keys:
| Key | Default | Action |
|---|---|---|
confirm | 'INPUT_FRONTEND_ACCEPT' (Enter) | Commit the placement |
cancel | 'INPUT_FRONTEND_CANCEL' (Backspace) | Revert and exit |
forward / back | 'INPUT_MOVE_UP_ONLY' / 'INPUT_MOVE_DOWN_ONLY' | W / S |
left / right | 'INPUT_MOVE_LEFT_ONLY' / 'INPUT_MOVE_RIGHT_ONLY' | A / D |
rotLeft / rotRight | 'INPUT_FRONTEND_LEFT' / 'INPUT_FRONTEND_RIGHT' | Arrow Left / Right |
up / down | 'INPUT_FRONTEND_UP' / 'INPUT_FRONTEND_DOWN' | Arrow Up / Down |
Do not delete individual entries from RM.Adjust.keys
Each is read without a fallback, so a missing entry reaches the control check as nil at runtime. Change the value instead of removing the line.
Setting RM.Adjust.command to an empty string is not an off switch. '' is truthy in Lua, so command = '' still passes the registration guard and registers a nameless command plus a bare / chat suggestion. Use false. This differs from RM.Webhook and RM.Adjust.ghostModel, where '' is the documented off value.
INPUT_ATTACK and INPUT_AIM are disabled for the whole adjust session and are not configurable. Gizmo drag sensitivity, ring geometry and HUD text are likewise fixed.
Showcase mannequin
| Key | Default | What it does |
|---|---|---|
RM.Showcase.enabled | true | Master switch. Also gates the per-player Preview toggle |
RM.Showcase.inMenu | false | false floats the mannequin in front of the player's view. true projects it into the menu's framed pane |
RM.Showcase.levelCamera | false | Creates a scripted level camera behind the character |
RM.Showcase.cameraBack | 3.0 | Metres behind the character for that camera |
RM.Showcase.clonePlayer | false | Broken for VORP and RSG MP characters — leave false |
RM.Showcase.opacity | 220 | 0–255 |
RM.Showcase.scale | 0.6 | Mannequin size |
RM.Showcase.distance | 1.5 | Metres in front of the camera |
RM.Showcase.heightOffset | 1.0 | Lowers the mannequin. Raise this if it sits too high |
RM.Showcase.models.male | 'rcsp_native_americanfathers_males_01' | Also the default adjust-mode ghost for male characters |
RM.Showcase.models.female | 're_deadjohn_females_01' | Same, and the fallback when the gender lookup misses |
Notes that matter:
opacity,scaleanddistanceare read without a fallback. Deleting one produces a runtime error when the mannequin spawns.- The framed in-menu pane is drawn as soon as
inMenu = true. SetlevelCamera = truealongside it — without the scripted camera the mannequin is projected through the gameplay camera and is not held at eye level. With the shipped defaults the pane is never drawn. cameraBackhas no effect untillevelCamera = true— the camera routine returns immediately while it is off.levelCamerais tested with== false, so removing the key turns the camera on.inMenuis also tested with~= false. Removing the key, or commenting it out, turns the framed pane on. Writefalseexplicitly.- Ambient models stream reliably at runtime; story models (
player_zero,cs_*) often fail on some RedM builds. - Walk styles and shared emotes never hover-preview by design — a walk is only visible in motion and a shared emote needs a partner.
Shared emotes
| Key | Default | What it does |
|---|---|---|
RM.Shared.range | 3.0 | Metres searched for the closest partner. Players, NPCs and animals all qualify |
RM.Shared.timeout | 8000 | Milliseconds before an unanswered request auto-declines |
The accept and decline keys (E and R) are hard-coded and are not part of RM.Shared. The definitions themselves live in shared/extras.lua.
What you will look for and not find
Fixed in code: the length of the Recent tab (24), the list of keys that can never be bound, the number of SHIFT hotkeys (5), the menu close sound, notification duration (3000 ms), the reserved /e words, and the database table names.
Chair models, sitting styles, prop emotes and shared emotes are configurable — they just live in shared/seats.lua and shared/extras.lua rather than here.
One key is conditionally inert: RM.Showcase.cameraBack does nothing until levelCamera = true. Otherwise every key in the shipped file is read by the code — there are no dead settings.
Recipes
Change the key that opens the menu
Numeric hash only; names are not resolved here.
RM.Keys = {
open = { active = true, control = 0x80F28E95 }, -- INPUT_PLAYER_MENU
cancel = { active = true, control = 0x7DA48D2A, forceStop = true }, -- X / INPUT_FRONTEND_RS
}A control hash is GetHashKey('INPUT_NAME'). The only two hashes verified in this resource are 0x4CC0E2FE (INPUT_OPEN_SATCHEL_MENU, B) and 0x7DA48D2A (INPUT_FRONTEND_RS, X), both from config.lua.
Not certain of a hash? Leave the key off and use /emotes, or the next recipe.
Hand the keys to your own keybinding resource
Turn off both threads but keep the table in place:
RM.Keys = {
open = { active = false, control = 0x4CC0E2FE },
cancel = { active = false, control = 0x7DA48D2A, forceStop = true },
}-- your own client script
RegisterCommand('myemotesbind', function()
TriggerEvent('rm_emotes:client:open')
end, false)rm_emotes:client:open is provided for exactly this. See the Developer API.
Run without any framework
RM.Framework = 'standalone'
RM.NotifyMode = 'text'The job lookup returns nothing, so restricted becomes an ACE-only check. Express job gating as ACE principals:
add_ace group.sheriff lawman allowThen set restricted to lawman on the emote row. Favourites and key binds keep working — they key off the player's identifier, not the framework.
Restrict the admin editor to one person
RM.AdminAce = 'rm_emotes.admin'
RM.Admins = {}add_ace identifier.license:0000000000000000000000000000000000000000 rm_emotes.admin allowTurn off adjust mode, or just the gizmo
RM.Adjust = {
enabled = false,
-- leave the rest of the table as shipped
}Keep adjust mode but drop the mouse gizmo:
gizmo = false,
gizmoKey = 'G',Write false explicitly — both are tested against literal false, so removing the line switches the feature back on.
Disable the showcase mannequin
RM.Showcase = {
enabled = false,
inMenu = false,
levelCamera = false,
-- leave every remaining key in place: opacity, scale and distance
-- are read without a fallback and removing them errors at runtime
cameraBack = 3.0,
clonePlayer = false,
opacity = 220,
scale = 0.6,
distance = 1.5,
heightOffset = 1.0,
models = {
male = 'rcsp_native_americanfathers_males_01',
female = 're_deadjohn_females_01',
},
}Switch to the framed in-menu preview pane
The pane is drawn as soon as inMenu = true. Set levelCamera = true alongside it, or the mannequin is projected through the gameplay camera and does not sit at eye level.
enabled = true,
inMenu = true,
levelCamera = true,
cameraBack = 3.0,If the mannequin sits too high or low inside the pane, adjust heightOffset: a larger value lowers it.
Enable the Discord audit log
RM.Webhook = 'https://discord.com/api/webhooks/000000000000000000/replace-with-your-own-token'The webhook URL is a credential
It sits in plain text in config.lua. Anyone you send your config to for support can post arbitrary messages into that channel. Redact it before sharing. Delivery failures are silent — the response is never inspected.
Three events are logged: emote created, updated and deleted through the editor. Player usage, favourites and rejected attempts are not.
Change the menu language and rename the commands
RM.Lang = 'Spanish'
RM.Command = { enabled = true, name = 'gestos' }
RM.SlashCommand = { enabled = true, name = 'g' }Several player-visible strings stay English regardless of RM.Lang: the /e and /adjustanim chat suggestions, the gizmo key-binding label RM Emotes: toggle adjust gizmo shown in RedM's Settings > Key Bindings screen, the mannequin load-failure notice and the RM Emotes title it is sent with, the [rm_emotes] console lines, and the Discord webhook embed titles. None are editable in the escrow edition. Emote names come from the database, so import the matching seed as well — see Languages.