Today Iβm releasing SpaceELS, a lightweight, standalone, and ultra-optimized Emergency Lighting System (ELS) controller designed for FiveM emergency vehicles.
Features
Ultra Optimized
Dynamic Environmental Lighting: Optional 3D ground & ambient reflections for immersive roleplay.
In-Game ELS UI: Sleek, clean, interactive control panel (/els or default U).
JSON Vehicle Profiles: Easy setup for stages, patterns, and light extras in vehicle_profiles.json.
Concept looks solid, so I gave it a try. Short version, facts plus fixes.
Tested on QBCore, current main. Works, but performance degrades over a session and a few things need fixing.
Custom keybinds do nothing. customSirenControl and friends (els.lua 111-134) are set but never read, there is no IsControlJustPressed in the client. Only the four RegisterKeyMapping calls work. Fix: add a control thread that polls the cached controls, or drop the layer and document that keys are set through the FiveM keybind menu. Also KEY_TO_CONTROL maps J to -1, which is invalid.
No cleanup. trackedVehicles, activeEnvVehicles and activeSequencerVehicles are keyed by entity handle and never pruned, and neither loop checks DoesEntityExist. Handles get reused, so a new vehicle inherits the old state, and the sequencer keeps working stale entries, which is where the frame time goes over a longer session. Fix: check DoesEntityExist inside both loops and nil the entry when it fails, or key by network ID.
Sound IDs leak. They are only released in onResourceStop. Fix: release in the same cleanup pass as point 2.
Sender gets its own sync back. Server broadcasts with TriggerClientEvent(-1), so every stage switch runs twice locally, which is the audible stutter. Fix: loop over GetPlayers and skip source, or ignore the event client-side when the netId belongs to a vehicle you control.
Siren enable sets data.stage = 3 without SetupVehicleStageData, so the UI shows stage 3 while no lights run. Fix: call SetupVehicleStageData right after.
isClearingIntersection is only reset if the vehicle still exists after 4200 ms, otherwise it stays true and the feature is dead until restart. The thread also uses GetTargetVehicle instead of the captured veh. Fix: reset unconditionally, use the captured handle.
Server events are unvalidated. netId is never checked against the sender, and saveVehicleProfile / resetVehicleProfile write the file with no permission check, no debounce, no size limit, so any player can wipe all profiles or spam disk writes. Fix: verify the sender owns the entity, gate the two profile events behind an ace permission, and debounce SaveProfiles.
The control disabler and SetVehicleHasMutedSirens apply to every vehicle entered, not just profile vehicles, so the horn is dead in civilian cars and other siren systems get muted. Fix: only run both when GetProfileForVehicle returns a profile.
Airhorn on E collides with the standard interaction key on most frameworks. Suggest a neutral default.
Config keys Command, CommandAlias, StudioCommand, StudioCommandAlias, DefaultKey, AllowPassengers and SoundVolume are never referenced. Either wire them up or remove them.
index.html loads Google Fonts and Font Awesome from CDNs, so restricted clients get an unstyled panel. Ship them locally.
Shipped vehicle_profiles.json is empty, nothing flashes out of the box. A sample profile for one vanilla vehicle would save everyone the first hour.
Happy to retest once some of this lands. Thanks for the work, hoping for fixes soon.