Skip to content

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:

cfg
restart rm_fireworks

That 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

KeyDefaultWhat it does
Framework"auto""auto", "vorp" or "rsg". Forcing a value does not skip detection
KeyUse0x760A9C6F (G)Control read to place the fuse and to push the plunger
KeyCancel0xE30CD707 (R)Control that aborts and refunds
KeyLabels{ use = "G", cancel = "R" }Display text only. No relationship to the hashes
SyncFireworkstrueRe-broadcast a detonation so everyone runs the show. Item flow only
RangeMin.interior5.0Minimum distance from the crate, used when the player is in an interior
RangeMin.exterior15.0Minimum distance outdoors
EnableLogstrueMaster 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
Language7 entriesPlaque strings. Only five are read
Effects.trailanm_oddf / ent_anim_oddf_firework_trailLooped particle attached to each climbing rocket
Effects.soundanm_oddf / ent_anim_oddf_powder_flash_showA particle, not audio
Effects.offSetZ60.0Height above the launch point at which a rocket bursts
Fireworks5 entriesThe 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.

SideVORP requiresRSG requires
Clientvorp_core startedrsg-core started
Serverboth vorp_core and vorp_inventory startedrsg-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 wantWhat to do
A different flash at the burstChange or extend the Effects.sound list
No second effect at allComment 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 boomAdd 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

lua
{
    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"},
    },
},
CombinationLaunched by
item set, coords = falseUsing the item, or Start(index, canCancel). Launches from the planted crate
item = false, coords setTriggerClientEvent("rm_fireworks:LaunchFireworks", -1, index). No animation, no item
Both setEither. The item path ignores coords; LaunchFireworks still honours it. Supported and useful
NeitherUnreachable

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

IndexitemcoordsnumberrandomWaiteffects
1firework_1false100{300, 800}burst, burst_long, burst_short
2firework_2false100{300, 800}burst ×1, ribbon ×4
3firework_3false100{300, 800}burst ×1, points ×4
4firework_4false100{300, 800}all five, one each
5falsevector3(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) / 1000

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

EntryRocketsAvg gapLaunch durationAlive 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

Wheredictname
Effects.trailanm_oddfent_anim_oddf_firework_trail
Effects.soundanm_oddfent_anim_oddf_powder_flash_show
Entry effectsscr_lom_trainscr_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:

BlockdictNames listedTrailing commas?
1scr_lom_trainall fiveNo
2scr_net_race_checkpointstwo only — burst and scr_net_race_starting_flareNo
3scr_ind1all fiveYes
4anm_indall fiveYes

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.

MistakeResult
Misspelled dictThe 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 nameThe 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

lua
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

StageDurationKeyCancel read?
Plant + three crate hauls~13.9 sNo
Scripted 180° turn2.0 sNo
Fuse walkuntil you actYes, always
Wiring animation~8.8 sNo
Detonator entry~1.8 sNo
Detonator promptuntil you actOnly 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.

BehaviourDetail
Which value appliesGetInteriorFromEntity on the player. 0 selects the exterior value
Distance type3D — height difference counts
Displayed numbermath.floor(distance) — reads 14 at 14.9
Below the minimumThe prompt still draws at 0.35 opacity and the use key is ignored. Cancel still works
Who covers the distanceThe 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:

  • 15 and 15.0 behave identically under lua54. The floats are convention, not a requirement.
  • Raising RangeMin.interior above what a room allows makes the firework unplaceable indoors — the player's only exit is cancel, which refunds.
  • Setting either to 0.0 makes the prompt usable immediately, removing the walk-away step.
  • Most RDR2 buildings are not real interiors, so GetInteriorFromEntity returns 0 inside them and the 15 m exterior gate applies. Game data, not a config bug.
  • The rope has a fixed initial length of 50.0. Setting RangeMin.exterior far beyond that stops the rope looking convincing.

Translating the UI

Language is declared with seven entries. Five are read.

IndexShippedUsed?Where
[1]"Coil"No — deadReferenced nowhere
[2]"Detonator"YesPlaque title, in both modes. There is no separate fuse title
[3]"Place"YesUse label during the fuse walk
[4]"Distance:"No — deadThe prefix is [7], not this
[5]"Press"YesUse label on the detonator
[6]"Cancel"YesCancel label, both prompts
[7]"Distance: "YesDistance 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étonateurDé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:

  1. The title renders uppercase — a CSS text-transform, not the config string. Remove it and rebuild to keep mixed case.
  2. The m unit suffix is hardcoded in the JSX, not a config string. See UI Reference.
  3. 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 EnableLogs is false or when JD_logsV3 is not started. Nothing errors and nothing is logged, so leaving EnableLogs = true on 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 offSetZ is configurable.
  • Effects.trail and Effects.sound are 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.

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