RedM Fireworks Configuration
Everything lives in config/config.lua. It is a shared_scripts entry, so the same RM_Config table exists on both the client and the server side of rm_fireworks.
After a config-only edit:
restart rm_fireworksThat is enough for anything here except a brand-new inventory item — a new VORP items row needs a full server restart before vorp_inventory sees it.
RM_Config does not cross resource boundaries
Every CitizenFX resource gets its own Lua state. shared_scripts shares the file between this resource's client and server, not between resources. Your own resource cannot read RM_Config no matter what the load order is — see Developer API.
Every key
| Key | Default | What it does |
|---|---|---|
Framework | "auto" | "auto", "vorp" or "rsg". Forcing a value does not skip detection |
KeyUse | 0x760A9C6F (G) | Control read to place the fuse and to push the plunger |
KeyCancel | 0xE30CD707 (R) | Control that aborts and refunds |
KeyLabels | { use = "G", cancel = "R" } | Display text only. No relationship to the hashes |
SyncFireworks | true | Re-broadcast a detonation so everyone runs the show. Item flow only |
RangeMin.interior | 5.0 | Minimum distance from the crate, used when the player is in an interior |
RangeMin.exterior | 15.0 | Minimum distance outdoors |
EnableLogs | true | Master switch for JD_logsV3 logging |
LogId_fireworks | "fireworks" | The JD_logsV3 channel. Must exist in your JD_logsV3 config |
LanguageLogs.explosion | "**FIREWORKS EXPLODED**\n\nPosition: " | Log prefix; coordinates are appended |
Language | 7 entries | Plaque strings. Only five are read |
Effects.trail | anm_oddf / ent_anim_oddf_firework_trail | Looped particle attached to each climbing rocket |
Effects.sound | anm_oddf / ent_anim_oddf_powder_flash_show | A particle, not audio |
Effects.offSetZ | 60.0 | Height above the launch point at which a rocket bursts |
Fireworks | 5 entries | The firework definitions. The index is the id |
Distances are game units; the plaque prints them with an m suffix.
Framework detection
Only VORP and RSG. There is no ESX/QBCore path and no standalone inventory mode.
| Side | VORP requires | RSG requires |
|---|---|---|
| Client | vorp_core started | rsg-core started |
| Server | both vorp_core and vorp_inventory started | rsg-core started |
Setting Framework to "vorp" or "rsg" does not skip detection. The 500 ms poll runs either way; the forced value only disables the other branch. Folder names must match exactly — a core folder named anything else leaves the loop polling forever, with no error printed.
The footgun in that table
The server bridge requires vorp_inventory; the client bridge does not. If vorp_inventory is stopped, the client reports a framework but the server's registration loop never exits its wait, so no firework item is ever registered as usable. Check vorp_inventory first when items do nothing.
Effects.sound is a particle, not audio
Worth reading twice before you file a bug.
RM_StartSingleFirework picks one entry from Effects.sound and hands it to the same helper it uses for the burst — RM_UTILS.PlayFXNonLooped, which calls StartParticleFxNonLoopedAtCoord. So the "sound" entry is a second particle effect at the burst coordinates. The shipped value is a powder flash.
The resource plays no audio at all. There is not a single audio native anywhere in it — no PlaySound*, no audio bank request, no sound dependency. The shows are silent apart from whatever noise the particle assets carry.
| You want | What to do |
|---|---|
| A different flash at the burst | Change or extend the Effects.sound list |
| No second effect at all | Comment out the second PlayFXNonLooped line. Emptying the list is not a clean off switch — the random pick then returns nothing and the rocket thread errors at the burst |
| An actual boom | Add your own audio call next to those two lines in RM_StartSingleFirework, or hang it off the detonation hook. There is no config key and no per-burst event |
Two things if you add audio there: the branch runs once per rocket, so a 100-rocket show fires it 100 times on every client — and source edits are lost on update.
RM_Config.Fireworks
Entry shape
{
item = "firework_1", -- item name, or false
coords = false, -- vector3, or false
number = 100, -- rockets in one show
randomWait = {300, 800}, -- ms between launches
effects = { -- one picked at random per rocket
{dict = "scr_lom_train", name = "scr_ind1_firework_burst"},
},
},| Combination | Launched by |
|---|---|
item set, coords = false | Using the item, or Start(index, canCancel). Launches from the planted crate |
item = false, coords set | TriggerClientEvent("rm_fireworks:LaunchFireworks", -1, index). No animation, no item |
| Both set | Either. The item path ignores coords; LaunchFireworks still honours it. Supported and useful |
| Neither | Unreachable |
The index is the id
RM_Config.Fireworks[3] is firework id 3. Inserting an entry in the middle renumbers everything below it, silently changing the id of every command, export call and event you already wrote. Always append at the end.
Use integer indexes only. A string-keyed entry does register its item, but the client runs the id through tonumber and rejects it with a red unknown firework id line.
What ships
| Index | item | coords | number | randomWait | effects |
|---|---|---|---|---|---|
| 1 | firework_1 | false | 100 | {300, 800} | burst, burst_long, burst_short |
| 2 | firework_2 | false | 100 | {300, 800} | burst ×1, ribbon ×4 |
| 3 | firework_3 | false | 100 | {300, 800} | burst ×1, points ×4 |
| 4 | firework_4 | false | 100 | {300, 800} | all five, one each |
| 5 | false | vector3(2508.15, -1201.93, 52.48) | 180 | {100, 300} | all five, ribbon ×2 |
Entry 5 is only an example, not a reserved slot. You can have any number of coords-driven entries.
Show length and performance
approximate launch duration (s) = number × ((randomWait[1] + randomWait[2]) / 2) / 1000Each rocket climbs offSetZ (60.0) at a fixed Z velocity of 40.0, so a rocket lives about 1.5 seconds. Concurrency is roughly 1.5 s ÷ average randomWait.
| Entry | Rockets | Avg gap | Launch duration | Alive at once |
|---|---|---|---|---|
| 1–4 (the items) | 100 | ~550 ms | ~55 s | ~3 |
| 5 (coords example) | 180 | ~200 ms | ~36 s | ~7–8 |
Each rocket is a separate thread with its own prop and a looped particle, ticking every 10 ms. Rocket props are non-networked, so every client that plays the show pays this cost locally.
The sync broadcast has no distance culling
It goes to every connected player, so a player on the far side of the map spawns and ticks the full number of rocket threads for a show they cannot see. That is the real cost driver on a full server. Add a distance check at the top of the rm_fireworks:SyncFireworks handler if it matters — see Troubleshooting → performance.
Rockets fly in straight diagonal lines rather than arcs: velocity is re-applied every 10 ms, so gravity never accumulates. X and Y are math.random(-10, 10), Z is a fixed 40.0. None of that is configurable.
Effect weighting
There is no weight field. The pick is effects[math.random(#effects)] — a uniform draw — so the way to weight an effect is to list it more than once. That is the entire tuning mechanism, and it applies identically to Effects.trail and Effects.sound.
The shipped firework_2 already uses it: burst ×1, ribbon ×4 — a 20 % / 80 % split.
For a 60 / 30 / 10 mix, pick a list length that divides cleanly — ten entries: six burst_long, three points, one ribbon. The list length costs nothing at runtime; it is indexed once per rocket, not iterated.
Particle dictionaries
| Where | dict | name |
|---|---|---|
Effects.trail | anm_oddf | ent_anim_oddf_firework_trail |
Effects.sound | anm_oddf | ent_anim_oddf_powder_flash_show |
Entry effects | scr_lom_train | scr_ind1_firework_burst, _burst_long, _burst_short, _points, _ribbon |
The bottom of config.lua carries a commented reference list of alternatives — four blocks, and they are not four equivalent sets:
| Block | dict | Names listed | Trailing commas? |
|---|---|---|---|
| 1 | scr_lom_train | all five | No |
| 2 | scr_net_race_checkpoints | two only — burst and scr_net_race_starting_flare | No |
| 3 | scr_ind1 | all five | Yes |
| 4 | anm_ind | all five | Yes |
Blocks 1 and 2 have no trailing comma
Pasting them into a table as-is is a Lua syntax error and the resource will not start. Add the commas yourself.
Bad dict names hang the effect thread
RM_UTILS.LoadPtfxAsset is an unbounded wait with no timeout and no error message.
| Mistake | Result |
|---|---|
Misspelled dict | The thread spins forever. A bad Effects.trail dict means every rocket hangs and nothing launches. A bad burst dict leaves props stuck in the air |
Valid dict, misspelled name | The asset loads, the effect just does not appear. No hang, no error |
If a config change makes fireworks stop appearing with a clean console, suspect a typo in dict.
Rebinding keys
RM_Config.KeyUse = 0x760A9C6F -- G
RM_Config.KeyCancel = 0xE30CD707 -- R
RM_Config.KeyLabels = { use = "G", cancel = "R" }The same two controls are used in both prompt stages; you cannot set a different key for the fuse and the detonator.
KeyLabels is display text only
Change KeyUse alone and the key changes while the prompt still says G. Change KeyLabels.use alone and the text changes while the key is still G. You must edit both.
Those two hashes are the only ones this resource has been checked against. This page deliberately gives you no third hash to copy: a wrong hash fails silently, the prompt sits there and the key never fires — indistinguishable from a hash someone made up. Look yours up in a RedM control reference (the INPUT_* control hash, not the keyboard scancode), set the matching label, and test it in game.
Where cancel actually works
| Stage | Duration | KeyCancel read? |
|---|---|---|
| Plant + three crate hauls | ~13.9 s | No |
| Scripted 180° turn | 2.0 s | No |
| Fuse walk | until you act | Yes, always |
| Wiring animation | ~8.8 s | No |
| Detonator entry | ~1.8 s | No |
| Detonator prompt | until you act | Only when canCancel is true |
The inventory item path passes canCancel = true, so a player using an item can always back out at the detonator.
The distance gate
Recomputed every frame during the fuse walk.
| Behaviour | Detail |
|---|---|
| Which value applies | GetInteriorFromEntity on the player. 0 selects the exterior value |
| Distance type | 3D — height difference counts |
| Displayed number | math.floor(distance) — reads 14 at 14.9 |
| Below the minimum | The prompt still draws at 0.35 opacity and the use key is ignored. Cancel still works |
| Who covers the distance | The player. The script never moves the ped |
That last row is the most common wrong assumption. The only movement task anywhere is a scripted 2-second turn. There is no TaskGoStraightToCoord and no pathing — once the turn finishes, the player walks away under their own control with a "spool" walk style swapped in. If nobody presses a movement key, the distance never changes and the prompt never lights up.
Tuning notes:
15and15.0behave identically underlua54. The floats are convention, not a requirement.- Raising
RangeMin.interiorabove what a room allows makes the firework unplaceable indoors — the player's only exit is cancel, which refunds. - Setting either to
0.0makes the prompt usable immediately, removing the walk-away step. - Most RDR2 buildings are not real interiors, so
GetInteriorFromEntityreturns0inside them and the 15 m exterior gate applies. Game data, not a config bug. - The rope has a fixed initial length of
50.0. SettingRangeMin.exteriorfar beyond that stops the rope looking convincing.
Translating the UI
Language is declared with seven entries. Five are read.
| Index | Shipped | Used? | Where |
|---|---|---|---|
[1] | "Coil" | No — dead | Referenced nowhere |
[2] | "Detonator" | Yes | Plaque title, in both modes. There is no separate fuse title |
[3] | "Place" | Yes | Use label during the fuse walk |
[4] | "Distance:" | No — dead | The prefix is [7], not this |
[5] | "Press" | Yes | Use label on the detonator |
[6] | "Cancel" | Yes | Cancel label, both prompts |
[7] | "Distance: " | Yes | Distance prefix. Keep the trailing space |
[1] and [4] are left in the file so index numbering stays stable across updates. Do not spend time translating them.
Save config.lua as UTF-8
The plaque is a browser page and the strings reach it as JSON. Accented Latin, Cyrillic, Greek and CJK all work — but only if the file is UTF-8. Editors defaulting to ANSI/Windows-1252 produce mojibake (Détonateur → Détonateur), not a Lua error and not a console warning. Fixing the setting afterwards is not enough; re-type the affected strings, because the wrong bytes are already saved.
Three more constraints:
- The title renders uppercase — a CSS
text-transform, not the config string. Remove it and rebuild to keep mixed case. - The
munit suffix is hardcoded in the JSX, not a config string. See UI Reference. - Never set an entry to
nil. The React side merges over an English default, so a deleted entry falls back to English rather than rendering empty. If a label mysteriously stays English, you deleted it instead of translating it.
The plaque has a minimum width of 430 px and grows with content. Check your longest string in game.
Logging
Optional, and targets JD_logsV3 only.
- Logging returns early when
EnableLogsis false or whenJD_logsV3is not started. Nothing errors and nothing is logged, so leavingEnableLogs = trueon a server with no logging resource is fine. - The log is not validated. The server event is a plain net event, so a malicious client can post arbitrary coordinates into your channel. The multiplayer broadcast on the same event is validated.
- Only the item flow reaches that event. Coords-driven shows never touch the server and produce no log line.
- There is no log for cancel, refund or item removal.
SyncFireworks and what it does not cover
The server re-broadcasts only when the config switch is on, an id was supplied, and the server recorded that this player genuinely had the matching item removed. A client spamming the event cannot force shows onto other players.
Set it to false and the show is visible only to the detonating player. The crate, spool and detonator are networked either way, so other players always see the props — only the rocket show is gated.
The coords path is not covered by this setting. LaunchFireworks is a plain client event with no server round trip. To make an event show visible to everyone, broadcast it yourself with -1.
Not configurable
Do not go looking for keys that do not exist.
- No sound. Zero audio natives in the resource.
- No state guards, anywhere. Mounted, driving, swimming, falling, in a gunfight — the plant animation still plays out on its timers. There is no key to gate this and no early exit.
- No cooldown, rate limit or per-player cap. Ten fireworks means ten back-to-back shows.
- No ACE or permission system inside the resource.
- Rocket flight is fixed. Only
offSetZis configurable. Effects.trailandEffects.soundare global — you cannot give one firework a different trail.- No runtime coordinates. A new position means a new config entry and a restart.
- No damage and no fires. The crate is invincible and rocket collision is disabled.