From df83fa7c88f1e974adecd1cc2c94473839214c97 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 15:58:23 +0100 Subject: [PATCH 01/67] feat: add high beam flash option --- resource/client/lights.lua | 73 ++++++++++++++++++++++++++---------- resource/server/parseVCF.lua | 7 ++++ 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index b0c65ab..c42f0c7 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -16,35 +16,68 @@ local function SetLightStage(vehicle, stage, toggle) SetVehicleSiren(vehicle, toggle) end - while ELSvehicle[stage] do - -- keep the engine on whilst the lights are activated - SetVehicleEngineOn(vehicle, true, true, false) + if (VCFdata.patterns[pattern].flashHighBeam) then + Citizen.CreateThread(function() + -- get the current vehicle lights state + local _, lightsOn, highbeamsOn = GetVehicleLightsState(vehicle) - local lastFlash = {} + -- turn the lights on to avoid flashing tail lights + if lightsOn == 0 then SetVehicleLights(vehicle, 2) end - for _, flash in ipairs(VCFdata.patterns[pattern]) do - if ELSvehicle[stage] then - for _, extra in ipairs(flash['extras']) do - -- turn the extra on - SetVehicleExtra(vehicle, extra, 0) + -- flash the high beam + while ELSvehicle[stage] do + SetVehicleFullbeam(vehicle, true) + SetVehicleLightMultiplier(vehicle, 5.0) - -- save the extra as last flashed - table.insert(lastFlash, extra) - end + Wait(500) - Citizen.Wait(flash.duration) - end + SetVehicleFullbeam(vehicle, false) + SetVehicleLightMultiplier(vehicle, 1.0) - -- turn off the last flashed extras - for _, v in ipairs(lastFlash) do - SetVehicleExtra(vehicle, v, 1) + Wait(500) end - lastFlash = {} - end + -- reset initial vehicle state + if lightsOn == 0 then SetVehicleLights(vehicle, 0) end + if highbeamsOn == 1 then SetVehicleFullbeam(vehicle, true) end - Citizen.Wait(0) + Wait(0) + end) end + + Citizen.CreateThread(function() + while ELSvehicle[stage] do + -- keep the engine on whilst the lights are activated + SetVehicleEngineOn(vehicle, true, true, false) + + local lastFlash = {} + + for _, flash in ipairs(VCFdata.patterns[pattern]) do + if ELSvehicle[stage] then + for _, extra in ipairs(flash['extras']) do + -- turn the extra on + SetVehicleExtra(vehicle, extra, 0) + + -- save the extra as last flashed + table.insert(lastFlash, extra) + end + + Citizen.Wait(flash.duration) + end + + -- turn off the last flashed extras + for _, v in ipairs(lastFlash) do + SetVehicleExtra(vehicle, v, 1) + end + + lastFlash = {} + end + + Citizen.Wait(0) + end + + Wait(0) + end) end RegisterNetEvent('kjELS:resetExtras') diff --git a/resource/server/parseVCF.lua b/resource/server/parseVCF.lua index c8dc8ec..5257f2b 100644 --- a/resource/server/parseVCF.lua +++ b/resource/server/parseVCF.lua @@ -108,6 +108,13 @@ function ParseVCF(xml, fileName) vcf.patterns[type].isEmergency = true end + -- whether the pattern toggles flashing the high beam, default is false + if elem.attr['FlashHighBeam'] then + vcf.patterns[type].flashHighBeam = elem.attr['FlashHighBeam'] == 'true' + else + vcf.patterns[type].flashHighBeam = false + end + for _, flash in ipairs(elem.kids) do -- backwards compatibility for VCF's with 'FlashXX' tags local tag = string.upper(string.sub(flash.name, 1, 5)) From 39c90ed1c9781744119ed416c350ef67ca702e4b Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 18:09:25 +0100 Subject: [PATCH 02/67] feat: make intensity configurable --- resource/client/lights.lua | 2 +- resource/config.example.lua | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index c42f0c7..3344b28 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -27,7 +27,7 @@ local function SetLightStage(vehicle, stage, toggle) -- flash the high beam while ELSvehicle[stage] do SetVehicleFullbeam(vehicle, true) - SetVehicleLightMultiplier(vehicle, 5.0) + SetVehicleLightMultiplier(vehicle, Config.HighBeamIntensity or 5.0) Wait(500) diff --git a/resource/config.example.lua b/resource/config.example.lua index 067fca8..d7b18e0 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -12,6 +12,9 @@ Config.EnvironmentalLights = { Intensity = 1.0, -- how intense the light source is } +-- You can make the flashing high beams brighter. Set to 1.0 for GTA default +Config.HighBeamIntensity = 5.0 + -- Whether vehicle passengers are allowed to control the lights and sirens Config.AllowPassengers = false From b7224e5981eec4609a6b783e419ef4820504b974 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 18:39:04 +0100 Subject: [PATCH 03/67] feat: add high beam option to configurator --- configurator/components/patterns.vue | 5 +++++ configurator/helpers/transformImportedVCF.js | 3 ++- configurator/helpers/xmlGenerator.js | 1 + configurator/store/index.js | 6 +++--- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index af92642..2bb3222 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -18,6 +18,11 @@ Is emergency + +
diff --git a/configurator/helpers/transformImportedVCF.js b/configurator/helpers/transformImportedVCF.js index 087f7e9..3434f47 100644 --- a/configurator/helpers/transformImportedVCF.js +++ b/configurator/helpers/transformImportedVCF.js @@ -73,7 +73,8 @@ export default function generateStoreAttributesFromExistingVCF (data) { for (const pattern of patternsObject.children) { vcf.patterns.push({ name: pattern.nodeName, - isEmergency: pattern.getAttribute('IsEmergency') === 'true' + isEmergency: pattern.getAttribute('IsEmergency') === 'true', + flashHighBeam: pattern.getAttribute('FlashHighBeam') === 'true' }) for (const flash of pattern.children) { diff --git a/configurator/helpers/xmlGenerator.js b/configurator/helpers/xmlGenerator.js index 15995c8..853f972 100644 --- a/configurator/helpers/xmlGenerator.js +++ b/configurator/helpers/xmlGenerator.js @@ -78,6 +78,7 @@ export default { data.patterns.forEach((pattern) => { const p = doc.createElement(pattern.name) p.setAttribute('IsEmergency', pattern.isEmergency) + p.setAttribute('FlashHighBeam', pattern.flashHighBeam) const flashes = data.flashes.filter(flash => flash.pattern === pattern.name) diff --git a/configurator/store/index.js b/configurator/store/index.js index 158d65f..8468030 100644 --- a/configurator/store/index.js +++ b/configurator/store/index.js @@ -34,9 +34,9 @@ export const state = () => ({ { name: 'SrnTone4', allowUse: true, audioString: 'VEHICLES_HORNS_AMBULANCE_WARNING', soundSet: 'DLC_WMSIRENS_SOUNDSET' } ], patterns: [ - { name: 'PRIMARY', isEmergency: true }, - { name: 'SECONDARY', isEmergency: true }, - { name: 'REARREDS', isEmergency: true } + { name: 'PRIMARY', isEmergency: true, flashHighBeam: false }, + { name: 'SECONDARY', isEmergency: true, flashHighBeam: false }, + { name: 'REARREDS', isEmergency: true, flashHighBeam: false } ], flashes: [] } From bcefda6b9eb6d769e70a998df276033757f71c9a Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 18:51:59 +0100 Subject: [PATCH 04/67] docs: add new option --- docs/vcf.md | 8 ++++++-- resource/xmlFiles/Example.xml | 2 +- resource/xmlFiles/Example_ServerSideSIrens.xml | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/vcf.md b/docs/vcf.md index b1241e8..f5deb1b 100644 --- a/docs/vcf.md +++ b/docs/vcf.md @@ -187,13 +187,17 @@ The duration is measured in milliseconds. Flashes will be executed by appearance It's recommended to include an 'empty flash' at the bottom of each pattern to make the transition from last to first flash smooth. +### Extra options + You can optionally set `IsEmergency` to `false` if you don't want vehicles to pull over when you have that light stage activated. +Also, you can set `FlashHighBeam` to `true` if you want your head lights to flash every .5 seconds. This is disabled by default. You can tweak the flash intensity in `config.lua`. By default, the intensity is set to `5.0`. The game default is `1.0`. + Pattern example: ```xml - + @@ -215,7 +219,7 @@ Pattern example: - + diff --git a/resource/xmlFiles/Example.xml b/resource/xmlFiles/Example.xml index 1a253ea..2657111 100644 --- a/resource/xmlFiles/Example.xml +++ b/resource/xmlFiles/Example.xml @@ -35,7 +35,7 @@ - + diff --git a/resource/xmlFiles/Example_ServerSideSIrens.xml b/resource/xmlFiles/Example_ServerSideSIrens.xml index 3e9243f..86c371c 100644 --- a/resource/xmlFiles/Example_ServerSideSIrens.xml +++ b/resource/xmlFiles/Example_ServerSideSIrens.xml @@ -35,7 +35,7 @@ - + From 14660af6c265ab6bb557b2a20976db6addc06f92 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 15:58:23 +0100 Subject: [PATCH 05/67] feat: add high beam flash option --- resource/client/lights.lua | 73 ++++++++++++++++++++++++++---------- resource/server/parseVCF.lua | 7 ++++ 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index b0c65ab..c42f0c7 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -16,35 +16,68 @@ local function SetLightStage(vehicle, stage, toggle) SetVehicleSiren(vehicle, toggle) end - while ELSvehicle[stage] do - -- keep the engine on whilst the lights are activated - SetVehicleEngineOn(vehicle, true, true, false) + if (VCFdata.patterns[pattern].flashHighBeam) then + Citizen.CreateThread(function() + -- get the current vehicle lights state + local _, lightsOn, highbeamsOn = GetVehicleLightsState(vehicle) - local lastFlash = {} + -- turn the lights on to avoid flashing tail lights + if lightsOn == 0 then SetVehicleLights(vehicle, 2) end - for _, flash in ipairs(VCFdata.patterns[pattern]) do - if ELSvehicle[stage] then - for _, extra in ipairs(flash['extras']) do - -- turn the extra on - SetVehicleExtra(vehicle, extra, 0) + -- flash the high beam + while ELSvehicle[stage] do + SetVehicleFullbeam(vehicle, true) + SetVehicleLightMultiplier(vehicle, 5.0) - -- save the extra as last flashed - table.insert(lastFlash, extra) - end + Wait(500) - Citizen.Wait(flash.duration) - end + SetVehicleFullbeam(vehicle, false) + SetVehicleLightMultiplier(vehicle, 1.0) - -- turn off the last flashed extras - for _, v in ipairs(lastFlash) do - SetVehicleExtra(vehicle, v, 1) + Wait(500) end - lastFlash = {} - end + -- reset initial vehicle state + if lightsOn == 0 then SetVehicleLights(vehicle, 0) end + if highbeamsOn == 1 then SetVehicleFullbeam(vehicle, true) end - Citizen.Wait(0) + Wait(0) + end) end + + Citizen.CreateThread(function() + while ELSvehicle[stage] do + -- keep the engine on whilst the lights are activated + SetVehicleEngineOn(vehicle, true, true, false) + + local lastFlash = {} + + for _, flash in ipairs(VCFdata.patterns[pattern]) do + if ELSvehicle[stage] then + for _, extra in ipairs(flash['extras']) do + -- turn the extra on + SetVehicleExtra(vehicle, extra, 0) + + -- save the extra as last flashed + table.insert(lastFlash, extra) + end + + Citizen.Wait(flash.duration) + end + + -- turn off the last flashed extras + for _, v in ipairs(lastFlash) do + SetVehicleExtra(vehicle, v, 1) + end + + lastFlash = {} + end + + Citizen.Wait(0) + end + + Wait(0) + end) end RegisterNetEvent('kjELS:resetExtras') diff --git a/resource/server/parseVCF.lua b/resource/server/parseVCF.lua index c8dc8ec..5257f2b 100644 --- a/resource/server/parseVCF.lua +++ b/resource/server/parseVCF.lua @@ -108,6 +108,13 @@ function ParseVCF(xml, fileName) vcf.patterns[type].isEmergency = true end + -- whether the pattern toggles flashing the high beam, default is false + if elem.attr['FlashHighBeam'] then + vcf.patterns[type].flashHighBeam = elem.attr['FlashHighBeam'] == 'true' + else + vcf.patterns[type].flashHighBeam = false + end + for _, flash in ipairs(elem.kids) do -- backwards compatibility for VCF's with 'FlashXX' tags local tag = string.upper(string.sub(flash.name, 1, 5)) From 56db2edaff5c4aa8a7b54c76f1e5bbbede930b0a Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 18:09:25 +0100 Subject: [PATCH 06/67] feat: make intensity configurable --- resource/client/lights.lua | 2 +- resource/config.example.lua | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index c42f0c7..3344b28 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -27,7 +27,7 @@ local function SetLightStage(vehicle, stage, toggle) -- flash the high beam while ELSvehicle[stage] do SetVehicleFullbeam(vehicle, true) - SetVehicleLightMultiplier(vehicle, 5.0) + SetVehicleLightMultiplier(vehicle, Config.HighBeamIntensity or 5.0) Wait(500) diff --git a/resource/config.example.lua b/resource/config.example.lua index 067fca8..d7b18e0 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -12,6 +12,9 @@ Config.EnvironmentalLights = { Intensity = 1.0, -- how intense the light source is } +-- You can make the flashing high beams brighter. Set to 1.0 for GTA default +Config.HighBeamIntensity = 5.0 + -- Whether vehicle passengers are allowed to control the lights and sirens Config.AllowPassengers = false From 9f1dc414ef794e70c68bc1137ba954cc134f2a1d Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 18:39:04 +0100 Subject: [PATCH 07/67] feat: add high beam option to configurator --- configurator/components/patterns.vue | 5 +++++ configurator/helpers/transformImportedVCF.js | 3 ++- configurator/helpers/xmlGenerator.js | 1 + configurator/store/index.js | 6 +++--- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index af92642..2bb3222 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -18,6 +18,11 @@ Is emergency + +
diff --git a/configurator/helpers/transformImportedVCF.js b/configurator/helpers/transformImportedVCF.js index 087f7e9..3434f47 100644 --- a/configurator/helpers/transformImportedVCF.js +++ b/configurator/helpers/transformImportedVCF.js @@ -73,7 +73,8 @@ export default function generateStoreAttributesFromExistingVCF (data) { for (const pattern of patternsObject.children) { vcf.patterns.push({ name: pattern.nodeName, - isEmergency: pattern.getAttribute('IsEmergency') === 'true' + isEmergency: pattern.getAttribute('IsEmergency') === 'true', + flashHighBeam: pattern.getAttribute('FlashHighBeam') === 'true' }) for (const flash of pattern.children) { diff --git a/configurator/helpers/xmlGenerator.js b/configurator/helpers/xmlGenerator.js index 15995c8..853f972 100644 --- a/configurator/helpers/xmlGenerator.js +++ b/configurator/helpers/xmlGenerator.js @@ -78,6 +78,7 @@ export default { data.patterns.forEach((pattern) => { const p = doc.createElement(pattern.name) p.setAttribute('IsEmergency', pattern.isEmergency) + p.setAttribute('FlashHighBeam', pattern.flashHighBeam) const flashes = data.flashes.filter(flash => flash.pattern === pattern.name) diff --git a/configurator/store/index.js b/configurator/store/index.js index 158d65f..8468030 100644 --- a/configurator/store/index.js +++ b/configurator/store/index.js @@ -34,9 +34,9 @@ export const state = () => ({ { name: 'SrnTone4', allowUse: true, audioString: 'VEHICLES_HORNS_AMBULANCE_WARNING', soundSet: 'DLC_WMSIRENS_SOUNDSET' } ], patterns: [ - { name: 'PRIMARY', isEmergency: true }, - { name: 'SECONDARY', isEmergency: true }, - { name: 'REARREDS', isEmergency: true } + { name: 'PRIMARY', isEmergency: true, flashHighBeam: false }, + { name: 'SECONDARY', isEmergency: true, flashHighBeam: false }, + { name: 'REARREDS', isEmergency: true, flashHighBeam: false } ], flashes: [] } From 12210b80111a6d76cec7f924866689ec880507c4 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Thu, 3 Mar 2022 18:51:59 +0100 Subject: [PATCH 08/67] docs: add new option --- docs/vcf.md | 8 ++++++-- resource/xmlFiles/Example.xml | 2 +- resource/xmlFiles/Example_ServerSideSIrens.xml | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/vcf.md b/docs/vcf.md index b1241e8..f5deb1b 100644 --- a/docs/vcf.md +++ b/docs/vcf.md @@ -187,13 +187,17 @@ The duration is measured in milliseconds. Flashes will be executed by appearance It's recommended to include an 'empty flash' at the bottom of each pattern to make the transition from last to first flash smooth. +### Extra options + You can optionally set `IsEmergency` to `false` if you don't want vehicles to pull over when you have that light stage activated. +Also, you can set `FlashHighBeam` to `true` if you want your head lights to flash every .5 seconds. This is disabled by default. You can tweak the flash intensity in `config.lua`. By default, the intensity is set to `5.0`. The game default is `1.0`. + Pattern example: ```xml - + @@ -215,7 +219,7 @@ Pattern example: - + diff --git a/resource/xmlFiles/Example.xml b/resource/xmlFiles/Example.xml index 1a253ea..2657111 100644 --- a/resource/xmlFiles/Example.xml +++ b/resource/xmlFiles/Example.xml @@ -35,7 +35,7 @@ - + diff --git a/resource/xmlFiles/Example_ServerSideSIrens.xml b/resource/xmlFiles/Example_ServerSideSIrens.xml index 3e9243f..86c371c 100644 --- a/resource/xmlFiles/Example_ServerSideSIrens.xml +++ b/resource/xmlFiles/Example_ServerSideSIrens.xml @@ -35,7 +35,7 @@ - + From b04ff5471033658c7063be4467db4742c82c65f6 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Fri, 11 Mar 2022 18:02:45 +0100 Subject: [PATCH 09/67] feat: add optional warning beep --- resource/client/lights.lua | 12 ++++++++++++ resource/config.example.lua | 5 +++++ resource/html/sounds/WarningBeep.ogg | Bin 0 -> 21101 bytes resource/server/parseVCF.lua | 7 +++++++ 4 files changed, 24 insertions(+) create mode 100644 resource/html/sounds/WarningBeep.ogg diff --git a/resource/client/lights.lua b/resource/client/lights.lua index 3344b28..189e45e 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -45,6 +45,18 @@ local function SetLightStage(vehicle, stage, toggle) end) end + if VCFdata.patterns[pattern].enableWarningBeep then + Citizen.CreateThread(function() + while ELSvehicle[stage] do + -- play warning sound + SendNUIMessage({ transactionType = 'playSound', transactionFile = 'WarningBeep', transactionVolume = 0.2 }) + + -- this should be equal to the length of the audio file + Citizen.Wait((Config.WarningBeepDuration or 0) * 1000) + end + end) + end + Citizen.CreateThread(function() while ELSvehicle[stage] do -- keep the engine on whilst the lights are activated diff --git a/resource/config.example.lua b/resource/config.example.lua index d7b18e0..d7f7e9b 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -31,6 +31,11 @@ Config.HornBlip = true -- Enables a short beep when a light stage or siren is activated Config.Beeps = false +-- Duration for the warning beep (in seconds) +-- Should be equal to the WarningBeep.ogg file +-- Only change this if you replace the audio file with your own +Config.WarningBeepDuration = 2.0 + -- Enables controller support for controlling the primary light stage and the sirens -- DPAD_LEFT = toggle primary lights -- DPAD_DOWN = toggle siren 1 diff --git a/resource/html/sounds/WarningBeep.ogg b/resource/html/sounds/WarningBeep.ogg new file mode 100644 index 0000000000000000000000000000000000000000..828c14ec44b7609fea1f886bf8bfacf10027d256 GIT binary patch literal 21101 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 Mq{m>xtrAG~A8&t-3jhEB literal 0 HcmV?d00001 diff --git a/resource/server/parseVCF.lua b/resource/server/parseVCF.lua index 5257f2b..4edec7f 100644 --- a/resource/server/parseVCF.lua +++ b/resource/server/parseVCF.lua @@ -115,6 +115,13 @@ function ParseVCF(xml, fileName) vcf.patterns[type].flashHighBeam = false end + -- whether the pattern enables a warning beep, default is false + if elem.attr['EnableWarningBeep'] then + vcf.patterns[type].enableWarningBeep = elem.attr['EnableWarningBeep'] == 'true' + else + vcf.patterns[type].enableWarningBeep = false + end + for _, flash in ipairs(elem.kids) do -- backwards compatibility for VCF's with 'FlashXX' tags local tag = string.upper(string.sub(flash.name, 1, 5)) From 7a5ee48110958dc0bd6724a7576d00371f1bad76 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Fri, 11 Mar 2022 18:10:34 +0100 Subject: [PATCH 10/67] docs: update examples --- docs/vcf.md | 6 ++++-- resource/xmlFiles/Example.xml | 2 +- resource/xmlFiles/Example_ServerSideSIrens.xml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/vcf.md b/docs/vcf.md index f5deb1b..b68cc1c 100644 --- a/docs/vcf.md +++ b/docs/vcf.md @@ -193,11 +193,13 @@ You can optionally set `IsEmergency` to `false` if you don't want vehicles to pu Also, you can set `FlashHighBeam` to `true` if you want your head lights to flash every .5 seconds. This is disabled by default. You can tweak the flash intensity in `config.lua`. By default, the intensity is set to `5.0`. The game default is `1.0`. +Finally, you can toggle `EnableWarningBeep` to `true` to add a subtile warning beep sound to indicate that, for example, your stop sign is turned on. By default, this is `false`. You can tweak the length of the beep by editing `Config.WarningBeepDuration`. The length of the default sound is 2 seconds. If you replace the audio file, make sure to set the duration to the exact length of the beep (or even longer to add a delay). + Pattern example: ```xml - + @@ -219,7 +221,7 @@ Pattern example: - + diff --git a/resource/xmlFiles/Example.xml b/resource/xmlFiles/Example.xml index 2657111..092ca38 100644 --- a/resource/xmlFiles/Example.xml +++ b/resource/xmlFiles/Example.xml @@ -51,7 +51,7 @@ - + diff --git a/resource/xmlFiles/Example_ServerSideSIrens.xml b/resource/xmlFiles/Example_ServerSideSIrens.xml index 86c371c..4356231 100644 --- a/resource/xmlFiles/Example_ServerSideSIrens.xml +++ b/resource/xmlFiles/Example_ServerSideSIrens.xml @@ -51,7 +51,7 @@ - + From 2658ac19cd2516e0bc40d2daa6df560468af9eca Mon Sep 17 00:00:00 2001 From: matsn0w Date: Fri, 11 Mar 2022 18:14:14 +0100 Subject: [PATCH 11/67] feat: add new option to configurator --- configurator/components/patterns.vue | 5 +++++ configurator/helpers/transformImportedVCF.js | 3 ++- configurator/helpers/xmlGenerator.js | 1 + configurator/store/index.js | 6 +++--- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index 2bb3222..ddee6d6 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -23,6 +23,11 @@ Flash high beam + +
diff --git a/configurator/helpers/transformImportedVCF.js b/configurator/helpers/transformImportedVCF.js index 3434f47..cd2cb84 100644 --- a/configurator/helpers/transformImportedVCF.js +++ b/configurator/helpers/transformImportedVCF.js @@ -74,7 +74,8 @@ export default function generateStoreAttributesFromExistingVCF (data) { vcf.patterns.push({ name: pattern.nodeName, isEmergency: pattern.getAttribute('IsEmergency') === 'true', - flashHighBeam: pattern.getAttribute('FlashHighBeam') === 'true' + flashHighBeam: pattern.getAttribute('FlashHighBeam') === 'true', + enableWarningBeep: pattern.getAttribute('EnableWarningBeep') === 'true' }) for (const flash of pattern.children) { diff --git a/configurator/helpers/xmlGenerator.js b/configurator/helpers/xmlGenerator.js index 853f972..1018e69 100644 --- a/configurator/helpers/xmlGenerator.js +++ b/configurator/helpers/xmlGenerator.js @@ -79,6 +79,7 @@ export default { const p = doc.createElement(pattern.name) p.setAttribute('IsEmergency', pattern.isEmergency) p.setAttribute('FlashHighBeam', pattern.flashHighBeam) + p.setAttribute('EnableWarningBeep', pattern.enableWarningBeep) const flashes = data.flashes.filter(flash => flash.pattern === pattern.name) diff --git a/configurator/store/index.js b/configurator/store/index.js index 8468030..0087ab4 100644 --- a/configurator/store/index.js +++ b/configurator/store/index.js @@ -34,9 +34,9 @@ export const state = () => ({ { name: 'SrnTone4', allowUse: true, audioString: 'VEHICLES_HORNS_AMBULANCE_WARNING', soundSet: 'DLC_WMSIRENS_SOUNDSET' } ], patterns: [ - { name: 'PRIMARY', isEmergency: true, flashHighBeam: false }, - { name: 'SECONDARY', isEmergency: true, flashHighBeam: false }, - { name: 'REARREDS', isEmergency: true, flashHighBeam: false } + { name: 'PRIMARY', isEmergency: true, flashHighBeam: false, enableWarningBeep: false }, + { name: 'SECONDARY', isEmergency: true, flashHighBeam: false, enableWarningBeep: false }, + { name: 'REARREDS', isEmergency: true, flashHighBeam: false, enableWarningBeep: false } ], flashes: [] } From 0aa6d9d604469e96226f0071ac22dc0cafa146c9 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Sat, 23 Apr 2022 22:39:54 +0200 Subject: [PATCH 12/67] feat: support client side key binds --- resource/client/extras_menu.lua | 10 +-- resource/client/keybinds.lua | 20 +++++ resource/client/main.lua | 126 ++++++++++++++++++++------------ 3 files changed, 106 insertions(+), 50 deletions(-) create mode 100644 resource/client/keybinds.lua diff --git a/resource/client/extras_menu.lua b/resource/client/extras_menu.lua index 786ade0..e079a87 100644 --- a/resource/client/extras_menu.lua +++ b/resource/client/extras_menu.lua @@ -37,6 +37,11 @@ mainMenu.OnCheckboxChange = function(sender, item, checked) end end +RegisterCommand('MISS-ELS:open-statics-menu', function () + -- toggle the menu visibility + mainMenu:Visible(not mainMenu:Visible()) +end) + Citizen.CreateThread(function() while true do if not kjxmlData then @@ -89,11 +94,6 @@ Citizen.CreateThread(function() MenuPool:RefreshIndex() end - - if IsDisabledControlJustPressed(0, Config.KeyBinds.ExtrasMenu) then - -- toggle the menu visibility - mainMenu:Visible(not mainMenu:Visible()) - end end Citizen.Wait(0) diff --git a/resource/client/keybinds.lua b/resource/client/keybinds.lua new file mode 100644 index 0000000..8f57899 --- /dev/null +++ b/resource/client/keybinds.lua @@ -0,0 +1,20 @@ +-- statics menu +RegisterKeyMapping('MISS-ELS:open-statics-menu', 'Open statics menu', 'KEYBOARD', 'U') + +-- indicators +RegisterKeyMapping('MISS-ELS:toggle-indicator-hazard', 'Toggle hazard lights', 'KEYBOARD', 'DOWN') +RegisterKeyMapping('MISS-ELS:toggle-indicator-left', 'Toggle left indicator', 'KEYBOARD', 'LEFT') +RegisterKeyMapping('MISS-ELS:toggle-indicator-right', 'Toggle right indicator', 'KEYBOARD', 'RIGHT') + +-- light stages +RegisterKeyMapping('MISS-ELS:toggle-stage-primary', 'Toggle primary lights', 'KEYBOARD', 'Q') +RegisterKeyMapping('MISS-ELS:toggle-stage-secondary', 'Toggle secondary lights', 'KEYBOARD', 'K') +RegisterKeyMapping('MISS-ELS:toggle-stage-warning', 'Toggle warning lights', 'KEYBOARD', 'L') + +-- sirens +RegisterKeyMapping('MISS-ELS:toggle-siren', 'Toggle sirens', 'KEYBOARD', 'LMENU') -- LEFT ALT +RegisterKeyMapping('MISS-ELS:toggle-siren-next', 'Toggle next siren', 'KEYBOARD', 'R') +RegisterKeyMapping('MISS-ELS:toggle-siren-one', 'Toggle siren 1', 'KEYBOARD', '1') +RegisterKeyMapping('MISS-ELS:toggle-siren-two', 'Toggle siren 2', 'KEYBOARD', '2') +RegisterKeyMapping('MISS-ELS:toggle-siren-three', 'Toggle siren 3', 'KEYBOARD', '3') +RegisterKeyMapping('MISS-ELS:toggle-siren-four', 'Toggle siren 4', 'KEYBOARD', '4') diff --git a/resource/client/main.lua b/resource/client/main.lua index 863195f..7e6478a 100644 --- a/resource/client/main.lua +++ b/resource/client/main.lua @@ -5,17 +5,14 @@ Indicators = { hazard = false } -local function HandleIndicators(vehicle) - -- only the driver can control the indicators - if not PedIsDriver(vehicle) then return end - - local type = nil +local function HandleIndicators(type) + if not type then return end - if IsDisabledControlJustReleased(0, Config.KeyBinds['IndicatorHazard']) and IsUsingKeyboard(0) then type = 'hazard' - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['IndicatorLeft']) and IsUsingKeyboard(0) then type = 'left' - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['IndicatorRight']) and IsUsingKeyboard(0) then type = 'right' end + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsUsing(ped) - if not type then return end + -- only the driver can control the indicators + if not vehicle or not PedIsDriver(vehicle) then return end -- disable all other indicators if type ~= 'left' and Indicators.left then Indicators.left = false @@ -30,9 +27,27 @@ local function HandleIndicators(vehicle) PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', 1) end -local function HandleHorn(vehicle) +-- indicators are allowed on all vehicles +if Config.Indicators then + RegisterCommand('MISS-ELS:toggle-indicator-hazard', function () + HandleIndicators('hazard') + end) + + RegisterCommand('MISS-ELS:toggle-indicator-left', function () + HandleIndicators('left') + end) + + RegisterCommand('MISS-ELS:toggle-indicator-right', function () + HandleIndicators('right') + end) +end + +local function HandleHorn() + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsUsing(ped) + -- only the driver can control the horn - if not PedIsDriver(vehicle) then return end + if not vehicle or not PedIsDriver(vehicle) then return end -- get the horn info from the VCF local mainHorn = kjxmlData[GetCarHash(vehicle)].sounds.mainHorn @@ -62,7 +77,10 @@ local function ToggleLights(vehicle, stage, toggle) TriggerServerEvent('kjELS:setSirenState', 0) end -local function HandleLightStage(vehicle, stage) +local function HandleLightStage(stage) + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsUsing(ped) + if kjEnabledVehicles[vehicle][stage] then -- turn lights off ToggleLights(vehicle, stage, false) @@ -77,7 +95,10 @@ local function HandleLightStage(vehicle, stage) end end -local function HandleSiren(vehicle, siren) +local function HandleSiren(siren) + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsUsing(ped) + -- siren only works in the primary light stage if not kjEnabledVehicles[vehicle].primary and not Config.SirenAlwaysAllowed then return end @@ -111,7 +132,10 @@ local function HandleSiren(vehicle, siren) end end -local function NextSiren(vehicle) +local function NextSiren() + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsUsing(ped) + -- get the next siren local next = kjEnabledVehicles[vehicle].siren + 1 @@ -137,9 +161,45 @@ local function NextSiren(vehicle) end -- turn the siren on - HandleSiren(vehicle, next) + HandleSiren(next) end +RegisterCommand('MISS-ELS:toggle-stage-primary', function () + HandleLightStage('primary') +end) + +RegisterCommand('MISS-ELS:toggle-stage-secondary', function () + HandleLightStage('secondary') +end) + +RegisterCommand('MISS-ELS:toggle-stage-warning', function () + HandleLightStage('warning') +end) + +RegisterCommand('MISS-ELS:toggle-siren', function () + HandleSiren() +end) + +RegisterCommand('MISS-ELS:toggle-siren-next', function () + NextSiren() +end) + +RegisterCommand('MISS-ELS:toggle-siren-one', function () + HandleSiren(1) +end) + +RegisterCommand('MISS-ELS:toggle-siren-two', function () + HandleSiren(2) +end) + +RegisterCommand('MISS-ELS:toggle-siren-three', function () + HandleSiren(3) +end) + +RegisterCommand('MISS-ELS:toggle-siren-four', function () + HandleSiren(4) +end) + AddEventHandler('onClientResourceStart', function(name) if not Config then CancelEvent() @@ -167,11 +227,6 @@ AddEventHandler('onClientResourceStart', function(name) local ped = PlayerPedId() local vehicle = GetVehiclePedIsUsing(ped) - if IsUsingKeyboard(0) then - -- indicators are allowed on all vehicles - if Config.Indicators then HandleIndicators(vehicle) end - end - -- only run if player is in an ELS enabled vehicle and can control the sirens if IsELSVehicle(vehicle) and CanControlSirens(vehicle) then -- conflicting controls @@ -191,11 +246,6 @@ AddEventHandler('onClientResourceStart', function(name) DisableControlAction(control[1], control[2], true) end - -- disable all ELS keybinds - for _, control in pairs(Config.KeyBinds) do - DisableControlAction(0, control, true) - end - -- set vehicle state SetVehRadioStation(vehicle, 'OFF') SetVehicleRadioEnabled(vehicle, false) @@ -205,27 +255,13 @@ AddEventHandler('onClientResourceStart', function(name) if kjEnabledVehicles[vehicle] == nil then AddVehicleToTable(vehicle) end -- handle the horn - HandleHorn(vehicle) + HandleHorn() - if IsUsingKeyboard(0) then - -- light stages - if IsDisabledControlJustReleased(0, Config.KeyBinds['PrimaryLights']) then HandleLightStage(vehicle, 'primary') - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['SecondaryLights']) then HandleLightStage(vehicle, 'secondary') - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['MiscLights']) then HandleLightStage(vehicle, 'warning') - end - - -- siren toggles - if IsDisabledControlJustReleased(0, Config.KeyBinds['ActivateSiren']) then HandleSiren(vehicle) - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['NextSiren']) then NextSiren(vehicle) - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['Siren1']) then HandleSiren(vehicle, 1) - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['Siren2']) then HandleSiren(vehicle, 2) - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['Siren3']) then HandleSiren(vehicle, 3) - elseif IsDisabledControlJustReleased(0, Config.KeyBinds['Siren4']) then HandleSiren(vehicle, 4) - end - else -- on controller - if IsDisabledControlJustReleased(1, 85 --[[ DPAD_LEFT ]]) then HandleLightStage(vehicle, 'primary') - elseif IsDisabledControlJustReleased(1, 170 --[[ B ]]) then NextSiren(vehicle) - elseif IsDisabledControlJustReleased(1, 173 --[[ DPAD_DOWN ]]) then HandleSiren(vehicle) + if not IsUsingKeyboard(0) then + -- on controller + if IsDisabledControlJustReleased(1, 85 --[[ DPAD_LEFT ]]) then HandleLightStage('primary') + elseif IsDisabledControlJustReleased(1, 170 --[[ B ]]) then NextSiren() + elseif IsDisabledControlJustReleased(1, 173 --[[ DPAD_DOWN ]]) then HandleSiren() end end end From 8688e6de02bdd66edbd0097acd9487a348aec0f6 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Sat, 23 Apr 2022 22:40:51 +0200 Subject: [PATCH 13/67] docs: remove server side keybinds --- resource/config.example.lua | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/resource/config.example.lua b/resource/config.example.lua index d7f7e9b..0440cd6 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -41,21 +41,3 @@ Config.WarningBeepDuration = 2.0 -- DPAD_DOWN = toggle siren 1 -- B = activate next siren Config.ControllerSupport = true - --- Sets keybinds for various actions --- See https://docs.fivem.net/docs/game-references/controls for a list of codes -Config.KeyBinds = { - PrimaryLights = 85, -- Q - SecondaryLights = 311, -- K - MiscLights = 182, -- L - ActivateSiren = 19, -- LEFT ALT - NextSiren = 45, -- R - Siren1 = 157, -- 1 - Siren2 = 158, -- 2 - Siren3 = 160, -- 3 - Siren4 = 164, -- 4 - IndicatorLeft = 174, -- ARROW LEFT - IndicatorRight = 175, -- ARROW RIGHT - IndicatorHazard = 173, -- ARROW DOWN - ExtrasMenu = 303, -- U -} From 6233ad738a2bb39467541354152324a8ea8e5427 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Sat, 23 Apr 2022 22:50:54 +0200 Subject: [PATCH 14/67] docs: add client side keybind feature --- README.md | 2 +- resource/config.example.lua | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 744fa2b..fe8ac9a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Oh, and you might ask yourself where all the 'Els' or 'Miss Els' jokes come from * Support for up to 4 different sirens per vehicle * Optional light reflections around the vehicle * Indicator controls -* Customizable keybinds +* Customizable keybinds (client side!) * [A simple VCF configuration wizard](https://matsn0w.github.io/MISS-ELS) *...and more!* diff --git a/resource/config.example.lua b/resource/config.example.lua index 0440cd6..ab41ce6 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -22,7 +22,6 @@ Config.AllowPassengers = false Config.SirenAlwaysAllowed = false -- Whether vehicle indicator control should be enabled --- The indicators are controlled with arrow left, right and down on your keyboard Config.Indicators = true -- Enables a short honk when a siren is activated From 9101526f39edaad7a604e874c35885d89fb96912 Mon Sep 17 00:00:00 2001 From: ImperiousTT <47085980+ImperiousTT@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:08:28 +0200 Subject: [PATCH 15/67] Update faq.md --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 7c93415..735aee2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## 1. Why don't other cars pull over? -You have to set a `sirenSetting` in a `carvariations.meta` file. This will make the game think your vehicle has a 'siren'. The ELS will trigger the emergency state when you toggle the sirens, and BOOM! They move over! +You have to set a `sirenSetting` in a `carvariations.meta` file. This will make the game think your vehicle has a 'siren'. Make shure the vehicle has the flag `FLAG_EMERGENCY_SERVICE` or `FLAG_LAW_ENFORCEMENT` in the `vehicles.meta`. The ELS will trigger the emergency state when you toggle the sirens, and BOOM! They move over! See [#2](#2-why-are-my-tail-lights-flashing) From 6d681a6a78b8d4719f829fc4b9388c20c6a971a5 Mon Sep 17 00:00:00 2001 From: ImperiousTT <47085980+ImperiousTT@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:10:03 +0200 Subject: [PATCH 16/67] Update faq.md --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 735aee2..49ffdb5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## 1. Why don't other cars pull over? -You have to set a `sirenSetting` in a `carvariations.meta` file. This will make the game think your vehicle has a 'siren'. Make shure the vehicle has the flag `FLAG_EMERGENCY_SERVICE` or `FLAG_LAW_ENFORCEMENT` in the `vehicles.meta`. The ELS will trigger the emergency state when you toggle the sirens, and BOOM! They move over! +You have to set a `sirenSetting` in a `carvariations.meta` file. This will make the game think your vehicle has a 'siren'. Make sure the vehicle has the flag `FLAG_EMERGENCY_SERVICE` or `FLAG_LAW_ENFORCEMENT` in the `vehicles.meta`. The ELS will trigger the emergency state when you toggle the sirens, and BOOM! They move over! See [#2](#2-why-are-my-tail-lights-flashing) From 1d453cbf5d7116899d549d75cbe7c0f92a798b0e Mon Sep 17 00:00:00 2001 From: ImperiousTT <47085980+ImperiousTT@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:11:13 +0200 Subject: [PATCH 17/67] Made an addition to the move-over question. --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 49ffdb5..fcbc3a2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## 1. Why don't other cars pull over? -You have to set a `sirenSetting` in a `carvariations.meta` file. This will make the game think your vehicle has a 'siren'. Make sure the vehicle has the flag `FLAG_EMERGENCY_SERVICE` or `FLAG_LAW_ENFORCEMENT` in the `vehicles.meta`. The ELS will trigger the emergency state when you toggle the sirens, and BOOM! They move over! +You have to set a `sirenSetting` in a `carvariations.meta` file. This will make the game think your vehicle has a 'siren'. Also make sure the vehicle has the flag `FLAG_EMERGENCY_SERVICE` or `FLAG_LAW_ENFORCEMENT` in the `vehicles.meta`. The ELS will trigger the emergency state when you toggle the sirens, and BOOM! They move over! See [#2](#2-why-are-my-tail-lights-flashing) From cda98ee14f51c574dd0fd471e0594a8f8b8376ec Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 13 Jun 2022 20:25:37 +0200 Subject: [PATCH 18/67] fix: disable auto repair when toggling extras --- resource/client/commands.lua | 3 +++ resource/client/extras_menu.lua | 3 +++ resource/client/lights.lua | 23 ++++++++++++++++------- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/resource/client/commands.lua b/resource/client/commands.lua index 83cad16..c142123 100644 --- a/resource/client/commands.lua +++ b/resource/client/commands.lua @@ -17,6 +17,9 @@ AddEventHandler('kjELS:toggleExtra', function(vehicle, extra) -- see if the extra is currently on or off local toggle = IsVehicleExtraTurnedOn(vehicle, extra) + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) + -- toggle the extra SetVehicleExtra(vehicle, extra, toggle) end) diff --git a/resource/client/extras_menu.lua b/resource/client/extras_menu.lua index e079a87..aa351df 100644 --- a/resource/client/extras_menu.lua +++ b/resource/client/extras_menu.lua @@ -31,6 +31,9 @@ mainMenu.OnCheckboxChange = function(sender, item, checked) return end + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) + -- toggle the extra SetVehicleExtra(vehicle, v[2], checked and 0 or 1) end diff --git a/resource/client/lights.lua b/resource/client/lights.lua index 189e45e..85a865b 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -61,30 +61,36 @@ local function SetLightStage(vehicle, stage, toggle) while ELSvehicle[stage] do -- keep the engine on whilst the lights are activated SetVehicleEngineOn(vehicle, true, true, false) - + local lastFlash = {} - + for _, flash in ipairs(VCFdata.patterns[pattern]) do if ELSvehicle[stage] then for _, extra in ipairs(flash['extras']) do + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) + -- turn the extra on SetVehicleExtra(vehicle, extra, 0) - + -- save the extra as last flashed table.insert(lastFlash, extra) end - + Citizen.Wait(flash.duration) end - + -- turn off the last flashed extras for _, v in ipairs(lastFlash) do + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) + SetVehicleExtra(vehicle, v, 1) end - + lastFlash = {} end - + Citizen.Wait(0) end @@ -110,6 +116,9 @@ AddEventHandler('kjELS:resetExtras', function(vehicle) for extra, info in pairs(kjxmlData[model].extras) do -- check if we can control this extra if info.enabled == true then + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) + -- disable the extra SetVehicleExtra(vehicle, extra, true) end From 24f7cfa0cc3742b9ef396cedc5951faf6873b069 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 13 Jun 2022 22:13:11 +0200 Subject: [PATCH 19/67] feat: only disable siren when all lights are turned off --- resource/client/lights.lua | 12 ++++++------ resource/client/main.lua | 8 ++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index 85a865b..3f6be71 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -2,8 +2,8 @@ local function SetLightStage(vehicle, stage, toggle) local ELSvehicle = kjEnabledVehicles[vehicle] local VCFdata = kjxmlData[GetCarHash(vehicle)] - -- convert the given light stage to a pattern in the VCF - local pattern = ConvertStageToPattern(stage) + -- get the pattern data + local patternData = VCFdata.patterns[ConvertStageToPattern(stage)] -- reset all extras TriggerEvent('kjELS:resetExtras', vehicle) @@ -11,12 +11,12 @@ local function SetLightStage(vehicle, stage, toggle) -- set the light state ELSvehicle[stage] = toggle - if VCFdata.patterns[pattern].isEmergency then + if patternData.isEmergency then -- toggle the native siren ('emergency mode') SetVehicleSiren(vehicle, toggle) end - if (VCFdata.patterns[pattern].flashHighBeam) then + if (patternData.flashHighBeam) then Citizen.CreateThread(function() -- get the current vehicle lights state local _, lightsOn, highbeamsOn = GetVehicleLightsState(vehicle) @@ -45,7 +45,7 @@ local function SetLightStage(vehicle, stage, toggle) end) end - if VCFdata.patterns[pattern].enableWarningBeep then + if patternData.enableWarningBeep then Citizen.CreateThread(function() while ELSvehicle[stage] do -- play warning sound @@ -64,7 +64,7 @@ local function SetLightStage(vehicle, stage, toggle) local lastFlash = {} - for _, flash in ipairs(VCFdata.patterns[pattern]) do + for _, flash in ipairs(patternData) do if ELSvehicle[stage] then for _, extra in ipairs(flash['extras']) do -- disable auto repairs diff --git a/resource/client/main.lua b/resource/client/main.lua index 7e6478a..52c34df 100644 --- a/resource/client/main.lua +++ b/resource/client/main.lua @@ -70,11 +70,15 @@ local function HandleHorn() end local function ToggleLights(vehicle, stage, toggle) + local ELSvehicle = kjEnabledVehicles[vehicle].primary + -- turn light stage on or off based on the toggle TriggerEvent('kjELS:toggleLights', vehicle, stage, toggle) - -- the siren is always off when the lights are toggled - TriggerServerEvent('kjELS:setSirenState', 0) + -- turn siren off when all lights are off + if not ELSvehicle.primary and not ELSvehicle.secondary and not ELSvehicle.warning then + TriggerServerEvent('kjELS:setSirenState', 0) + end end local function HandleLightStage(stage) From 59eb7cb3da58f10531c1eaa7bdbe8414e0864592 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 13 Jun 2022 23:41:14 +0200 Subject: [PATCH 20/67] fix: remove accidental object key --- resource/client/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/client/main.lua b/resource/client/main.lua index 52c34df..7bd9579 100644 --- a/resource/client/main.lua +++ b/resource/client/main.lua @@ -70,7 +70,7 @@ local function HandleHorn() end local function ToggleLights(vehicle, stage, toggle) - local ELSvehicle = kjEnabledVehicles[vehicle].primary + local ELSvehicle = kjEnabledVehicles[vehicle] -- turn light stage on or off based on the toggle TriggerEvent('kjELS:toggleLights', vehicle, stage, toggle) From 503927e8a98b1aad5bc8526547203eef1e198a1b Mon Sep 17 00:00:00 2001 From: matsn0w Date: Tue, 14 Jun 2022 21:47:41 +0200 Subject: [PATCH 21/67] feat: add high beams toggle to menu, refactor code --- resource/client/extras_menu.lua | 144 +++++++++++++++++++------------- resource/client/lights.lua | 16 ++-- resource/shared/funcs.lua | 3 +- 3 files changed, 97 insertions(+), 66 deletions(-) diff --git a/resource/client/extras_menu.lua b/resource/client/extras_menu.lua index aa351df..6ea65c3 100644 --- a/resource/client/extras_menu.lua +++ b/resource/client/extras_menu.lua @@ -1,96 +1,117 @@ +-- store menu entries +local menuItems = {} +local currentVehicle = nil + local function ShowNotification(text) SetNotificationTextEntry('STRING') AddTextComponentString(text) DrawNotification(false, false) end --- create a menu -MenuPool = NativeUI.CreatePool() -local mainMenu = NativeUI.CreateMenu('MISS ELS', '~b~Static extras menu') -MenuPool:Add(mainMenu) +local function ToggleMenu(menu) + -- toggle the menu visibility + menu:Visible(not menu:Visible()) +end --- disable mouse input -MenuPool:ControlDisablingEnabled(false) -MenuPool:MouseControlsEnabled(false) +local function AddHighBeamMenuEntry(menu, vehicle) + if kjEnabledVehicles[vehicle] == nil then AddVehicleToTable(vehicle) end --- store menu entries -local extras = {} -local currentVehicle = nil + local checked = kjEnabledVehicles[vehicle].highBeamEnabled + local newItem = NativeUI.CreateCheckboxItem("Flashing high beam", checked, "Enables/disables the flashing of high beams on the vehicle.") + + -- add the item to the menu + menu:AddItem(newItem) + + -- store the menu item + table.insert(menuItems, {newItem, checked, 'highBeam'}) +end + +local function AddStaticExtraEntries(menu, vehicle) + local statics = kjxmlData[GetCarHash(vehicle)].statics + + for extra, info in spairs(statics) do + local name = info.name or ('Extra ' .. extra) + local checked = menuItems[extra] or IsVehicleExtraTurnedOn(vehicle, extra) + local description = '~italic~Extra ' .. extra + + -- create the new menu item for the extra + local newitem = NativeUI.CreateCheckboxItem(name, checked, description) + + -- add the item to the menu + menu:AddItem(newitem) + + -- store the menu item + table.insert(menuItems, {newitem, extra, 'extra'}) + end +end + +-- create a menu pool +MenuPool = NativeUI.CreatePool() + +-- create a menu +local mainMenu = NativeUI.CreateMenu('MISS ELS', '~b~Vehicle Control Menu') +MenuPool:Add(mainMenu) -- listen for changes mainMenu.OnCheckboxChange = function(sender, item, checked) local ped = PlayerPedId() local vehicle = GetVehiclePedIsIn(ped) - for k, v in pairs(extras) do - local extra = v[2] + for _, v in pairs(menuItems) do + local menuItem = v[1] + local setting = v[2] + local type = v[3] - if v[1] == item then - if not DoesExtraExist(vehicle, extra) then - ShowNotification('~r~Extra ' .. extra .. ' does not exist on this vehicle!') - return - end + if menuItem == item then + if type == 'extra' then + if not DoesExtraExist(vehicle, setting) then + ShowNotification('~r~Extra ' .. setting .. ' does not exist on this vehicle!') + return + end + + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) - -- disable auto repairs - SetVehicleAutoRepairDisabled(vehicle, true) + -- toggle the extra + SetVehicleExtra(vehicle, setting, checked and 0 or 1) + end - -- toggle the extra - SetVehicleExtra(vehicle, v[2], checked and 0 or 1) + if type == 'highBeam' then + kjEnabledVehicles[vehicle].highBeamEnabled = checked + end end end end -RegisterCommand('MISS-ELS:open-statics-menu', function () - -- toggle the menu visibility - mainMenu:Visible(not mainMenu:Visible()) -end) +-- disable mouse input +MenuPool:ControlDisablingEnabled(false) +MenuPool:MouseControlsEnabled(false) Citizen.CreateThread(function() while true do - if not kjxmlData then + local function process() -- wait for the data to load while not kjxmlData do Citizen.Wait(0) end - end + while not kjEnabledVehicles do Citizen.Wait(0) end + + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsIn(ped) - local ped = PlayerPedId() - local vehicle = GetVehiclePedIsIn(ped) + -- wait untill the player is in a vehicle + while not IsPedInAnyVehicle(ped, false) do Citizen.Wait(0) end - -- wait untill the player is in a vehicle - while not IsPedInAnyVehicle(ped, false) do Citizen.Wait(0) end + -- wait untill the player is in an ELS enabled vehicle and can control the sirens + if not IsELSVehicle(vehicle) or not CanControlSirens(vehicle) then return end - if IsELSVehicle(vehicle) and CanControlSirens(vehicle) then MenuPool:ProcessMenus() if vehicle ~= currentVehicle then + -- reset the menu mainMenu:Clear() - extras = {} - - for extra, info in spairs(kjxmlData[GetCarHash(vehicle)].statics) do - local name = info.name or ('Extra ' .. extra) - local checked = extras[extra] or IsVehicleExtraTurnedOn(vehicle, extra) - local description = '~italic~Extra ' .. extra - - -- create the new menu item for the extra - local newitem = NativeUI.CreateCheckboxItem(name, checked, description) - - -- add the item to the menu - mainMenu:AddItem(newitem) - -- store the menu item - table.insert(extras, {newitem, extra}) - end - - -- check if there isn't any extra - if #extras == 0 then - local nothing = NativeUI.CreateItem('No statics available...', 'Select to close the menu') - mainMenu:AddItem(nothing) - mainMenu.OnItemSelect = function(sender, item, index) - if item == nothing then - -- close the menu - mainMenu:Visible(false) - end - end - end + -- insert menu entries + AddHighBeamMenuEntry(mainMenu, vehicle) + AddStaticExtraEntries(mainMenu, vehicle) -- store this as the current vehicle currentVehicle = vehicle @@ -99,6 +120,11 @@ Citizen.CreateThread(function() end end + process() Citizen.Wait(0) end end) + +RegisterCommand('MISS-ELS:open-statics-menu', function () + ToggleMenu(mainMenu) +end) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index 3f6be71..5a1d45d 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -26,15 +26,19 @@ local function SetLightStage(vehicle, stage, toggle) -- flash the high beam while ELSvehicle[stage] do - SetVehicleFullbeam(vehicle, true) - SetVehicleLightMultiplier(vehicle, Config.HighBeamIntensity or 5.0) + if ELSvehicle.highBeamEnabled then + SetVehicleFullbeam(vehicle, true) + SetVehicleLightMultiplier(vehicle, Config.HighBeamIntensity or 5.0) - Wait(500) + Wait(500) - SetVehicleFullbeam(vehicle, false) - SetVehicleLightMultiplier(vehicle, 1.0) + SetVehicleFullbeam(vehicle, false) + SetVehicleLightMultiplier(vehicle, 1.0) - Wait(500) + Wait(500) + end + + Wait(0) end -- reset initial vehicle state diff --git a/resource/shared/funcs.lua b/resource/shared/funcs.lua index e653330..720c306 100644 --- a/resource/shared/funcs.lua +++ b/resource/shared/funcs.lua @@ -51,7 +51,8 @@ function AddVehicleToTable(vehicle) secondary = false, warning = false, siren = 0, - sound = nil + sound = nil, + highBeamEnabled = true, } end From b50e4e7e33cc97bd0f993bb3146d288e8787b906 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Wed, 15 Jun 2022 22:15:38 +0200 Subject: [PATCH 22/67] fix: check if player can control ELS --- resource/client/main.lua | 18 ++++++++++++++++++ resource/shared/funcs.lua | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/resource/client/main.lua b/resource/client/main.lua index 7bd9579..ba22b33 100644 --- a/resource/client/main.lua +++ b/resource/client/main.lua @@ -169,38 +169,56 @@ local function NextSiren() end RegisterCommand('MISS-ELS:toggle-stage-primary', function () + if not CanControlELS() then return end + HandleLightStage('primary') end) RegisterCommand('MISS-ELS:toggle-stage-secondary', function () + if not CanControlELS() then return end + HandleLightStage('secondary') end) RegisterCommand('MISS-ELS:toggle-stage-warning', function () + if not CanControlELS() then return end + HandleLightStage('warning') end) RegisterCommand('MISS-ELS:toggle-siren', function () + if not CanControlELS() then return end + HandleSiren() end) RegisterCommand('MISS-ELS:toggle-siren-next', function () + if not CanControlELS() then return end + NextSiren() end) RegisterCommand('MISS-ELS:toggle-siren-one', function () + if not CanControlELS() then return end + HandleSiren(1) end) RegisterCommand('MISS-ELS:toggle-siren-two', function () + if not CanControlELS() then return end + HandleSiren(2) end) RegisterCommand('MISS-ELS:toggle-siren-three', function () + if not CanControlELS() then return end + HandleSiren(3) end) RegisterCommand('MISS-ELS:toggle-siren-four', function () + if not CanControlELS() then return end + HandleSiren(4) end) diff --git a/resource/shared/funcs.lua b/resource/shared/funcs.lua index 720c306..6300024 100644 --- a/resource/shared/funcs.lua +++ b/resource/shared/funcs.lua @@ -81,3 +81,24 @@ function ConvertStageToPattern(stage) return pattern end + +function CanControlELS() + if not kjxmlData then + -- wait for the data to load + while not kjxmlData do Citizen.Wait(0) end + end + + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsIn(ped) + + -- player must be in a vehicle + if not IsPedInAnyVehicle(ped, false) then return false end + + -- player must be in an ELS vehicle + if not IsELSVehicle(vehicle) then return false end + + -- player must be in a position to control the sirens + if not CanControlSirens(vehicle) then return false end + + return true +end From fec2f8624ecfbd988a4eab7c4fd889ca0ebb238a Mon Sep 17 00:00:00 2001 From: matsn0w Date: Wed, 15 Jun 2022 23:16:38 +0200 Subject: [PATCH 23/67] feat: replace nativeui with warmenu --- resource/client/extras_menu.lua | 130 -------------------------------- resource/client/menu.lua | 77 +++++++++++++++++++ resource/config.example.lua | 9 +++ resource/fxmanifest.lua | 4 +- 4 files changed, 88 insertions(+), 132 deletions(-) delete mode 100644 resource/client/extras_menu.lua create mode 100644 resource/client/menu.lua diff --git a/resource/client/extras_menu.lua b/resource/client/extras_menu.lua deleted file mode 100644 index 6ea65c3..0000000 --- a/resource/client/extras_menu.lua +++ /dev/null @@ -1,130 +0,0 @@ --- store menu entries -local menuItems = {} -local currentVehicle = nil - -local function ShowNotification(text) - SetNotificationTextEntry('STRING') - AddTextComponentString(text) - DrawNotification(false, false) -end - -local function ToggleMenu(menu) - -- toggle the menu visibility - menu:Visible(not menu:Visible()) -end - -local function AddHighBeamMenuEntry(menu, vehicle) - if kjEnabledVehicles[vehicle] == nil then AddVehicleToTable(vehicle) end - - local checked = kjEnabledVehicles[vehicle].highBeamEnabled - local newItem = NativeUI.CreateCheckboxItem("Flashing high beam", checked, "Enables/disables the flashing of high beams on the vehicle.") - - -- add the item to the menu - menu:AddItem(newItem) - - -- store the menu item - table.insert(menuItems, {newItem, checked, 'highBeam'}) -end - -local function AddStaticExtraEntries(menu, vehicle) - local statics = kjxmlData[GetCarHash(vehicle)].statics - - for extra, info in spairs(statics) do - local name = info.name or ('Extra ' .. extra) - local checked = menuItems[extra] or IsVehicleExtraTurnedOn(vehicle, extra) - local description = '~italic~Extra ' .. extra - - -- create the new menu item for the extra - local newitem = NativeUI.CreateCheckboxItem(name, checked, description) - - -- add the item to the menu - menu:AddItem(newitem) - - -- store the menu item - table.insert(menuItems, {newitem, extra, 'extra'}) - end -end - --- create a menu pool -MenuPool = NativeUI.CreatePool() - --- create a menu -local mainMenu = NativeUI.CreateMenu('MISS ELS', '~b~Vehicle Control Menu') -MenuPool:Add(mainMenu) - --- listen for changes -mainMenu.OnCheckboxChange = function(sender, item, checked) - local ped = PlayerPedId() - local vehicle = GetVehiclePedIsIn(ped) - - for _, v in pairs(menuItems) do - local menuItem = v[1] - local setting = v[2] - local type = v[3] - - if menuItem == item then - if type == 'extra' then - if not DoesExtraExist(vehicle, setting) then - ShowNotification('~r~Extra ' .. setting .. ' does not exist on this vehicle!') - return - end - - -- disable auto repairs - SetVehicleAutoRepairDisabled(vehicle, true) - - -- toggle the extra - SetVehicleExtra(vehicle, setting, checked and 0 or 1) - end - - if type == 'highBeam' then - kjEnabledVehicles[vehicle].highBeamEnabled = checked - end - end - end -end - --- disable mouse input -MenuPool:ControlDisablingEnabled(false) -MenuPool:MouseControlsEnabled(false) - -Citizen.CreateThread(function() - while true do - local function process() - -- wait for the data to load - while not kjxmlData do Citizen.Wait(0) end - while not kjEnabledVehicles do Citizen.Wait(0) end - - local ped = PlayerPedId() - local vehicle = GetVehiclePedIsIn(ped) - - -- wait untill the player is in a vehicle - while not IsPedInAnyVehicle(ped, false) do Citizen.Wait(0) end - - -- wait untill the player is in an ELS enabled vehicle and can control the sirens - if not IsELSVehicle(vehicle) or not CanControlSirens(vehicle) then return end - - MenuPool:ProcessMenus() - - if vehicle ~= currentVehicle then - -- reset the menu - mainMenu:Clear() - - -- insert menu entries - AddHighBeamMenuEntry(mainMenu, vehicle) - AddStaticExtraEntries(mainMenu, vehicle) - - -- store this as the current vehicle - currentVehicle = vehicle - - MenuPool:RefreshIndex() - end - end - - process() - Citizen.Wait(0) - end -end) - -RegisterCommand('MISS-ELS:open-statics-menu', function () - ToggleMenu(mainMenu) -end) diff --git a/resource/client/menu.lua b/resource/client/menu.lua new file mode 100644 index 0000000..d6ad771 --- /dev/null +++ b/resource/client/menu.lua @@ -0,0 +1,77 @@ +local function AddHighBeamMenuEntry(vehicle) + local checked = kjEnabledVehicles[vehicle].highBeamEnabled + + if WarMenu.CheckBox(Config.Translations.VehicleControlMenu.FlashingHighBeam, checked) then + kjEnabledVehicles[vehicle].highBeamEnabled = not checked + end +end + +local function AddStaticExtraEntries(vehicle) + local statics = kjxmlData[GetCarHash(vehicle)].statics + + for extra, info in spairs(statics) do + local name = info.name or ('Extra ' .. extra) + local checked = IsVehicleExtraTurnedOn(vehicle, extra) and true or false + local extraExists = DoesExtraExist(vehicle, extra) + + if WarMenu.CheckBox(name, checked) and extraExists then + -- disable auto repairs + SetVehicleAutoRepairDisabled(vehicle, true) + + -- toggle the extra + SetVehicleExtra(vehicle, extra, checked) + end + + if not extraExists then + if WarMenu.IsItemHovered() then + WarMenu.ToolTip(Config.Translations.VehicleControlMenu.ExtraDoesNotExist) + end + end + end +end + +local function ShowMainMenu() + Citizen.CreateThread(function () + WarMenu.OpenMenu('main') + + while true do + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsIn(ped) + + if WarMenu.Begin('main') then + AddHighBeamMenuEntry(vehicle) + AddStaticExtraEntries(vehicle) + + WarMenu.End() + else return end + + Citizen.Wait(0) + end + end) +end + +local function ToggleMainMenu() + if WarMenu.IsAnyMenuOpened() then + WarMenu.CloseMenu() + return + end + + ShowMainMenu() +end + +local style = { + backgroundColor = { 0, 0, 0, 200 }, + titleBackgroundColor = { 10, 60, 120, 200 }, + titleColor = { 255, 255, 255 }, + subTitleBackgroundColor = { 0, 0, 0, 200 }, + subTitleColor = { 88, 172, 217 }, +} + +WarMenu.CreateMenu('main', 'MISS ELS', Config.Translations.VehicleControlMenu.MenuTitle) +WarMenu.SetMenuStyle('main', style) + +RegisterCommand('MISS-ELS:open-statics-menu', function () + if not CanControlELS() then return end + + ToggleMainMenu() +end) diff --git a/resource/config.example.lua b/resource/config.example.lua index ab41ce6..7518012 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -40,3 +40,12 @@ Config.WarningBeepDuration = 2.0 -- DPAD_DOWN = toggle siren 1 -- B = activate next siren Config.ControllerSupport = true + +-- Customize various strings to your own liking +Config.Translations = { + VehicleControlMenu = { + MenuTitle = 'Vehicle Control Menu', + ExtraDoesNotExist = 'This extra does not exist on your vehicle!', + FlashingHighBeam = 'Flashing high beam', + } +} diff --git a/resource/fxmanifest.lua b/resource/fxmanifest.lua index f458e51..a575bfb 100644 --- a/resource/fxmanifest.lua +++ b/resource/fxmanifest.lua @@ -7,7 +7,7 @@ version '2.1.1' dependencies { 'baseevents', - 'NativeUI' + 'warmenu' } ui_page 'html/index.html' @@ -21,7 +21,7 @@ shared_scripts { 'shared/*.lua' } -client_script '@NativeUI/NativeUI.lua' +client_script '@warmenu/warmenu.lua' client_scripts { 'client/*.lua' From ce302157ff581f761473c8388b9248db521f5126 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 21 Aug 2023 00:51:06 +0200 Subject: [PATCH 24/67] feat: support miscs alongside extras --- resource/client/commands.lua | 39 ++++++++++++++ resource/client/lights.lua | 96 ++++++++++++++++++++++++++++----- resource/client/menu.lua | 23 ++++++-- resource/config.example.lua | 1 + resource/server/parseVCF.lua | 40 ++++++++++++-- resource/shared/funcs.lua | 101 +++++++++++++++++++++++++++++++++++ 6 files changed, 282 insertions(+), 18 deletions(-) diff --git a/resource/client/commands.lua b/resource/client/commands.lua index c142123..53880c4 100644 --- a/resource/client/commands.lua +++ b/resource/client/commands.lua @@ -6,6 +6,7 @@ AddEventHandler('kjELS:toggleExtra', function(vehicle, extra) end extra = tonumber(extra) or -1 + local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) if not DoesExtraExist(vehicle, extra) then @@ -33,3 +34,41 @@ RegisterCommand('extra', function(source, args) TriggerEvent('kjELS:toggleExtra', vehicle, extra) end) + +RegisterNetEvent('kjELS:toggleMisc') +AddEventHandler('kjELS:toggleMisc', function(vehicle, misc) + if not vehicle or not misc then + CancelEvent() + return + end + + local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) + + if not DoesMiscExist(vehicle, misc) then + print('Misc ' .. ConvertMiscIdToName(misc) .. ' does not exist on your ' .. model .. '!') + CancelEvent() + return + end + + local index = IsVehicleMiscTurnedOn(vehicle, misc) and 0 or -1 + + -- toggle the misc + SetVehicleModKit(vehicle, 0) + -- TODO: respect custom wheel setting + SetVehicleMod(vehicle, misc, index, false) +end) + +RegisterCommand('misc', function(source, args) + local ped = PlayerPedId() + if not IsPedInAnyVehicle(ped) then return end + + local vehicle = GetVehiclePedIsIn(ped) + local misc = args[1] or -1 + + if not string.match(misc, '%a') then + print('Misc must be a single letter!') + return + end + + TriggerEvent('kjELS:toggleMisc', vehicle, ConvertMiscNameToId(misc)) +end) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index 5a1d45d..e42f3e0 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -1,3 +1,16 @@ +local function ToggleExtra(vehicle, extra, toggle) + local value = toggle and 0 or 1 + + SetVehicleAutoRepairDisabled(vehicle, true) + SetVehicleExtra(vehicle, extra, value) +end + +local function ToggleMisc(vehicle, misc, toggle) + SetVehicleModKit(vehicle, 0) + -- TODO: respect custom wheel setting + SetVehicleMod(vehicle, misc, toggle, false) +end + local function SetLightStage(vehicle, stage, toggle) local ELSvehicle = kjEnabledVehicles[vehicle] local VCFdata = kjxmlData[GetCarHash(vehicle)] @@ -5,8 +18,9 @@ local function SetLightStage(vehicle, stage, toggle) -- get the pattern data local patternData = VCFdata.patterns[ConvertStageToPattern(stage)] - -- reset all extras + -- reset all extras and miscs TriggerEvent('kjELS:resetExtras', vehicle) + TriggerEvent('kjELS:resetMiscs', vehicle) -- set the light state ELSvehicle[stage] = toggle @@ -66,7 +80,10 @@ local function SetLightStage(vehicle, stage, toggle) -- keep the engine on whilst the lights are activated SetVehicleEngineOn(vehicle, true, true, false) - local lastFlash = {} + local lastFlash = { + extras = {}, + miscs = {}, + } for _, flash in ipairs(patternData) do if ELSvehicle[stage] then @@ -75,24 +92,37 @@ local function SetLightStage(vehicle, stage, toggle) SetVehicleAutoRepairDisabled(vehicle, true) -- turn the extra on - SetVehicleExtra(vehicle, extra, 0) + ToggleExtra(vehicle, extra, true) -- save the extra as last flashed - table.insert(lastFlash, extra) + table.insert(lastFlash.extras, extra) + end + + for _, misc in ipairs(flash['miscs']) do + -- turn the misc on + ToggleMisc(vehicle, misc, true) + + -- save the misc as last flashed + table.insert(lastFlash.miscs, misc) end Citizen.Wait(flash.duration) end - -- turn off the last flashed extras - for _, v in ipairs(lastFlash) do + -- turn off the last flashed lights + for _, v in ipairs(lastFlash.extras) do -- disable auto repairs SetVehicleAutoRepairDisabled(vehicle, true) - SetVehicleExtra(vehicle, v, 1) + ToggleExtra(vehicle, v, false) end - lastFlash = {} + for _, v in ipairs(lastFlash.miscs) do + ToggleMisc(vehicle, v, false) + end + + lastFlash.extras = {} + lastFlash.miscs = {} end Citizen.Wait(0) @@ -124,7 +154,31 @@ AddEventHandler('kjELS:resetExtras', function(vehicle) SetVehicleAutoRepairDisabled(vehicle, true) -- disable the extra - SetVehicleExtra(vehicle, extra, true) + ToggleExtra(vehicle, extra, false) + end + end +end) + +RegisterNetEvent('kjELS:resetMiscs') +AddEventHandler('kjELS:resetMiscs', function(vehicle) + if not vehicle then + CancelEvent() + return + end + + local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) + + if not SetContains(kjxmlData, model) then + CancelEvent() + return + end + + -- loop through all miscs + for misc, info in pairs(kjxmlData[model].miscs) do + -- check if we can control this misc + if info.enabled == true then + -- disable the misc + ToggleMisc(vehicle, misc, false) end end end) @@ -249,8 +303,9 @@ AddEventHandler('kjELS:updateIndicators', function(dir, toggle) end end) -local function CreateEnviromentLight(vehicle, extra, offset, color) - local boneIndex = GetEntityBoneIndexByName(vehicle, 'extra_' .. extra) +local function CreateEnviromentLight(vehicle, light, offset, color) + -- local boneIndex = GetEntityBoneIndexByName(vehicle, 'extra_' .. extra) + local boneIndex = GetEntityBoneIndexByName(vehicle, light.type .. '_' .. tostring(light.name)) local coords = GetWorldPositionOfEntityBone(vehicle, boneIndex) local position = coords + offset @@ -286,9 +341,26 @@ Citizen.CreateThread(function() for extra, info in pairs(data.extras) do if IsVehicleExtraTurnedOn(vehicle, extra) and info.env_light then local offset = vector3(info.env_pos.x, info.env_pos.y, info.env_pos.z) + local light = { + type = 'extra', + name = extra + } + + -- flash on walls + CreateEnviromentLight(vehicle, light, offset, info.env_color) + end + end + + for misc, info in pairs(data.miscs) do + if IsVehicleMiscTurnedOn(vehicle, misc) and info.env_light then + local offset = vector3(info.env_pos.x, info.env_pos.y, info.env_pos.z) + local light = { + type = 'misc', + name = ConvertMiscIdToName(misc) + } -- flash on walls - CreateEnviromentLight(vehicle, extra, offset, info.env_color) + CreateEnviromentLight(vehicle, light, offset, info.env_color) end end end diff --git a/resource/client/menu.lua b/resource/client/menu.lua index d6ad771..1e9c932 100644 --- a/resource/client/menu.lua +++ b/resource/client/menu.lua @@ -6,10 +6,10 @@ local function AddHighBeamMenuEntry(vehicle) end end -local function AddStaticExtraEntries(vehicle) +local function AddStaticsEntries(vehicle) local statics = kjxmlData[GetCarHash(vehicle)].statics - for extra, info in spairs(statics) do + for extra, info in spairs(statics.extras) do local name = info.name or ('Extra ' .. extra) local checked = IsVehicleExtraTurnedOn(vehicle, extra) and true or false local extraExists = DoesExtraExist(vehicle, extra) @@ -28,6 +28,23 @@ local function AddStaticExtraEntries(vehicle) end end end + + for misc, info in spairs(statics.miscs) do + local name = info.name or ('Misc ' .. ConvertMiscIdToName(misc)) + local checked = IsVehicleMiscTurnedOn(vehicle, misc) + local miscExists = DoesMiscExist(vehicle, misc) + + if WarMenu.CheckBox(name, checked) and miscExists then + -- toggle the misc + TriggerEvent('kjELS:toggleMisc', vehicle, misc) + end + + if not miscExists then + if WarMenu.IsItemHovered() then + WarMenu.ToolTip(Config.Translations.VehicleControlMenu.MiscDoesNotExist) + end + end + end end local function ShowMainMenu() @@ -40,7 +57,7 @@ local function ShowMainMenu() if WarMenu.Begin('main') then AddHighBeamMenuEntry(vehicle) - AddStaticExtraEntries(vehicle) + AddStaticsEntries(vehicle) WarMenu.End() else return end diff --git a/resource/config.example.lua b/resource/config.example.lua index 7518012..8380979 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -46,6 +46,7 @@ Config.Translations = { VehicleControlMenu = { MenuTitle = 'Vehicle Control Menu', ExtraDoesNotExist = 'This extra does not exist on your vehicle!', + MiscDoesNotExist = 'This misc does not exist on your vehicle!', FlashingHighBeam = 'Flashing high beam', } } diff --git a/resource/server/parseVCF.lua b/resource/server/parseVCF.lua index 4edec7f..b2798e2 100644 --- a/resource/server/parseVCF.lua +++ b/resource/server/parseVCF.lua @@ -8,7 +8,11 @@ function ParseVCF(xml, fileName) vcf.patterns.secondary = {} vcf.patterns.rearreds = {} vcf.extras = {} - vcf.statics = {} + vcf.miscs = {} + vcf.statics = { + extras = {}, + miscs = {}, + } vcf.sounds = {} for i = 1, #xml.root.el do @@ -46,6 +50,25 @@ function ParseVCF(xml, fileName) vcf.extras[extra].env_pos['z'] = tonumber(elem.attr['OffsetZ'] or 0.0) vcf.extras[extra].env_color = string.upper(elem.attr['Color'] or 'RED') end + elseif string.find(elem.name, 'Misc', 1) then + local misc = ConvertMiscNameToId(string.sub(elem.name, -1)) + + vcf.miscs[misc] = {} + vcf.miscs[misc].enabled = elem.attr['IsElsControlled'] == 'true' + vcf.miscs[misc].env_light = false + vcf.miscs[misc].env_pos = {} + vcf.miscs[misc].env_pos['x'] = 0 + vcf.miscs[misc].env_pos['y'] = 0 + vcf.miscs[misc].env_pos['z'] = 0 + vcf.miscs[misc].env_color = 'RED' + + if elem.attr['AllowEnvLight'] == 'true' then + vcf.miscs[misc].env_light = true + vcf.miscs[misc].env_pos['x'] = tonumber(elem.attr['OffsetX'] or 0.0) + vcf.miscs[misc].env_pos['y'] = tonumber(elem.attr['OffsetY'] or 0.0) + vcf.miscs[misc].env_pos['z'] = tonumber(elem.attr['OffsetZ'] or 0.0) + vcf.miscs[misc].env_color = string.upper(elem.attr['Color'] or 'RED') + end end end end @@ -58,9 +81,14 @@ function ParseVCF(xml, fileName) local extra = tonumber(string.sub(elem.name, -2)) if extra then - vcf.statics[extra] = {} - vcf.statics[extra].name = elem.attr['Name'] + vcf.statics.extras[extra] = {} + vcf.statics.extras[extra].name = elem.attr['Name'] end + elseif string.upper(string.sub(elem.name, 1, -2)) == 'MISC' then + local misc = ConvertMiscNameToId(string.sub(elem.name, -1)) + + vcf.statics.miscs[misc] = {} + vcf.statics.miscs[misc].name = elem.attr['Name'] end end end @@ -129,6 +157,7 @@ function ParseVCF(xml, fileName) if tag == 'FLASH' then vcf.patterns[type][id] = {} vcf.patterns[type][id].extras = {} + vcf.patterns[type][id].miscs = {} vcf.patterns[type][id].duration = tonumber(flash.attr['Duration'] or '100') for extra in string.gmatch(flash.attr['Extras'] or '', '([0-9]+)') do @@ -139,6 +168,11 @@ function ParseVCF(xml, fileName) table.insert(vcf.patterns[type][id].extras, tonumber(extra)) end + for misc in string.gmatch(flash.attr['Miscs'] or '', '([a-z]+)') do + -- insert misc in the table + table.insert(vcf.patterns[type][id].miscs, ConvertMiscNameToId(misc)) + end + id = id + 1 end end diff --git a/resource/shared/funcs.lua b/resource/shared/funcs.lua index 6300024..e3b22f3 100644 --- a/resource/shared/funcs.lua +++ b/resource/shared/funcs.lua @@ -102,3 +102,104 @@ function CanControlELS() return true end + +-- source: https://docs.fivem.net/natives/?_0x6AF0636DDEDCB6DD +local VMT = { + SPOILER = 0, + BUMPER_F = 1, + BUMPER_R = 2, + SKIRT = 3, + EXHAUST = 4, + CHASSIS = 5, + GRILL = 6, + BONNET = 7, + WING_L = 8, + WING_R = 9, + ROOF = 10, + ENGINE = 11, + BRAKES = 12, + GEARBOX = 13, + HORN = 14, + SUSPENSION = 15, + ARMOUR = 16, + NITROUS = 17, + TURBO = 18, + SUBWOOFER = 19, + TYRE_SMOKE = 20, + HYDRAULICS = 21, + XENON_LIGHTS = 22, + WHEELS = 23, + WHEELS_REAR_OR_HYDRAULICS = 24, + PLTHOLDER = 25, + PLTVANITY = 26, + INTERIOR1 = 27, + INTERIOR2 = 28, + INTERIOR3 = 29, + INTERIOR4 = 30, + INTERIOR5 = 31, + SEATS = 32, + STEERING = 33, + KNOB = 34, + PLAQUE = 35, + ICE = 36, + TRUNK = 37, + HYDRO = 38, + ENGINEBAY1 = 39, + ENGINEBAY2 = 40, + ENGINEBAY3 = 41, + CHASSIS2 = 42, + CHASSIS3 = 43, + CHASSIS4 = 44, + CHASSIS5 = 45, + DOOR_L = 46, + DOOR_R = 47, + LIVERY_MOD = 48, + LIGHTBAR = 49, +} + +local miscs = { + A = VMT.SPOILER, + B = VMT.BUMPER_F, + C = VMT.BUMPER_R, + D = VMT.SKIRT, + E = VMT.EXHAUST, + F = VMT.CHASSIS, + G = VMT.GRILL, + H = VMT.BONNET, + I = VMT.WING_L, + J = VMT.WING_R, + K = VMT.ROOF, + L = VMT.PLTHOLDER, + M = VMT.PLTVANITY, + N = VMT.INTERIOR1, + O = VMT.INTERIOR2, + P = VMT.INTERIOR3, + Q = VMT.INTERIOR4, + R = VMT.INTERIOR5, + S = VMT.SEATS, + T = VMT.STEERING, + U = VMT.KNOB, + V = VMT.PLAQUE, + W = VMT.ICE, + X = VMT.TRUNK, + Y = VMT.HYDRO, + Z = VMT.ENGINEBAY1, +} + +function ConvertMiscNameToId(misc) + return miscs[string.upper(misc)] +end + +function ConvertMiscIdToName(misc) + for k, v in pairs(miscs) do + if v == misc then return string.lower(k) end + end +end + +function IsVehicleMiscTurnedOn(vehicle, misc) + return GetVehicleMod(vehicle, misc) == -1 +end + +function DoesMiscExist(vehicle, misc) + return GetNumVehicleMods(vehicle, misc) > 0 +end From 9bf329c9e77e159a6553e48c2a027b1326f62388 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 21 Aug 2023 01:17:25 +0200 Subject: [PATCH 25/67] docs: add references --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe8ac9a..4b1f2f9 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ Oh, and you might ask yourself where all the 'Els' or 'Miss Els' jokes come from ## Key Features * Sirens and lights synced across the entire server -* Customizable light patterns per vehicle +* Fully customizable light patterns per vehicle * Use native game sirens or use your own with [WMServerSirens](https://github.com/Walsheyy/WMServerSirens) * A simple yet comprehensive configuration * Support for 3 different light stages * Support for up to 4 different sirens per vehicle +* Support for up to 36 independent light sources (12 extras + 26 miscs) * Optional light reflections around the vehicle * Indicator controls * Customizable keybinds (client side!) @@ -36,7 +37,7 @@ See the instructions in the [documentation](docs/README.md)! ## Vehicle Configuration Files -If you have any existing VCF files from the original author of this resource, then you'll probably be fine. The XML should be fully backwards compatible. If you have a VCF from any other resource, that'll most likely not work. +If you have any existing VCF files from the [original author](https://github.com/InfImpSolutions) of this resource, then you'll probably be fine. The XML should be fully backwards compatible. If you have a VCF from any other resource, that'll most likely not work. In both cases, it's probably best to re-create them by either writing them yourself OR... use my convient configuration GUI! Using this tool, you can easily generate configuration files for your vehicles. You can find it here: @@ -46,7 +47,7 @@ In both cases, it's probably best to re-create them by either writing them yours Please read the [documentation](docs/README.md) first and check the [Q&A section](https://github.com/matsn0w/IIS-EmergencyLS-ELS-FiveM/discussions/categories/q-a) on Github if your question has already been asked. It probably is! -Didn't find and answer? Then feel free to [start a new topic](https://github.com/matsn0w/IIS-EmergencyLS-ELS-FiveM/discussions/new?category=q-a). The community is here to help you! +Didn't find and answer? Then feel free to [start a new topic](https://github.com/matsn0w/IIS-EmergencyLS-ELS-FiveM/discussions/new?category=q-a). The community is here to help you! You can also join the official [MISS ELS Discord server](https://matsn0w.dev/discord). Found a bug? Please [make an issue](https://github.com/matsn0w/MISS-ELS/issues/new)! From fd17de02f7511284ed5b19b6e3180ebee3bed180 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 21 Aug 2023 16:13:30 +0200 Subject: [PATCH 26/67] feat: upgrade configurator to Nuxt 3 --- configurator/.editorconfig | 13 - configurator/.eslintrc.js | 19 - configurator/.gitignore | 100 +- configurator/.npmrc | 2 + configurator/README.md | 80 +- configurator/app.vue | 5 + configurator/assets/css/main.scss | 82 +- configurator/components/extras.vue | 28 +- configurator/components/importButton.vue | 23 +- configurator/components/patterns.vue | 131 +- configurator/components/sounds.vue | 78 +- configurator/components/statics.vue | 53 +- configurator/composables/vcfConfiguration.js | 146 + configurator/helpers/transformImportedVCF.js | 94 - configurator/helpers/xmlGenerator.js | 108 - configurator/jsconfig.json | 12 - configurator/layouts/default.vue | 5 + configurator/nuxt.config.js | 56 - configurator/nuxt.config.ts | 16 + configurator/package-lock.json | 39818 +++++------------ configurator/package.json | 38 +- configurator/pages/index.vue | 135 +- configurator/public/favicon.ico | Bin 0 -> 4286 bytes configurator/server/tsconfig.json | 3 + configurator/static/favicon.ico | Bin 8636 -> 0 bytes configurator/store/README.md | 10 - configurator/store/index.js | 94 - configurator/tailwind.config.js | 18 +- configurator/tsconfig.json | 4 + configurator/utils/transformImportedVCF.js | 96 + configurator/utils/xmlGenerator.js | 110 + 31 files changed, 10905 insertions(+), 30472 deletions(-) delete mode 100644 configurator/.editorconfig delete mode 100644 configurator/.eslintrc.js create mode 100644 configurator/.npmrc create mode 100644 configurator/app.vue create mode 100644 configurator/composables/vcfConfiguration.js delete mode 100644 configurator/helpers/transformImportedVCF.js delete mode 100644 configurator/helpers/xmlGenerator.js delete mode 100644 configurator/jsconfig.json create mode 100644 configurator/layouts/default.vue delete mode 100644 configurator/nuxt.config.js create mode 100644 configurator/nuxt.config.ts create mode 100644 configurator/public/favicon.ico create mode 100644 configurator/server/tsconfig.json delete mode 100644 configurator/static/favicon.ico delete mode 100644 configurator/store/README.md delete mode 100644 configurator/store/index.js create mode 100644 configurator/tsconfig.json create mode 100644 configurator/utils/transformImportedVCF.js create mode 100644 configurator/utils/xmlGenerator.js diff --git a/configurator/.editorconfig b/configurator/.editorconfig deleted file mode 100644 index 5d12634..0000000 --- a/configurator/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/configurator/.eslintrc.js b/configurator/.eslintrc.js deleted file mode 100644 index 9e383e1..0000000 --- a/configurator/.eslintrc.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - node: true - }, - parserOptions: { - parser: '@babel/eslint-parser', - requireConfigFile: false - }, - extends: [ - '@nuxtjs', - 'plugin:nuxt/recommended' - ], - plugins: [ - ], - // add your custom rules here - rules: {} -} diff --git a/configurator/.gitignore b/configurator/.gitignore index e8f682b..4a7f73a 100644 --- a/configurator/.gitignore +++ b/configurator/.gitignore @@ -1,90 +1,24 @@ -# Created by .ignore support plugin (hsz.mobi) -### Node template -# Logs -/logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output +# Nuxt dev/build outputs +.output +.data .nuxt - -# Nuxt generate +.nitro +.cache dist -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless - -# IDE / Editor -.idea +# Node dependencies +node_modules -# Service worker -sw.* +# Logs +logs +*.log -# macOS +# Misc .DS_Store +.fleet +.idea -# Vim swap files -*.swp +# Local env files +.env +.env.* +!.env.example diff --git a/configurator/.npmrc b/configurator/.npmrc new file mode 100644 index 0000000..cf04042 --- /dev/null +++ b/configurator/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false diff --git a/configurator/README.md b/configurator/README.md index 1334515..0496ac8 100644 --- a/configurator/README.md +++ b/configurator/README.md @@ -1,69 +1,39 @@ -# miss-els-vcf-configurator +# MISS ELS VCF Configurator -## Build Setup +Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. -```bash -# install dependencies -$ npm install - -# serve with hot reload at localhost:3000 -$ npm run dev +## Setup -# build for production and launch server -$ npm run build -$ npm run start +Make sure to install the dependencies: -# generate static project -$ npm run generate +```bash +# npm +npm install ``` -For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org). - -## Special Directories - -You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality. - -### `assets` - -The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets). - -### `components` - -The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components). - -### `layouts` - -Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop. +## Development Server -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts). +Start the development server on `http://localhost:3000`: +```bash +# npm +npm run dev +``` -### `pages` - -This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing). - -### `plugins` - -The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins). - -### `static` - -This directory contains your static files. Each file inside this directory is mapped to `/`. +## Production -Example: `/static/robots.txt` is mapped as `/robots.txt`. +Build the application for production: -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static). +```bash +# npm +npm run build +``` -### `store` +Locally preview production build: -This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex. +```bash +# npm +npm run preview +``` -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store). +Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/configurator/app.vue b/configurator/app.vue new file mode 100644 index 0000000..f8eacfa --- /dev/null +++ b/configurator/app.vue @@ -0,0 +1,5 @@ + diff --git a/configurator/assets/css/main.scss b/configurator/assets/css/main.scss index 84ab727..eccfed8 100644 --- a/configurator/assets/css/main.scss +++ b/configurator/assets/css/main.scss @@ -7,67 +7,56 @@ body { } .card { - @apply px-3 py-1 my-6 bg-white rounded-md shadow-lg; -} + @apply p-2 my-6 bg-white rounded-md shadow-lg; -.card h2 { - @apply font-semibold text-gray-800; + h2 { + @apply font-semibold text-gray-800; + } } button { - @apply px-2 py-1 bg-blue-500 text-white rounded-md shadow; + @apply px-2 py-1 rounded-md shadow; } -thead { - @apply font-semibold uppercase text-gray-400 bg-gray-50; -} +table { + thead { + @apply font-semibold uppercase text-gray-400 bg-gray-50; + } -tbody { - @apply divide-y divide-gray-100; -} + tbody { + @apply divide-y divide-gray-100; + } -th, td { - @apply p-2 align-middle; -} + th, + td { + @apply p-2 align-middle; + } -th { - @apply font-semibold text-left; + th { + @apply font-semibold text-left; + } } label { @apply block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4; } -input[type=text], input[type=number] { - @apply bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full p-2 text-gray-700 leading-tight align-middle; -} - -input[type=text]:focus, input[type=number]:focus { - @apply focus:outline-none focus:bg-white focus:border-blue-500; +input[type="text"], +input[type="number"], +select { + @apply form-input block w-full px-2 py-1 rounded bg-gray-200 text-gray-700 border-2 border-gray-200 focus:bg-white focus:border-blue-500 focus:outline-none; } select { - @apply - form-select appearance-none - block w-full - px-3 py-1.5 - text-base font-normal text-gray-700 - bg-white bg-clip-padding bg-no-repeat - border border-solid border-gray-300 rounded - transition ease-in-out - m-0 - focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none; -} - -.form-select { - -moz-padding-start: calc(.75rem - 3px); + @extend select; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); - background-position: right .75rem center; + background-position: right 0.75rem center; background-size: 16px 12px; + background-repeat: no-repeat; } -input[type='checkbox'] { - @apply form-checkbox h-5 w-5 mr-2 text-blue-500 rounded; +input[type="checkbox"] { + @apply form-checkbox h-5 w-5 text-blue-500 rounded; } .cb-label { @@ -108,11 +97,12 @@ input[type='checkbox'] { border-radius: 5px; padding: 8px 12px; cursor: pointer; -} -.import-button input { - position: absolute; - top: 0; - left: 0; - z-index: -1; - opacity: 0; + + input { + position: absolute; + top: 0; + left: 0; + z-index: -1; + opacity: 0; + } } diff --git a/configurator/components/extras.vue b/configurator/components/extras.vue index 9f56fcb..6a268a9 100644 --- a/configurator/components/extras.vue +++ b/configurator/components/extras.vue @@ -16,26 +16,22 @@ - - - Extra {{ extra.id }} - + + Extra {{ extra.id }}
+ Is emergency
- + @@ -41,21 +58,30 @@ - + @@ -67,50 +93,43 @@ - diff --git a/configurator/components/lights.vue b/configurator/components/lights.vue new file mode 100644 index 0000000..bcdd3a3 --- /dev/null +++ b/configurator/components/lights.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index d770fe6..aca50ea 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -53,6 +53,7 @@ + @@ -66,16 +67,26 @@ {{ extra.id }} + + + + + + + + + + @@ -105,14 +147,16 @@ diff --git a/configurator/components/statics.vue b/configurator/components/statics.vue index 05ab3d9..17b9225 100644 --- a/configurator/components/statics.vue +++ b/configurator/components/statics.vue @@ -10,7 +10,8 @@
Duration Extras
- + {{ extra.id }} + >{{ extra.id }} -
Duration ExtrasMiscs
+ {{ misc.id }} +
+ Preview + + {{ extra?.id }} + + {{ misc.id }} + + + +
+ +
@@ -93,7 +135,7 @@ class="red" @click="removeFlash(pattern, flash)" > - × +
- + + @@ -19,16 +20,21 @@ - diff --git a/configurator/composables/vcfConfiguration.js b/configurator/composables/vcfConfiguration.ts similarity index 53% rename from configurator/composables/vcfConfiguration.js rename to configurator/composables/vcfConfiguration.ts index ad34222..607d498 100644 --- a/configurator/composables/vcfConfiguration.js +++ b/configurator/composables/vcfConfiguration.ts @@ -1,4 +1,11 @@ -export const useVcfConfiguration = () => { +import {letterLightableId, Lightable, numericalLightableId} from "~/types/lights"; +import {vcfConfig} from "~/types/vcfConfig"; +import {flashType} from "~/types/flash"; +import {staticType} from "~/types/static"; +import {patternType} from "~/types/patterns"; +import {Ref} from "vue"; + +export const useVcfConfiguration = (): Ref => { return useState("vcfConfiguration", () => ({ flashID: 1, configuration: { @@ -14,7 +21,10 @@ export const useVcfConfiguration = () => { audioString: "SIRENS_AIRHORN", soundSet: null, }, - { name: "NineMode", allowUse: true }, + { + name: "NineMode", + allowUse: false, + }, { name: "SrnTone1", allowUse: true, @@ -46,7 +56,7 @@ export const useVcfConfiguration = () => { isEmergency: true, flashHighBeam: false, enableWarningBeep: false, - loopPreview: true, + loopPreview: false, }, { name: "SECONDARY", @@ -65,37 +75,44 @@ export const useVcfConfiguration = () => { ], flashes: [], }, - })); + } as vcfConfig)); }; -const getFlashIndex = (flash) => { +const getFlashIndex = (flash: flashType) => { const VCF = useVcfConfiguration(); return VCF.value.configuration.flashes.map((f) => f.id).indexOf(flash.id); }; -export const useAddStatic = (value) => { +export const miscIds: letterLightableId[] = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]; +export const extraIds: numericalLightableId[] = [1,2,3,4,5,6,7,8,9,10,11,12] + +export const isLightableIdInUse = (id: letterLightableId|numericalLightableId) => useVcfConfiguration().value.configuration.lightables.map((lightable: Lightable) => lightable.id).includes(id) +export const availableMiscIds = computed(() => miscIds.filter(miscId => !isLightableIdInUse(miscId))) +export const availableExtraIds = computed(() => extraIds.filter(extraId => !isLightableIdInUse(extraId))) + +export const useAddStatic = (value: staticType) => { const VCF = useVcfConfiguration(); VCF.value.configuration.statics.push(value); }; -export const useRemoveStatic = (value) => { +export const useRemoveStatic = (value: staticType) => { const VCF = useVcfConfiguration(); const index = VCF.value.configuration.statics - .map((item) => item.extra) - .indexOf(value.extra); + .map((item) => item.id) + .indexOf(value.id); VCF.value.configuration.statics.splice(index, 1); }; -export const useAddFlash = (value) => { +export const useAddFlash = (pattern: patternType) => { const VCF = useVcfConfiguration(); - const flash = { + const flash: flashType = { id: VCF.value.flashID++, - pattern: value.pattern.name, + pattern: pattern.name, duration: 100, extras: [], miscs: [], @@ -104,43 +121,42 @@ export const useAddFlash = (value) => { VCF.value.configuration.flashes.push(flash); }; -export const useRemoveFlash = (value) => { +export const useRemoveFlash = (pattern: patternType, flash: flashType) => { const VCF = useVcfConfiguration(); - const flashIndex = getFlashIndex(value.flash); + const flashIndex = getFlashIndex(flash); if (flashIndex !== -1) { VCF.value.configuration.flashes.splice(flashIndex, 1); } }; -export const useToggleLight = (value) => { +export const useToggleLight = (pattern: patternType, flash: flashType, lightable: Lightable) => { const VCF = useVcfConfiguration(); - const flashIndex = getFlashIndex(value.flash); + const flashIndex: number = getFlashIndex(flash); const extras = VCF.value.configuration.flashes[flashIndex].extras; const miscs = VCF.value.configuration.flashes[flashIndex].miscs; - if (!isNaN(value.light.id)) { - if (extras.includes(value.light.id)) { - extras.splice(extras.indexOf(value.light.id), 1); + if (typeof lightable.id === "number") { + if (extras.includes(lightable.id)) { + extras.splice(extras.indexOf(lightable.id), 1); } else { - extras.push(value.light.id); + extras.push(lightable.id); } } else { - if (miscs.includes(value.light.id)) { - miscs.splice(miscs.indexOf(value.light.id), 1); - } else { - miscs.push(value.light.id); + if (miscs.includes(lightable.id)) { + miscs.splice(miscs.indexOf(lightable.id), 1); + } else if (typeof lightable.id === "string") { + miscs.push(lightable.id); } } }; -export const useImportExistingConfiguration = (value) => { +export const useImportExistingConfiguration = (value: vcfConfig) => { const VCF = useVcfConfiguration(); + console.log(value) VCF.value.flashID = value.flashID; - delete value.flashID; - - VCF.value.configuration = value; + VCF.value.configuration = value.configuration; }; diff --git a/configurator/types/flash.ts b/configurator/types/flash.ts new file mode 100644 index 0000000..cce3bf6 --- /dev/null +++ b/configurator/types/flash.ts @@ -0,0 +1,10 @@ +import {patternType} from "~/types/patterns"; +import {letterLightableId, numericalLightableId} from "~/types/lights"; + +export type flashType = { + id: number + pattern: patternType["name"] + duration: number + extras: numericalLightableId[] + miscs: letterLightableId[] +} \ No newline at end of file diff --git a/configurator/types/patterns.ts b/configurator/types/patterns.ts new file mode 100644 index 0000000..6fa8957 --- /dev/null +++ b/configurator/types/patterns.ts @@ -0,0 +1,7 @@ +export type patternType = { + name: string + isEmergency: boolean + flashHighBeam: boolean, + enableWarningBeep: boolean + loopPreview: boolean +} \ No newline at end of file diff --git a/configurator/types/sounds.ts b/configurator/types/sounds.ts new file mode 100644 index 0000000..dc6ae5f --- /dev/null +++ b/configurator/types/sounds.ts @@ -0,0 +1,6 @@ +export type soundType = { + name: string + allowUse: boolean + audioString?: string|null + soundSet?: string|null +} \ No newline at end of file diff --git a/configurator/types/static.ts b/configurator/types/static.ts new file mode 100644 index 0000000..8658454 --- /dev/null +++ b/configurator/types/static.ts @@ -0,0 +1,7 @@ +import {letterLightableId, lightableType, numericalLightableId} from "~/types/lights"; + +export type staticType = { + id: numericalLightableId|letterLightableId|null, + type: lightableType|null + name: string|null +} \ No newline at end of file diff --git a/configurator/types/vcfConfig.ts b/configurator/types/vcfConfig.ts new file mode 100644 index 0000000..031544a --- /dev/null +++ b/configurator/types/vcfConfig.ts @@ -0,0 +1,19 @@ +import {Lightable, lightableType} from "~/types/lights"; +import {staticType} from "~/types/static"; +import {soundType} from "~/types/sounds"; +import {patternType} from "~/types/patterns"; +import {flashType} from "~/types/flash"; + +export type vcfConfig = { + flashID: number + configuration: { + author: string|null + description: string|null + lightables: Lightable[] + statics: staticType[] + useServerSirens: boolean + sounds: soundType[] + patterns: patternType[] + flashes: flashType[] + } +} \ No newline at end of file diff --git a/configurator/utils/transformImportedVCF.js b/configurator/utils/transformImportedVCF.ts similarity index 70% rename from configurator/utils/transformImportedVCF.js rename to configurator/utils/transformImportedVCF.ts index 548d796..1f6c942 100644 --- a/configurator/utils/transformImportedVCF.js +++ b/configurator/utils/transformImportedVCF.ts @@ -1,32 +1,34 @@ +import {vcfConfig} from "~/types/vcfConfig"; +import {letterLightableId, numericalLightableId} from "~/types/lights"; + /** * This method takes in an existing VCF file (as XML string), parses the XML * and maps it to an expected format for the VueX store. * */ export const generateStoreAttributesFromExistingVCF = (data) => { - const vcf = { + const vcf: vcfConfig = { flashID: 1, - author: null, - description: null, - lightables: [], - statics: { - extras: [], - miscs: [], - }, - useServerSirens: false, - sounds: [], - patterns: [], - flashes: [], + configuration: { + author: null, + description: null, + lightables: [], + statics: [], + useServerSirens: false, + sounds: [], + patterns: [], + flashes: [], + } }; // parse the XML string const parsedVCF = new DOMParser().parseFromString(data, "text/xml"); // get basic document info - vcf.description = parsedVCF + vcf.configuration.description = parsedVCF .querySelector("vcfroot") - .getAttribute("Description"); - vcf.author = parsedVCF.querySelector("vcfroot").getAttribute("Author"); + .getAttribute("Description") ?? null; + vcf.configuration.author = parsedVCF.querySelector("vcfroot").getAttribute("Author"); // EOVERRIDE section const eoverride = parsedVCF.querySelector("EOVERRIDE"); @@ -41,7 +43,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { }); extras.forEach((extra) => { - vcf.lightables.push({ + vcf.configuration.lightables.push({ id: parseInt(extra.nodeName.match(/([0-9]|[1-9][0-9])$/g)[0]), type: 'extra', enabled: extra.getAttribute("IsElsControlled") === "true", @@ -51,7 +53,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { }); miscs.forEach((misc) => { - vcf.lightables.push({ + vcf.configuration.lightables.push({ id: misc.nodeName.match(/([A-Z])$/g)[0], type: 'misc', enabled: misc.getAttribute("IsElsControlled") === "true", @@ -73,15 +75,17 @@ export const generateStoreAttributesFromExistingVCF = (data) => { }); staticExtras.forEach((staticExtra) => { - vcf.statics.extras.push({ - extra: parseInt(staticExtra.nodeName.match(/([0-9]|[1-9][0-9])$/g)[0]), + vcf.configuration.statics.push({ + id: parseInt(staticExtra.nodeName.match(/([0-9]|[1-9][0-9])$/g)[0]) as numericalLightableId, + type: 'extra', name: staticExtra.getAttribute("Name") ?? staticExtra.nodeName, }); }); staticMiscs.forEach((staticMisc) => { - vcf.statics.miscs.push({ - misc: staticMisc.nodeName.match(/([A-Z])$/g)[0], + vcf.configuration.statics.push({ + id: staticMisc.nodeName.match(/([A-Z])$/g)[0].toLowerCase() as letterLightableId, + type: 'misc', name: staticMisc.getAttribute("Name") ?? staticMisc.nodeName, }); }); @@ -91,7 +95,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { const sounds = soundsObject.querySelectorAll("*"); sounds.forEach((sound) => { - vcf.sounds.push({ + vcf.configuration.sounds.push({ name: sound.nodeName, allowUse: sound.getAttribute("AllowUse") === "true", audioString: sound.getAttribute("AudioString") ?? null, @@ -102,7 +106,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { // determine whether a SoundSet is present sounds.forEach((sound) => { if (sound.getAttribute("SoundSet") !== null) { - vcf.useServerSirens = true; + vcf.configuration.useServerSirens = true; } }); @@ -110,21 +114,22 @@ export const generateStoreAttributesFromExistingVCF = (data) => { const patternsObject = parsedVCF.querySelector("PATTERN"); for (const pattern of patternsObject.children) { - vcf.patterns.push({ + vcf.configuration.patterns.push({ name: pattern.nodeName, isEmergency: pattern.getAttribute("IsEmergency") === "true", flashHighBeam: pattern.getAttribute("FlashHighBeam") === "true", enableWarningBeep: pattern.getAttribute("EnableWarningBeep") === "true", + loopPreview: false }); for (const flash of pattern.children) { const enabledExtras = flash.getAttribute("Extras")?.split(",") ?? []; - const enabledMiscs = flash.getAttribute("Miscs")?.split(",") ?? []; + const enabledMiscs: letterLightableId[] = flash.getAttribute("Miscs")?.split(",") as letterLightableId[] ?? []; - vcf.flashes.push({ + vcf.configuration.flashes.push({ id: vcf.flashID++, duration: parseInt(flash.getAttribute("Duration")) ?? 100, - extras: enabledExtras.map((extra) => parseInt(extra)) ?? [], + extras: enabledExtras.map((extra) => parseInt(extra) as numericalLightableId) ?? [], miscs: enabledMiscs ?? [], pattern: pattern.nodeName, }); diff --git a/configurator/utils/xmlGenerator.js b/configurator/utils/xmlGenerator.js index 3f9281e..0dbdc30 100644 --- a/configurator/utils/xmlGenerator.js +++ b/configurator/utils/xmlGenerator.js @@ -62,8 +62,15 @@ export const generateVcfDocument = (data) => { const statics = doc.createElement("STATIC"); data.statics.forEach((stat) => { - const s = doc.createElement(`Extra${stat.extra}`); - s.setAttribute("Name", stat.name ?? `Extra ${stat.extra}`); + let nodeName = '' + if (s.type === 'extra') { + nodeName = `EXTRA${stat.id}` + } else if (stat.type === 'misc') { + nodeName = `MISC${String(stat.id).toUpperCase()}` + } + + const s = doc.createElement(nodeName); + s.setAttribute("Name", stat.name ?? nodeName); statics.appendChild(s); }); From 7a162390be2dd155a0512477b5166563426f4c64 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Fri, 8 Sep 2023 22:47:27 +0200 Subject: [PATCH 39/67] make button bigger --- configurator/pages/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configurator/pages/index.vue b/configurator/pages/index.vue index 6213cf1..d6fa4f4 100644 --- a/configurator/pages/index.vue +++ b/configurator/pages/index.vue @@ -37,7 +37,7 @@
- +
From d05242af74624a76fb0db36333d1847d290672b3 Mon Sep 17 00:00:00 2001 From: Justin Rijsdijk Date: Mon, 11 Sep 2023 19:14:50 +0200 Subject: [PATCH 40/67] small fixes for import/export. Leave the border around the loopPreview button --- configurator/components/patterns.vue | 15 +++++++++++---- configurator/composables/vcfConfiguration.ts | 2 ++ configurator/utils/transformImportedVCF.ts | 2 +- configurator/utils/xmlGenerator.js | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index 08ffe30..f2b77a8 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -93,7 +93,7 @@ + +
- @@ -36,11 +35,6 @@ -
ExtraLightable typeLightable id Name
Lightable type Lightable IDIs ELS controlled Allow env light Color - - @@ -62,13 +62,16 @@ + + \ No newline at end of file diff --git a/configurator/components/sounds.vue b/configurator/components/sounds.vue index 26f2f44..11ec6b7 100644 --- a/configurator/components/sounds.vue +++ b/configurator/components/sounds.vue @@ -12,10 +12,10 @@ /> Use  WMServerSirensWMServerSirens @@ -67,5 +67,58 @@ diff --git a/configurator/composables/vcfConfiguration.ts b/configurator/composables/vcfConfiguration.ts index 221c17f..cd0350f 100644 --- a/configurator/composables/vcfConfiguration.ts +++ b/configurator/composables/vcfConfiguration.ts @@ -6,7 +6,7 @@ import {patternType} from "~/types/patterns"; import {Ref} from "vue"; import {DateTime} from "luxon"; -const defaultVcfConfig: vcfConfig = { +export const defaultVcfConfig: vcfConfig = { flashID: 1, configuration: { author: null, diff --git a/configurator/pages/index.vue b/configurator/pages/index.vue index de7cbf4..72ce318 100644 --- a/configurator/pages/index.vue +++ b/configurator/pages/index.vue @@ -11,22 +11,10 @@ - + /> @@ -46,23 +34,11 @@
- + :isForNotice="true" + />