Frameworks
Detection is automatic. The framework is used for exactly two things: notifications, and resolving a player's job for the restricted column.
Everything else — favourites, key binds, the library, adjust mode, sitting, props — is framework-independent, because player identity comes from the steam identifier with a license fallback.
Detection order
| Order | Test | Result |
|---|---|---|
| 0 | RM.Framework is not 'auto' | That value is used verbatim, with no validation |
| 1 | vorp_core is starting or started | vorp |
| 2 | rsg-core is starting or started | rsg |
| 3 | redem_roleplay is starting or started | redemrp |
| 4 | None of the above | standalone |
Detection runs once, at script load
A core that starts after RedM Emotes is never re-detected, and the resource silently behaves as standalone for the rest of its lifetime — no job restrictions, and notifications in chat instead of your framework's notify.
ensure your framework before ensure rm_emotes, or set RM.Framework explicitly.
Both starting and started count, so a core mid-boot is detected correctly.
What actually differs
| VORP Core | RSG Core | RedEM:RP | Standalone | |
|---|---|---|---|---|
| Detected by | vorp_core | rsg-core | redem_roleplay | nothing matched |
| Notification | vorp:TipRight | RSGCore:Notify | redem_roleplay:Tip | chat:addMessage |
| Job source | character .job | PlayerData.job.name | GetJob(), else .job | none — ACE only |
| Re-sync on character load | client | client and server | server | none — pulls once at start |
Extra server.cfg lines | none | none | none | none |
That is the complete list. There is nothing else.
VORP Core
ensure oxmysql
ensure vorp_core
ensure rm_emotes
add_ace group.admin rm_emotes.admin allowconfig.lua can stay untouched — RM.Framework = 'auto' detects it, notifications use vorp:TipRight, and favourites sync on vorp:SelectedCharacter.
Restricted emotes: put a VORP job name (e.g. sheriff) or an ACE in the emote's Restriction field.
Keep RM.Showcase.clonePlayer = false
That is the shipped default and it should stay that way. RedM cannot clone a component-built mp_male / mp_female character cleanly — the mannequin renders as detached clothing. The configured RM.Showcase.models are used instead, picked by character gender.
The same applies to RM.Adjust.ghostClone.
RSG Core
ensure oxmysql
ensure rsg-core
ensure rm_emotes
add_ace group.admin rm_emotes.admin allowNotifications use RSGCore:Notify, which is the one pipe that receives the error / primary kind. Favourites sync on RSGCore:Client:OnPlayerLoaded and RSGCore:Server:OnPlayerLoaded.
Servers without Steam are handled — identity falls back to the license: identifier, so favourites and key binds persist either way.
Restricted emotes: use the job name as it appears in PlayerData.job.name, not the label. vallaw, not "Valentine Law".
If your admins are managed through RSG groups rather than cfg ACEs, grant the editor per identifier:
add_ace identifier.license:xxxxxxxxxxxxxxxxxxxx rm_emotes.admin allowRedEM:RP
ensure oxmysql
ensure redem_roleplay
ensure rm_emotes
add_ace group.admin rm_emotes.admin allowoxmysql is still required
RedEM:RP has its own database layer, but RedM Emotes uses oxmysql regardless. It must be started even if nothing else on your server uses it. It can point at the same database.
Notifications use redem_roleplay:Tip. Favourites sync on the server-side redemrp:playerLoaded event, one second after it fires.
Restricted emotes: the job returned by GetJob(), falling back to .job.
Standalone
The only hard dependency is oxmysql.
ensure oxmysql
ensure rm_emotes
add_ace group.admin rm_emotes.admin allowEverything works except job-based restrictions. The job lookup returns nothing, so restricted degrades to a pure ACE check. Express your access rules as ACE principals:
add_ace group.vip rm_emotes.vip allow
add_principal identifier.license:xxxxxxxxxxxxxxxxxxxx group.vipThen put rm_emotes.vip in the emote's Restriction field.
Notifications are plain chat lines. Prefer silence, or your own system:
RM.NotifyMode = 'none' -- suppress entirely
RM.NotifyMode = 'ctrl' -- route to a resource handling ctrl:cAvancedNotifyFavourites and key binds keep working — they key off the player's Steam or license identifier, not the framework.
No character-load hook fires
On an unknown or absent framework the client pulls once when the resource starts. If your setup has its own character-select step, trigger a re-pull yourself — see Developer API.
Running something else entirely
Anything not in the four lists above falls through to standalone behaviour without erroring. Set it explicitly rather than relying on the fallback:
RM.Framework = 'standalone'
RM.NotifyMode = 'ctrl'RM.Framework is not validated — an unrecognised string behaves exactly like standalone except for the string itself.
Adding a real bridge
A genuine fifth framework means editing RMBridge.jobOf in server/framework.lua and RMBridge.notify in client/framework.lua. Those files are encrypted in the escrow edition, so this is only possible on the Open Source edition.
Escrow customers are limited to the RM.Framework and RM.NotifyMode values above, plus ACE-based restrictions — which, since ACE works identically on all four setups, covers most of what job restrictions would have given you.
The notification contract
RM.NotifyMode picks the pipe. 'ctrl' is the one that hands you the full payload:
| Mode | Triggers | Gets title | Gets kind |
|---|---|---|---|
vorp | vorp:TipRight(message, 3000) | no | no |
rsg | RSGCore:Notify(message, 'error'|'primary', 3000) | no | yes |
redemrp | redem_roleplay:Tip(message, 3000) | no | no |
ctrl | ctrl:cAvancedNotify(kind, title, message, 3000) | yes | yes |
none | nothing | — | — |
text or anything unknown | chat:addMessage({ args = { title, message } }) | yes | no |
-- my_notify/client.lua
-- kind is 'default' or 'error'; duration is always 3000 ms
AddEventHandler('ctrl:cAvancedNotify', function(kind, title, message, duration)
MyNotify(title, message, kind == 'error' and 'error' or 'info', duration)
end)The duration is hard-coded on every branch. Several strings bypass the locale system and stay English regardless of RM.Lang: the /e and /adjustanim chat suggestions, the preview-model failure notice, and the key-binding label "RM Emotes: toggle adjust gizmo" shown in the RedM key settings. See Languages for the full list.
Troubleshooting detection
Notifications appear as chat lines on a framework server. Detection fell through to standalone because your core started after RedM Emotes. Fix the ensure order, or set RM.Framework and RM.NotifyMode explicitly.
Job-restricted emotes are invisible to everyone, including the right job. Either the same detection failure, or the job string does not match exactly — the comparison is case-sensitive. Check the order first, then compare against the exact name your framework returns.