├── .github ├── CONTRIBUTING.md ├── FUNDING.yml └── workflows │ └── build-ui.yml ├── .gitignore ├── LICENSE.md ├── README.md └── ulc ├── client ├── c_beeps.lua ├── c_blackout.lua ├── c_brake.lua ├── c_buttons.lua ├── c_cruise.lua ├── c_doors.lua ├── c_horn.lua ├── c_hud.lua ├── c_lvc.lua ├── c_main.lua ├── c_park.lua ├── c_reverse.lua ├── c_signals.lua └── c_stages.lua ├── config.lua ├── fxmanifest.lua ├── html ├── assets │ ├── button_off.5152e6d3.png │ ├── button_on_amber.bb2616bf.png │ ├── button_on_blue.2277a1b4.png │ ├── button_on_green.536ce497.png │ ├── button_on_red.2fb72a77.png │ ├── image.7105ce7a.png │ ├── index.7ebd96a6.js │ └── index.e1c6fa6f.css ├── index.html └── vite.svg ├── server ├── s_blackout.lua ├── s_lvc.lua ├── s_main.js └── s_main.lua ├── shared └── shared_functions.lua └── src ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── App.css ├── App.tsx ├── assets │ ├── background.png │ ├── button_off.png │ ├── button_on_amber.png │ ├── button_on_blue.png │ ├── button_on_green.png │ ├── button_on_red.png │ ├── image.png │ ├── ta_off.png │ ├── ta_on.png │ └── texture.jpg ├── components │ ├── Menu.tsx │ ├── StageButton.css │ ├── StageButton.tsx │ ├── TaModule.css │ └── TaModule.tsx ├── main.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [Your Project Name] 2 | 3 | Thank you for your interest in contributing to this project! Follow the guidelines below to maintain a clean, stable codebase and ensure smooth collaboration. 4 | 5 | ## Branching Strategy 6 | 7 | We follow a **GitFlow** workflow. Here's a summary of the branches and their purposes: 8 | 9 | - **`main`**: This branch always contains production-ready code. Only stable and fully tested features should be merged into `main`. 10 | - **`develop`**: Active development happens here. Features and fixes are integrated here before they reach `main`. 11 | - **`feature/*`**: Use these branches for developing specific features or enhancements. Once completed and reviewed, merge them into `develop`. 12 | 13 | ## How to Contribute 14 | 15 | ### 1. Fork the Repository 16 | Start by forking the repository and creating a local clone. 17 | 18 | ### 2. Create a Feature Branch 19 | If you're adding a new feature or fixing an issue, create a feature branch from `develop`: 20 | 21 | ```bash 22 | git checkout develop 23 | git checkout -b feature/your-feature-name 24 | ``` 25 | 26 | ### 3. Make Changes and Commit 27 | Work on your feature, making commits as needed. Ensure your commits are clear and concise. 28 | 29 | ```bash 30 | git commit -m "Add detailed description of changes here" 31 | ``` 32 | 33 | ### 4. Push to Your Fork and Open a Pull Request 34 | Push your branch to your fork and open a Pull Request (PR) targeting the `develop` branch. 35 | 36 | ```bash 37 | git push origin feature/your-feature-name 38 | ``` 39 | 40 | ### 5. Code Review 41 | Your PR will be reviewed by the maintainers. Please address any feedback, and once approved, the branch will be merged into `develop`. 42 | 43 | Thank you for following these guidelines and contributing to the project! 44 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: https://discord.gg/zH3k624aSv 14 | -------------------------------------------------------------------------------- /.github/workflows/build-ui.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'ulc/src/**' 7 | pull_request: 8 | paths: 9 | - 'ulc/src/**' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '18' 24 | 25 | - name: Install dependencies 26 | run: | 27 | cd ulc/src 28 | npm install 29 | 30 | - name: Build project 31 | run: | 32 | cd ulc/src 33 | npm run build 34 | 35 | - name: Commit and push build output 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | run: | 39 | git config user.name "github-actions" 40 | git config user.email "github-actions@github.com" 41 | cd ulc 42 | git add html/ 43 | git commit -m "Deploy build output" || echo "No changes to commit" 44 | git push origin main 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ulc.zip 3 | .vscode/settings.json 4 | ulc/examples/example_ulc.lua 5 | node_modules/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Dawnstar Entertainment Open Source License 2 | 3 | ## Summary 4 | 5 | ### You can: 6 | - Download the resource 7 | - Use the resource 8 | - Modify the resource 9 | - Distribute the resource 10 | 11 | ### You can not: 12 | - Release or re-distribute the resource with a new name 13 | - Release or distribute the resource without this license 14 | 15 | ## Full License Text 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy of this resource and associated documentation files (the "Resource"), to deal in the Resource without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Resource, subject to the following conditions: 18 | 19 | ### Conditions: 20 | 1. **Attribution** 21 | - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Resource. 22 | 23 | 2. **Non-Renaming** 24 | - You may not release or re-distribute the Resource with a new name. 25 | 26 | 3. **License Inclusion** 27 | - You may not release or distribute the Resource without including this license. 28 | 29 | 4. **No Warranty** 30 | - The Resource is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Resource or the use or other dealings in the Resource. 31 | 32 | 5. **No Endorsement** 33 | - This license does not grant permission to use the trade names, trademarks, service marks, or product names of the licensor, except as required for reasonable and customary use in describing the origin of the Resource and reproducing the content of the notice file. 34 | 35 | 6. **Contribution Back** 36 | - Any modifications or enhancements made to the Resource should be documented and shared back with the community, although this is not a strict requirement. 37 | 38 | ### Additional Provisions: 39 | - **Severability** 40 | - If any provision of this license is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 41 | 42 | - **Termination** 43 | - This license is perpetual, but it may be terminated if you fail to comply with its terms and conditions. Upon termination, you must cease all use, distribution, and modification of the Resource. 44 | 45 | THE RESOURCE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE RESOURCE OR THE USE OR OTHER DEALINGS IN THE RESOURCE. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ultimate Lighting Controller 2 | 3 | ![ulc_banner](https://user-images.githubusercontent.com/48927090/224608424-52e9505c-adc2-47dd-b5ab-30a5f933427f.png)

4 | Discord 5 | GitHub release (latest by date) 6 | GitHub issues 7 | 8 | ULC is an all-in-one lighting controller for non-ELS vehicles in FiveM! It uses the extra-based lighting stages on your vehicles and adds extra automation and improvements to create amazing, realistic, and fully-configurable lighting controls. 9 | 10 | If you are a vehicle developer, [view the full documentation](https://docs.dwnstr.com/ulc/overview). 11 | 12 | # Links 13 | 14 | [Join Discord](https://discord.gg/zH3k624aSv)
15 | [Full ULC documentation](https://docs.dwnstr.com/ulc/overview)
16 | [Config Generator](https://ulc.dwnstr.com/generator) 17 | 18 | [CFX Forum Post](https://forum.cfx.re/t/free-ultimate-lighting-controller/4985223) 19 | 20 | [Video Preview](https://www.youtube.com/watch?v=f1H6sohjTao)
21 | [Video Tutorial](https://youtu.be/FIF3qqRY0Ts) 22 | 23 | # Key Features 24 | 25 | - Stage Controls 26 | - Park Patterns 27 | - Park Pattern Sync 28 | - Intuitive & Minimal UI 29 | - Brake Extras 30 | - Reverse Extras 31 | - Smart Steady Burns 32 | - Blackout Command 33 | - Horn/Honk Patterns & Extras 34 | - Smart Stage Controls 35 | - Granular Configuration per Vehicle 36 | - Re-mappable Keybindings 37 | - Great performance 38 | - more! 39 | 40 | # Installation 41 | 42 | 1. place `ulc` in your `resources` folder
43 | 2. add `ensure ulc` to your `server.cfg` 44 | 3. add the names of your ULC ready resources in the `config.lua` 45 | 46 | For more installation help view the [video tutorial!](https://youtu.be/FIF3qqRY0Ts) 47 | 48 | # Dependencies 49 | 50 | - `onesync` (as of v1.7.0) 51 | - `baseevents` 52 | 53 |
54 | 55 | # Goes well with: 56 | 57 | - [Real Brake Lights](https://github.com/Flohhhhh/real-brake-lights) 58 | - [Luxart Vehicle Control](https://github.com/TrevorBarns/luxart-vehicle-control) 59 | 60 | # Credits 61 | 62 | - [theebu](https://github.com/theebu) - for help with auto repair limitation 63 | - Everyone who helped test! 64 | 65 |
66 | 67 | # Vehicle Configuration 68 | 69 | [Get started by viewing the full documentation here!](https://docs.dwnstr.com/ulc/overview) 70 | 71 | Each vehicle that is configured to use ULC can have it's own dedicated configuration. This configuration can even be included in the vehicle's resource as a `ulc.lua` file, this way vehicle developers can deliver their vehicles pre-configured for ULC. 72 | 73 | ULC offers a wide range of configuration settings and all features are opt-in. 74 | 75 | **By default, no vehicles are affected by ULC's functionality.** In order to enable ULC for a vehicle, you must configure it. There are two methods for doing so. 76 | 77 | # Information for Contributors 78 | 79 | ## How to work on the interface 80 | 81 | The UI is built using React, Vite, Typescript, and Tailwind CSS. The React project in `/ulc/src` builds out to `/ulc/html` which is where FiveM runs it from. 82 | 83 | Follow these steps to run and test locally. 84 | 85 | 1. `cd ulc/src/src` 86 | 2. `npm i` 87 | 3. Uncomment the debugging buttons in `App.tsx`. 88 | 4. `npm run dev` 89 | 5. View the app in your browser and use the debug buttons to show the UI. 90 | 91 | To build the UI to the `html` folder: 92 | 93 | 1. Simply `npm run build` 94 | 95 | ## Contribution guide 96 | 97 | 98 | 99 | 1. All changes that effect the `ulc.lua` config in any way must be 100% backwards compatible. That is, a `ulc.lua` file that was created on the first day ULC was released must still work today. 100 | 1. Any new features should never throw errors if that config section is not present and by default the new behavior should be entirely disabled. 101 | 2. Changes to the configuration format of an existing feature must also still support the old format. 102 | 3. For this reason, it's imperative that new features be well though-out in terms of the format of their configuration in the `ulc.lua` files. 103 | -------------------------------------------------------------------------------- /ulc/client/c_beeps.lua: -------------------------------------------------------------------------------- 1 | 2 | ------------------------- 3 | --------- SOUND --------- 4 | ------------------------- 5 | 6 | function PlayBeep(highPitched) 7 | if highPitched then 8 | PlaySoundFrontend(-1, "5_SEC_WARNING", "HUD_MINI_GAME_SOUNDSET", 1) 9 | else 10 | PlaySoundFrontend(-1, "Beep_Red", "DLC_HEIST_HACKING_SNAKE_SOUNDS", 1) 11 | end 12 | end 13 | 14 | --[[ 15 | coming soon, once client options arrive it will be opt in per client 16 | 17 | --------------- 18 | -- REMINDERS -- 19 | --------------- 20 | local mute = false 21 | 22 | if Config.reminderBeeps then 23 | CreateThread(function() 24 | while true do Wait(Config.reminderBeepTime * 1000) 25 | if Lights and not mute then PlayBeep(false) end 26 | end 27 | end) 28 | end 29 | 30 | RegisterCommand('mutelights', function() 31 | mute = not mute 32 | if mute then 33 | print("Lighting reminders muted.") 34 | else 35 | print("Lighting reminders unmuted.") 36 | end 37 | end) 38 | ]] -------------------------------------------------------------------------------- /ulc/client/c_blackout.lua: -------------------------------------------------------------------------------- 1 | local rblIntegration = false 2 | 3 | function ULC:SetBlackout(newState) 4 | --print("Setting blackout to " .. newState) 5 | local vehicle = GetVehiclePedIsIn(PlayerPedId(), false) 6 | if newState == 0 then 7 | -- do blackout stuff 8 | -- turn off headlights 9 | SetVehicleLights(vehicle, 1) 10 | -- turn off emergency lights 11 | SetVehicleSiren(vehicle, false) 12 | -- turn off specified blackout extras? 13 | -- might need to just make a blackout file and config section, i think this can work when brake patterns aren't being used? 14 | -- turn off cruise lights (do in c_cruise.lua) 15 | -- if lights are turned on with q, or h, or a button is pressed, cancel effect, not sure how to do this. 16 | -- when these actions are done check [if Entity(GetVehiclePedIsIn(PlayerPedId())).state.rbl_blackout or ulc_blackout] <- depending on if these can be accessed when not defined, may have to just use a static global variable in here. 17 | 18 | -- if rbl is not loaded, start checking if vehicle is moving to disable blackout, rbl handles this itself if loaded 19 | if not rblIntegration then 20 | CreateThread(function() 21 | while true do Wait(500) 22 | if Entity(vehicle).state.ulc_blackout == 1 then ULC:SetBlackout(1) return end 23 | local speed = GetEntitySpeed(vehicle) * 2.236936 24 | --print("Speed is " .. speed) 25 | if speed > 5 then ULC:SetBlackout(1) return end 26 | end 27 | end) 28 | end 29 | elseif newState == 1 then 30 | -- do undo blackout stuff 31 | SetVehicleLights(vehicle, 0) 32 | end 33 | end 34 | 35 | -- add statebag change handler for ulc_blackout 36 | AddStateBagChangeHandler('ulc_blackout', null, function(bagName, key, value) 37 | Wait(0) 38 | local vehicle = GetEntityFromStateBagName(bagName) 39 | --print("ulc_blackout listener: Vehicle is " .. vehicle .. " and GetVehiclePedIsIn(PlayerPedId()) is " .. GetVehiclePedIsIn(PlayerPedId())) 40 | if vehicle == 0 or vehicle ~= GetVehiclePedIsIn(PlayerPedId()) then 41 | print("ulc_blackout listener: Vehicle is 0 or not mine.") 42 | return 43 | end 44 | local blackout = value 45 | --print("ulc_blackout listener: new state value is " .. tostring(blackout)) 46 | if blackout == 0 then 47 | ULC:SetBlackout(0) 48 | elseif blackout == 1 then 49 | ULC:SetBlackout(1) 50 | end 51 | end) 52 | 53 | -- add statebag change handler for rbl blackout 54 | AddStateBagChangeHandler('rbl_blackout', null, function(bagName, key, value) 55 | Wait(0) 56 | rblIntegration = true 57 | local vehicle = GetEntityFromStateBagName(bagName) 58 | --print("rbl_blackout listener: Vehicle is " .. vehicle .. " and GetVehiclePedIsIn(PlayerPedId()) is " .. GetVehiclePedIsIn(PlayerPedId())) 59 | if vehicle == 0 or vehicle ~= GetVehiclePedIsIn(PlayerPedId()) then 60 | --print("rbl_blackout listener: Vehicle is 0 or not mine") 61 | return 62 | end 63 | local blackout = value 64 | --print("rbl_blackout listener: new state value is " .. tostring(blackout)) 65 | if blackout == true then 66 | --print("rbl_blackout listener: setting blackout to 0") 67 | ULC:SetBlackout(0) 68 | elseif blackout == false then 69 | --print("rbl_blackout listener: setting blackout to 1") 70 | ULC:SetBlackout(1) 71 | end 72 | end) 73 | 74 | -- register command for blackout 75 | 76 | -- if rbl loads first then ULC 77 | -- ulc will overwrite command 78 | -- ULC needs to trigger rbl:setBlackout state change on server 79 | -- ULC only manages the extras 80 | 81 | -- if ulc loads first then rbl 82 | -- rbl will overwrite command 83 | -- rbl needs to trigger ulc:setBlackout state change on server 84 | -- rbl only manages the brake lights 85 | 86 | RegisterCommand('blackout', function() 87 | local newState 88 | local vehicle = GetVehiclePedIsIn(PlayerPedId(), false) 89 | if not vehicle then return end 90 | local currentState = Entity(vehicle).state.ulc_blackout 91 | --print("/blackout: Current state: " .. tostring(currentState)) 92 | if currentState == nil or currentState == 1 then 93 | print("Setting blackout to true/0") 94 | newState = 0 95 | elseif currentState == 0 then 96 | print("Setting blackout to false/1") 97 | newState = 1 98 | end 99 | -- trigger server event to set blackout on my vehicle 100 | -- might need to extract this to an event/function to control the effect programmatically, like disbling when q pressed 101 | TriggerServerEvent('ulc:setBlackout', VehToNet(GetVehiclePedIsIn(PlayerPedId())), newState) 102 | TriggerServerEvent('rbl:setBlackout', VehToNet(GetVehiclePedIsIn(PlayerPedId())), newState) 103 | end) 104 | -- toggle blackout state on vehicle 105 | 106 | 107 | ------------------------------- 108 | -- DISABLE BLACKOUT TRIGGERS -- 109 | ------------------------------- 110 | 111 | --TODO when H is pressed to control headlights disable blackout 112 | --TODO when Q is pressed to control emergency lights disable blackout 113 | 114 | 115 | -------------------------------------------------------------------------------- /ulc/client/c_brake.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC] Brake Extras Loaded") 2 | local realBrakeThreshold = 3 3 | local shouldUseRealBrakes = function() 4 | return (MyVehicleConfig.brakeConfig.speedThreshold or 3) <= realBrakeThreshold 5 | end 6 | local braking = false 7 | 8 | ------------------- 9 | -- MAIN FUNCTIONS -- 10 | ------------------- 11 | 12 | local disabledExtras = {} 13 | 14 | local function setBrakeExtras(newState) 15 | for _, v in pairs(MyVehicleConfig.brakeConfig.brakeExtras) do 16 | local currentState 17 | if IsVehicleExtraTurnedOn(MyVehicle, v) then currentState = 0 else currentState = 1 end 18 | --print("[ULC] setBrakeExtras() newState: " .. newState .. " currentState: " .. currentState) 19 | if currentState == newState then break end 20 | ULC:SetStage(v, newState, false, true, false, false, true, false) 21 | end 22 | if newState == 0 then 23 | -- disable the disable extras and save the ones that we change 24 | if not MyVehicleConfig.brakeConfig.disableExtras then return end 25 | for _, v in pairs(MyVehicleConfig.brakeConfig.disableExtras) do 26 | if IsVehicleExtraTurnedOn(MyVehicle, v) then 27 | ULC:SetStage(v, 1, false, true, false, false, true, false) 28 | table.insert(disabledExtras, v) 29 | end 30 | end 31 | elseif newState == 1 then 32 | -- re-enable any extras that were disabled 33 | for _, v in pairs(disabledExtras) do 34 | ULC:SetStage(v, 0, false, true, false, false, true, false) 35 | end 36 | disabledExtras = {} 37 | end 38 | end 39 | 40 | 41 | ---------------------------- 42 | -- REALISTIC BRAKE LIGHTS -- 43 | ---------------------------- 44 | if shouldUseRealBrakes then 45 | print("Realistic brake light functionality intialized.") 46 | local mode = "STANDARD" 47 | 48 | -- start checking if stopped manually with a loop 49 | CreateThread(function() 50 | local sleep = 1000 51 | while true do 52 | Wait(sleep) 53 | -- if rbl_brakelights change handler gets triggered that means rbl exists and we want to use that functionality instead, return from this loop 54 | if mode == "RBL" then 55 | print("real-brake-lights resource detected, integrating brakelight functionality.") 56 | return 57 | end 58 | if not MyVehicle then 59 | sleep = 1000 60 | goto continue 61 | end 62 | if not shouldUseRealBrakes() then 63 | sleep = 1000 64 | goto continue 65 | end 66 | if not MyVehicleConfig.brakeConfig.useBrakes then 67 | sleep = 1000 68 | goto continue 69 | end 70 | if braking then goto continue end 71 | sleep = 250 72 | local speed = GetVehicleSpeedConverted(MyVehicle) 73 | if speed < realBrakeThreshold and shouldUseRealBrakes() and not IsControlPressed(0, 72) then 74 | --print("[manual checks] Enabling brakes") 75 | setBrakeExtras(0) 76 | else 77 | --print("[manual checks] Disabling brakes") 78 | setBrakeExtras(1) 79 | end 80 | ::continue:: 81 | end 82 | end) 83 | 84 | -- add a statebag change handler for rbl_brakelights 85 | -- once this is triggered, disable manual checking 86 | AddStateBagChangeHandler('rbl_brakelights', null, function(bagName, key, value) 87 | Wait(0) -- Nedded as GetEntityFromStateBagName sometimes returns 0 on first frame 88 | mode = "RBL" -- set mode to RBL to disable manual checking 89 | if not MyVehicle then return end 90 | if not MyVehicleConfig.brakeConfig.useBrakes then return end 91 | local vehicle = GetEntityFromStateBagName(bagName) 92 | --print("state changed for vehicle") 93 | if vehicle == 0 or vehicle ~= MyVehicle then return end 94 | local newState 95 | if value then newState = 0 else newState = 1 end 96 | --print("ULC: Setting brakes to state" .. newState) 97 | setBrakeExtras(newState) 98 | end) 99 | end 100 | 101 | ----------------- 102 | -- KEYBINDINGS -- 103 | ----------------- 104 | 105 | -- pressed brakes 106 | RegisterCommand('+ulc:brakePattern', function() 107 | braking = true 108 | if MyVehicle and MyVehicleConfig.brakeConfig.useBrakes then 109 | if GetVehicleCurrentGear(MyVehicle) == 0 then return end -- disable while reversing 110 | --print("Enabling brakes") 111 | local speed = GetVehicleSpeedConverted(MyVehicle) 112 | -- if using real brakes always enable 113 | if shouldUseRealBrakes() or speed > (MyVehicleConfig.brakeConfig.speedThreshold or 3) then 114 | setBrakeExtras(0) 115 | end 116 | end 117 | SendNUIMessage({ 118 | type = 'toggleBrakeIndicator', 119 | state = true 120 | }) 121 | end) 122 | 123 | RegisterCommand('-ulc:brakePattern', function() 124 | braking = false 125 | if MyVehicle and MyVehicleConfig.brakeConfig.useBrakes then 126 | local speed = GetVehicleSpeedConverted(MyVehicle) 127 | if shouldUseRealBrakes() and speed < realBrakeThreshold then return end 128 | --print("Disabling brakes") 129 | setBrakeExtras(1) 130 | end 131 | SendNUIMessage({ 132 | type = 'toggleBrakeIndicator', 133 | state = false 134 | }) 135 | end) 136 | 137 | RegisterKeyMapping('+ulc:brakePattern', 'ULC: Activate Brake Pattern (Hold)', 'keyboard', 's') 138 | -------------------------------------------------------------------------------- /ulc/client/c_buttons.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC]: Stage Controls Loaded") 2 | 3 | ------------------- 4 | ------------------- 5 | ----- HELPERS ----- 6 | ------------------- 7 | ------------------- 8 | 9 | function GetExtraByKey(key) 10 | local result = nil 11 | for _, v in pairs(MyVehicleConfig.buttons) do 12 | if v.key == key then 13 | result = v.extra 14 | end 15 | end 16 | return result 17 | end 18 | 19 | function GetButtonByExtra(extra) 20 | local result = nil 21 | for _, v in pairs(MyVehicleConfig.buttons) do 22 | if v.extra == extra then 23 | result = v 24 | end 25 | end 26 | return result 27 | end 28 | 29 | function ULC:ChangeExtra(extra, newState, repair) 30 | --print("[ULC:ChangeExtra()] Changing extra: " .. extra .. " to: " .. newState) 31 | -- disable repair 32 | if not repair then 33 | SetVehicleAutoRepairDisabled(MyVehicle, true) 34 | end 35 | -- change extra 36 | SetVehicleExtra(MyVehicle, extra, newState) 37 | -- fix deformation if repair is true 38 | if repair then 39 | SetVehicleDeformationFixed(MyVehicle) 40 | end 41 | -- enable repair 42 | SetVehicleAutoRepairDisabled(MyVehicle, false) 43 | end 44 | 45 | --------------- 46 | --------------- 47 | -- MAIN CODE -- 48 | --------------- 49 | --------------- 50 | 51 | -- new event 52 | AddEventHandler('ulc:SetStage', function(extra, action, playSound, extraOnly, repair, forceChange, forceUi) 53 | ULC:SetStage(extra, action, playSound, extraOnly, repair, forceChange, forceUi) 54 | end) 55 | 56 | -- change specified extra, and if not extraOnly, and extra is in a button, act on the linked and off extras as well, acts recursively; 57 | -- action 0 enables, 1 disables, 2 toggles; 58 | -- updates ui whenever extra is used in a button 59 | function ULC:SetStage(extra, action, playSound, extraOnly, repair, forceChange, forceUi, allowOutside) 60 | ---------- 61 | -- checks 62 | if not MyVehicle then 63 | print("[ULC:SetStage()] MyVehicle is not defined right now :/") 64 | return false 65 | end 66 | if not allowOutside and not IsPedInAnyVehicle(PlayerPedId(), false) then 67 | print("[ULC:SetStage()] Player must be in a vehicle, or allowOutside must be true.") 68 | return false 69 | end 70 | 71 | --print("[ulc:SetStage]", extra, action, playSound, extraOnly) 72 | 73 | -------------- 74 | -- definitions 75 | local button = GetButtonByExtra(extra) 76 | local buttonStates = {} -- track button states for UI 77 | 78 | -------------------------- 79 | -- determine the new state 80 | local newState 81 | if IsVehicleExtraTurnedOn(MyVehicle, extra) then 82 | if action == 1 or action == 2 then 83 | newState = 1 84 | end 85 | else 86 | if action == 0 or action == 2 then 87 | newState = 0 88 | end 89 | end 90 | 91 | --------------------------------------------------------- 92 | -- built in don't try to change if it's the same already! 93 | --[[ forceChange is used to forceChange the change even if it's the same 94 | this is used to trigger the additional actions like linked and off extras 95 | even if the extra is already in the state we want it to be 96 | (used in cycling stages and one other place i cant remember)]] 97 | if not forceChange and not newState then return end 98 | 99 | local canChange = true 100 | if repair then 101 | if not AreVehicleDoorsClosed(MyVehicle) then 102 | canChange = false 103 | print("[ULC:SetStage] Can't change stage with repair while a door is open.") 104 | end 105 | if not IsVehicleHealthy(MyVehicle) then 106 | canChange = false 107 | print("[ULC:SetStage] Can't change stage with repair while vehicle is damaged.") 108 | end 109 | end 110 | 111 | if not canChange then return end 112 | 113 | --------------------------------------- 114 | -- if the extra corresponds to a button 115 | if button then 116 | -------------------- 117 | -- sound 118 | if playSound then 119 | if newState == 0 then 120 | PlayBeep(true) 121 | else 122 | PlayBeep(false) 123 | end 124 | end 125 | 126 | ---------------------- 127 | -- smart stages stuff 128 | local key = button.key 129 | if MyVehicleConfig.stages then 130 | local keyStage = contains(MyVehicleConfig.stages.stageKeys, key) -- find whether MyVehicleConfig.stages.stageKeys contain the key 131 | 132 | -- # TODO we're not getting here for some reason when cycling stages at max stage 133 | -- if the key pressed is not a stage, just change the extra 134 | if not keyStage then 135 | ULC:ChangeExtra(extra, newState, repair) 136 | end 137 | 138 | -- if the key pressed is a stage and extraOnly is false 139 | if keyStage and not extraOnly then 140 | print("key: " .. key .. " keyStage: " .. tostring(keyStage) .. " currentStage: " .. currentStage) 141 | -- if it's the same as the current stage, change the extra and proceed normally 142 | if keyStage == currentStage then 143 | print("Key is the same as current stage") 144 | ULC:ChangeExtra(extra, newState, repair) 145 | -- set the stage to 0 146 | print("Setting stage to: 0") 147 | currentStage = 0 148 | else 149 | -- if it's a different stage then we want to change to a state where that stage is enabled 150 | print("Key is not the same as current stage") 151 | newState = 0 -- change to newState to 0 since we want the new stage to be enabled 152 | 153 | local currentPrimaryExtraState = IsVehicleExtraTurnedOn(MyVehicle, extra) 154 | print("New state: " .. 155 | newState .. "Extra " .. extra .. " current state: " .. tostring(currentPrimaryExtraState)) 156 | 157 | if not IsVehicleExtraTurnedOn(MyVehicle, extra) and newState == 0 then 158 | print("Extra needs to be turned on") 159 | ULC:ChangeExtra(extra, newState, repair) 160 | elseif IsVehicleExtraTurnedOn(MyVehicle, extra) and newState == 1 then 161 | print("Extra needs to be turned off") 162 | ULC:ChangeExtra(extra, newState, repair) 163 | else 164 | -- if it's in the correct state 165 | print("Extra is already in the correct state") 166 | end 167 | -- set the new stage 168 | print("Setting stage to: " .. keyStage) 169 | currentStage = keyStage 170 | end 171 | else -- if extraOnly is true 172 | ULC:ChangeExtra(extra, newState, repair) 173 | end 174 | else 175 | -- if there are no stages, just change the extra 176 | ULC:ChangeExtra(extra, newState, repair) 177 | end 178 | 179 | 180 | ---------------------- 181 | -- initialize UI changes 182 | -- add that button to the new button states for UI with it's extra and new state 183 | table.insert(buttonStates, { extra = extra, newState = newState }) 184 | 185 | ----------------------------- 186 | -- additional actions/extras 187 | if not extraOnly then 188 | -- set linked extras 189 | if button.linkedExtras then 190 | for _, v in ipairs(button.linkedExtras) do 191 | ULC:SetStage(v, newState, false, true, repair, forceChange) 192 | -- add linked buttons to the new button states for UI with their extras and new state 193 | table.insert(buttonStates, { extra = v, newState = newState }) 194 | end 195 | end 196 | 197 | -- set opposite extras 198 | if button.oppositeExtras or false then -- in case they have old config without the feature 199 | local oppState 200 | if newState == 1 then oppState = 0 elseif newState == 0 then oppState = 1 end 201 | for _, v in pairs(button.oppositeExtras) do 202 | ULC:SetStage(v, oppState, false, true, repair, forceChange) 203 | -- add opposite buttons to the new button states for UI with their extras and new state 204 | table.insert(buttonStates, { extra = v, newState = oppState }) 205 | end 206 | end 207 | 208 | -- set off extras 209 | if button.offExtras then 210 | for _, v in ipairs(button.offExtras) do 211 | ULC:SetStage(v, 1, false, true, repair, forceChange) 212 | -- add off buttons to the new button states for UI with their extras and new state 213 | table.insert(buttonStates, { extra = v, newState = 1 }) 214 | end 215 | end 216 | end 217 | 218 | if not extraOnly or forceUi then 219 | -- update UI 220 | ULC:SetButtons(buttonStates) 221 | end 222 | else -- if it's not a button, we just change the extra because we don't care about stages or linked extras etc. 223 | ULC:ChangeExtra(extra, newState, repair) 224 | end 225 | end 226 | 227 | ----------------------- 228 | ----------------------- 229 | ------ KEYBINDS ------- 230 | ----------------------- 231 | ----------------------- 232 | 233 | for i = 1, 9, 1 do 234 | RegisterKeyMapping('ulc:num' .. i, 'ULC: Toggle Button ' .. i, 'keyboard', 'NUMPAD' .. i) 235 | RegisterCommand('ulc:num' .. i, function() 236 | local extra = GetExtraByKey(i) 237 | local button = GetButtonByExtra(extra) 238 | if not button then return end 239 | ULC:SetStage(extra, 2, true, false, button.repair or false) 240 | end) 241 | end 242 | 243 | ------------------ 244 | ------ HELP ------ 245 | ------------------ 246 | 247 | local activeButtons = {} 248 | local showingHelp = false 249 | 250 | function ShowHelp() 251 | CreateThread(function() 252 | if not showingHelp then 253 | -- show help 254 | showingHelp = true 255 | for k, v in ipairs(activeButtons) do 256 | --print('Showing help for button: ' .. k .. ' : ' .. v.key) 257 | SendNUIMessage({ 258 | type = 'showHelp', 259 | button = k, 260 | key = v.key, 261 | }) 262 | end 263 | Wait(3000) 264 | -- hide help 265 | showingHelp = false 266 | for k, v in ipairs(activeButtons) do 267 | SendNUIMessage({ 268 | type = 'hideHelp', 269 | button = k, 270 | label = string.upper(v.label), 271 | }) 272 | end 273 | end 274 | end) 275 | end 276 | -------------------------------------------------------------------------------- /ulc/client/c_cruise.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC]: Cruise lights Loaded") 2 | 3 | -- 1 disabled, 0 enabled 4 | local sbState = 1 5 | 6 | -- 0 on, 1 off 7 | local function setCruiseLights(newState) 8 | sbState = newState 9 | for _, v in pairs(MyVehicleConfig.steadyBurnConfig.sbExtras) do 10 | --print("Setting cruise lights extra: " .. v) 11 | ULC:SetStage(v, newState, false, true, false, false, true, false) 12 | end 13 | end 14 | 15 | local function getSteadyBurnState() 16 | if IsVehicleExtraTurnedOn(MyVehicle, MyVehicleConfig.steadyBurnConfig.sbExtras[1]) then 17 | return 0 18 | else 19 | return 1 20 | end 21 | end 22 | 23 | CreateThread(function() 24 | while true do 25 | if MyVehicle then 26 | TriggerEvent('ulc:CheckCruise', false) 27 | end 28 | Wait(1000) 29 | end 30 | end) 31 | 32 | --TODO: disable when lights are on, enable when lights are off 33 | 34 | AddEventHandler('ulc:lightsOn', function() 35 | --print("Lights on") 36 | if MyVehicle and (MyVehicleConfig.steadyBurnConfig.disableWithLights or false) then 37 | setCruiseLights(1) 38 | end 39 | end) 40 | 41 | AddEventHandler('ulc:lightsOff', function() 42 | --print("Lights off") 43 | if MyVehicle and (MyVehicleConfig.steadyBurnConfig.disableWithLights or false) then 44 | TriggerEvent('ulc:CheckCruise') 45 | end 46 | end) 47 | 48 | AddEventHandler('ulc:CheckCruise', function() 49 | sbState = getSteadyBurnState() 50 | if not MyVehicle then return end 51 | 52 | if Entity(MyVehicle).state.ulc_blackout == 0 then 53 | -- print("Blackout is on, disabling cruise lights") 54 | setCruiseLights(1) 55 | return 56 | end 57 | 58 | if MyVehicleConfig.steadyBurnConfig.forceOn then 59 | if sbState == 0 then return end 60 | if Lights and MyVehicleConfig.steadyBurnConfig.disableWithLights then return end 61 | --print("Setting cruise lights on") 62 | setCruiseLights(0) 63 | elseif MyVehicleConfig.steadyBurnConfig.useTime then 64 | local isTime = GetClockHours() > Config.SteadyBurnSettings.nightStartHour or 65 | GetClockHours() < Config.SteadyBurnSettings.nightEndHour 66 | if isTime then 67 | -- if lights are already on do nothing 68 | if sbState == 0 then return end 69 | if Lights and MyVehicleConfig.steadyBurnConfig.disableWithLights then return end 70 | setCruiseLights(0) 71 | else 72 | -- if already off do nothing 73 | if sbState == 1 then return end 74 | setCruiseLights(1) 75 | end 76 | end 77 | end) 78 | -------------------------------------------------------------------------------- /ulc/client/c_doors.lua: -------------------------------------------------------------------------------- 1 | local doors = { 2 | [0] = false, -- d front 3 | [1] = false, -- p front 4 | [2] = false, -- d rear 5 | [3] = false, -- p rear 6 | [4] = false, -- hood 7 | [5] = false -- trunk 8 | } 9 | 10 | local function intNot(value) 11 | result = 0 12 | if value == 0 then 13 | result = 1 14 | end 15 | return result 16 | end 17 | 18 | -- state 1 = closed, state 0 = open 19 | local function onDoorStateChange(door, newDoorState) 20 | --print("Handling door change", door, newDoorState) 21 | if door == 0 or door == 2 then -- if driver side 22 | for _, v in pairs(MyVehicleConfig.doorConfig.driverSide.enable) do 23 | --print("Enable extra:", v) 24 | ULC:SetStage(v, newDoorState, true, true, false, false, true, true) 25 | end 26 | for _, v in pairs(MyVehicleConfig.doorConfig.driverSide.disable) do 27 | --print("Disable extra:", v, intNot(newDoorState)) 28 | ULC:SetStage(v, intNot(newDoorState), true, true, false, false, true, true) 29 | end 30 | elseif door == 1 or door == 3 then -- if pass side 31 | for _, v in pairs(MyVehicleConfig.doorConfig.passSide.enable) do 32 | --print("Enable extra:", v) 33 | ULC:SetStage(v, newDoorState, true, true, false, false, true, true) 34 | end 35 | for _, v in pairs(MyVehicleConfig.doorConfig.passSide.disable) do 36 | --print("Disable extra:", v, intNot(newDoorState)) 37 | ULC:SetStage(v, intNot(newDoorState), true, true, false, false, true, true) 38 | end 39 | elseif door == 5 then -- if trunk 40 | for _, v in pairs(MyVehicleConfig.doorConfig.trunk.enable) do 41 | ULC:SetStage(v, newDoorState, true, true, false, false, true, true) 42 | end 43 | for _, v in pairs(MyVehicleConfig.doorConfig.trunk.disable) do 44 | ULC:SetStage(v, intNot(newDoorState), true, true, false, false, true, true) 45 | end 46 | end 47 | end 48 | 49 | CreateThread(function() 50 | local sleep = 1000 51 | while true do 52 | Wait(sleep) 53 | if not MyVehicle then 54 | sleep = 1000 55 | goto continue 56 | end 57 | if not MyVehicleConfig.doorConfig or false then 58 | sleep = 1000 59 | goto continue 60 | end 61 | if not MyVehicleConfig.doorConfig.useDoors then 62 | sleep = 1000 63 | goto continue 64 | end 65 | sleep = 250 66 | 67 | for k, v in pairs(doors) do 68 | if GetVehicleDoorAngleRatio(MyVehicle, k) > 0.0 then 69 | if v == false then 70 | -- print("Setting door", k, "open.") 71 | doors[k] = true -- set door open 72 | onDoorStateChange(k, 0) -- handle what to do 73 | end 74 | else 75 | if v == true then 76 | -- print("Setting door", k, "closed.") 77 | doors[k] = false -- set door closed 78 | onDoorStateChange(k, 1) -- handle what to do 79 | end 80 | end 81 | end 82 | 83 | ::continue:: 84 | end 85 | end) 86 | -------------------------------------------------------------------------------- /ulc/client/c_horn.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC]: Horn Extras Loaded") 2 | 3 | local extraStates = {} 4 | 5 | local function GetPreviousStateByExtra(extra) 6 | for k, v in pairs(extraStates) do 7 | --print(v.extra, v.state) 8 | if extra == v.extra then 9 | --print('Found state of : ' .. tostring(v.state) .. ' for extra ' .. extra) 10 | return v.state 11 | end 12 | end 13 | end 14 | 15 | function SetHornExtras(newState) 16 | -- print('SetHornExtras: ' .. newState) 17 | if newState == 0 then 18 | for _, extra in pairs(MyVehicleConfig.hornConfig.hornExtras) do 19 | local extraState = { 20 | extra = extra, 21 | state = IsVehicleExtraTurnedOn(MyVehicle, extra) 22 | } 23 | table.insert(extraStates, extraState) 24 | ULC:SetStage(extra, 0, false, true, false, false, true, false) 25 | end 26 | if not MyVehicleConfig.hornConfig.disableExtras then return end 27 | for _, extra in pairs(MyVehicleConfig.hornConfig.disableExtras) do 28 | local extraState = { 29 | extra = extra, 30 | state = IsVehicleExtraTurnedOn(MyVehicle, extra) 31 | } 32 | table.insert(extraStates, extraState) 33 | ULC:SetStage(extra, 1, false, true, false, false, true, false) 34 | end 35 | elseif newState == 1 then 36 | for _, extra in pairs(MyVehicleConfig.hornConfig.hornExtras) do 37 | local prevState = GetPreviousStateByExtra(extra) 38 | if not prevState then 39 | ULC:SetStage(extra, 1, false, true, false, false, true, false) 40 | end 41 | end 42 | if not MyVehicleConfig.hornConfig.disableExtras then return end 43 | for _, extra in pairs(MyVehicleConfig.hornConfig.disableExtras) do 44 | local prevState = GetPreviousStateByExtra(extra) 45 | if prevState then 46 | ULC:SetStage(extra, 0, false, true, false, false, true, false) 47 | end 48 | end 49 | end 50 | end 51 | 52 | RegisterCommand('+ulc:horn', function() 53 | --print('horn') 54 | extraStates = {} 55 | 56 | if MyVehicle and MyVehicleConfig.hornConfig.useHorn then 57 | SetHornExtras(0) 58 | end 59 | end) 60 | 61 | RegisterCommand('-ulc:horn', function() 62 | if MyVehicle and MyVehicleConfig.hornConfig.useHorn then 63 | SetHornExtras(1) 64 | end 65 | end) 66 | 67 | RegisterKeyMapping('+ulc:horn', 'ULC: Activate Horn Extras', 'keyboard', 'e') 68 | -------------------------------------------------------------------------------- /ulc/client/c_hud.lua: -------------------------------------------------------------------------------- 1 | -- MAIN FUNCTIONS -- 2 | 3 | function ULC:PopulateButtons(_buttons, placeholders) 4 | --print("Populating buttons") 5 | 6 | local buttons = _buttons 7 | 8 | if placeholders then 9 | buttons = { 10 | { label = 'TEST STAGE', extra = 1, color = 'green', enabled = true }, 11 | { label = 'TEST STAGE', extra = 2, color = 'blue', enabled = false }, 12 | { label = 'TEST STAGE', extra = 3, color = 'blue', enabled = false }, 13 | { label = 'TEST STAGE', extra = 4, color = 'blue', enabled = true }, 14 | { label = 'test stage', extra = 5, color = 'red', enabled = true } 15 | } 16 | end 17 | 18 | local buttonsToSend = {} 19 | for _, v in pairs(buttons) do 20 | local thisButton = {} 21 | local thisState = false 22 | if IsVehicleExtraTurnedOn(MyVehicle, v.extra) then thisState = true end 23 | thisButton.extra = v.extra 24 | thisButton.enabled = thisState 25 | thisButton.color = v.color or 'green' 26 | thisButton.label = string.upper(v.label) 27 | thisButton.numKey = v.key 28 | 29 | --print("Sending button: " .. json.encode(thisButton)) 30 | table.insert(buttonsToSend, thisButton) 31 | end 32 | 33 | SendNUIMessage({ 34 | type = 'populateButtons', 35 | buttons = buttonsToSend 36 | }) 37 | end 38 | 39 | -- deprecated in exchange for "SetButtons" 40 | function ULC:SetButton(extra, enabled) 41 | local newState 42 | if enabled == 0 then 43 | newState = true 44 | elseif enabled == 1 then 45 | newState = false 46 | end 47 | 48 | SendNUIMessage({ 49 | type = 'setButton', 50 | extra = extra, 51 | newState = newState 52 | }) 53 | end 54 | 55 | function ULC:SetButtons(buttonStates) 56 | -- print("Setting buttons", json.encode(buttonStates)) 57 | -- go through the buttons and replace newState with a boolean 58 | -- newState = 1 means false, 0 means true 59 | 60 | for k, v in pairs(buttonStates) do 61 | if v.newState == 1 then 62 | v.newState = false 63 | elseif v.newState == 0 then 64 | v.newState = true 65 | end 66 | end 67 | 68 | SendNUIMessage({ 69 | type = 'setButtons', 70 | buttonStates = buttonStates 71 | }) 72 | end 73 | 74 | ----------------- 75 | -- UI SETTINGS -- 76 | ----------------- 77 | 78 | function ULC:SetDisplay(bool) 79 | if bool then 80 | SendNUIMessage({ 81 | type = 'showHUD', 82 | }) 83 | else 84 | SendNUIMessage({ 85 | type = 'hideHUD', 86 | }) 87 | end 88 | end 89 | 90 | function ULC:SetPosition(x, y) 91 | SendNUIMessage({ 92 | type = 'setPosition', 93 | x = x, 94 | y = y 95 | }) 96 | end 97 | 98 | function ULC:SetScale(float) 99 | SendNUIMessage({ 100 | type = 'setScale', 101 | scale = float 102 | }) 103 | end 104 | 105 | -- TODO this is unused in game, should be in menu 106 | function ULC:SetUseLeftAnchor(bool) 107 | SendNUIMessage({ 108 | type = 'setAnchor', 109 | bool = bool 110 | }) 111 | end 112 | 113 | function ULC:SetHudDisabled(bool) 114 | SendNUIMessage({ 115 | type = 'setHudDisabled', 116 | bool = bool 117 | }) 118 | end 119 | 120 | function ULC:SetHelpDisplay(bool) 121 | if bool then 122 | SendNUIMessage({ type = 'showHelp' }) 123 | else 124 | SendNUIMessage({ type = 'hideHelp' }) 125 | end 126 | end 127 | 128 | ---------- 129 | -- MENU -- 130 | ---------- 131 | 132 | -- TODO set this up in js 133 | function ULC:SetMenuDisplay(bool) 134 | --print("Client setting display", bool) 135 | if bool then 136 | SendNUIMessage({ 137 | type = 'showMenu', 138 | }) 139 | else 140 | SendNUIMessage({ 141 | type = 'hideHideMenu', 142 | }) 143 | end 144 | end 145 | 146 | ---------------------- 147 | -- USER PREFERENCES -- 148 | ---------------------- 149 | print("[ULC] Client Storage Loaded") 150 | ClientPrefs = {} 151 | 152 | local function loadUserPrefs() 153 | -- if prefs already exist 154 | local prefsExist = GetResourceKvpString('ulc') 155 | if prefsExist == "exists" then 156 | print("[ULC] Loading prefs") 157 | -- load 158 | ClientPrefs.hideUi = GetResourceKvpInt("ulc:hideUi") 159 | ClientPrefs.x = GetResourceKvpInt("ulc:x") 160 | ClientPrefs.y = GetResourceKvpInt("ulc:y") 161 | ClientPrefs.scale = GetResourceKvpFloat("ulc:scale") 162 | ClientPrefs.useLeftAnchor = GetResourceKvpString("ulc:useLeftAnchor") 163 | else 164 | print("[ULC] Creating prefs") 165 | -- set defaults 166 | SetResourceKvp('ulc', "exists") 167 | SetResourceKvpInt('ulc:x', 0) 168 | SetResourceKvpInt('ulc:y', 0) 169 | SetResourceKvpFloat('ulc:scale', 1.0) 170 | SetResourceKvpInt('ulc:hideUi', 0) 171 | SetResourceKvp('ulc:useLeftAnchor', 'false') 172 | 173 | 174 | loadUserPrefs() 175 | 176 | Wait(5000) 177 | TriggerEvent('chat:addMessage', { 178 | color = { 0, 153, 204 }, 179 | multiline = false, 180 | args = { "ULC", "^4This server uses ULC! Type /ulc to view settings and adjust the HUD!" } 181 | }) 182 | end 183 | end 184 | 185 | loadUserPrefs() 186 | 187 | -- use the values 188 | CreateThread(function() 189 | --print("CLIENT PREF DISABLED =", ClientPrefs.hideUi) 190 | Wait(1000) 191 | -- positioning 192 | if ClientPrefs.x then 193 | --print("Loaded position from kvp: ", ClientPrefs.x, ClientPrefs.y) 194 | ULC:SetPosition(ClientPrefs.x, ClientPrefs.y) 195 | end 196 | if ClientPrefs.scale then 197 | -- print("Loaded saved scale from kvp: " .. ClientPrefs.scale) 198 | ULC:SetScale(ClientPrefs.scale + 0.0) 199 | end 200 | if ClientPrefs.hideUi then 201 | -- print("Loaded disabled HUD kvp: " .. ClientPrefs.hideUi) 202 | ULC:SetHudDisabled(ClientPrefs.hideUi) 203 | end 204 | if ClientPrefs.useLeftAnchor then 205 | -- print("Loaded useLeftAnchor from kvp", ClientPrefs.useLeftAnchor) 206 | ULC:SetUseLeftAnchor(ClientPrefs.useLeftAnchor) 207 | end 208 | end) 209 | 210 | 211 | -------------- 212 | -- COMMANDS -- 213 | -------------- 214 | 215 | RegisterCommand('ulc', function() 216 | if not MyVehicle then 217 | ULC:PopulateButtons({}, true) 218 | ULC:SetDisplay(true) 219 | end 220 | ULC:SetMenuDisplay(true) 221 | if MyVehicle then 222 | ULC:SetHelpDisplay(true) 223 | end 224 | SetNuiFocus(true, true) 225 | end) 226 | 227 | TriggerEvent('chat:addSuggestion', '/ulc', 'Enables dragging ULC HUD and shows settings menu and controls.', { 228 | }) 229 | 230 | RegisterCommand("ulcReset", function() 231 | DeleteResourceKvp("ulc") 232 | loadUserPrefs() 233 | end) 234 | 235 | TriggerEvent('chat:addSuggestion', '/ulcReset', 'Resets all saved ULC settings to defaults.', { 236 | }) 237 | 238 | -- NUI CALLBACKS -- 239 | 240 | RegisterNUICallback("savePosition", function(data, cb) 241 | --print("NUI Setting position", data.newX, data.newY, "type = ", type(data.newX)) 242 | SetResourceKvpInt('ulc:x', data.newX) 243 | SetResourceKvpInt('ulc:y', data.newY) 244 | 245 | cb({ success = true }) 246 | end) 247 | 248 | RegisterNUICallback("saveScale", function(data, cb) 249 | --print("NUI Setting Scale " .. data.scale + 0.0) 250 | SetResourceKvpFloat('ulc:scale', data.scale + 0.0) 251 | 252 | cb({ success = true }) 253 | end) 254 | 255 | RegisterNUICallback("saveAnchor", function(data, cb) 256 | --print("NUI Setting Anchor ", data.useLeftAnchor) 257 | SetResourceKvp('ulc:useLeftAnchor', data.useLeftAnchor) 258 | 259 | cb({ success = true }) 260 | end) 261 | 262 | RegisterNUICallback("focusGame", function(data, cb) 263 | ULC:SetMenuDisplay(false) 264 | SetNuiFocus(false, false) 265 | ULC:SetHelpDisplay(false) 266 | 267 | if not MyVehicle then 268 | ULC:SetDisplay(false) 269 | end 270 | 271 | cb({ success = true }) 272 | end) 273 | 274 | RegisterNUICallback("setHudDisabled", function(data, cb) 275 | --print("NUI Setting HUD Disabled ", data.hudDisabled) 276 | 277 | if not data.hudDisabled then 278 | --print("Set HUD enabled") 279 | if MyVehicle then 280 | ULC:SetDisplay(true) 281 | end 282 | if #ClientPrefs > 0 then 283 | ClientPrefs.hideUi = 0 284 | --print("Are we here?") 285 | end 286 | SetResourceKvpInt('ulc:hideUi', 0) 287 | else 288 | --print("Set HUD disabled") 289 | ULC:SetDisplay(false) 290 | if #ClientPrefs > 0 then 291 | ClientPrefs.hideUi = 1 292 | --print("Are we here?") 293 | end 294 | SetResourceKvpInt('ulc:hideUi', 1) 295 | end 296 | 297 | cb({ success = true }) 298 | end) 299 | -------------------------------------------------------------------------------- /ulc/client/c_lvc.lua: -------------------------------------------------------------------------------- 1 | print("[ULC] LVC Integrations Loaded") 2 | 3 | -- going to store the LVC siren state just for fun 4 | LVC_SirenState = 0 5 | 6 | -- received from s_lvc.lua when player changes main siren state in LVC 7 | -- sirenId is an int representing the index of a siren in lvc/SIRENS.lua:SIRENS 8 | RegisterNetEvent("ulc:LVC_MainSirenStateChange") 9 | AddEventHandler("ulc:LVC_MainSirenStateChange", function(sirenId) 10 | print("[ulc:LVC_MainSirenStateChange] " .. sirenId) 11 | -- # TODO check how much of this is actually needed 12 | if not MyVehicle then return end 13 | if not MyVehicleConfig.luxartVehicleControlConfig then return end 14 | if not MyVehicleConfig.luxartVehicleControlConfig.useLVC then return end 15 | 16 | local config = MyVehicleConfig.luxartVehicleControlConfig 17 | 18 | -- if sirenId is not in the config, return 19 | if not MyVehicleConfig.luxartVehicleControlConfig[sirenId] then 20 | print("[ULC: LVC_MainSirenStateChange()] siren [" .. 21 | sirenId .. "] is not defined in MyVehicleConfig.luxartVehicleControlConfig") 22 | return false 23 | end 24 | 25 | for _, v in pairs(config[sirenId].enable) do 26 | ULC:SetStage(v, 0, false, false, false, false, false, false) 27 | end 28 | 29 | for _, v in pairs(config[sirenId].disable) do 30 | ULC:SetStage(v, 1, false, false, false, false, false, false) 31 | end 32 | end) 33 | -------------------------------------------------------------------------------- /ulc/client/c_main.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC]: Main Thread Loaded") 2 | 3 | ULC = {} 4 | 5 | ----------------- 6 | -- DEFINITIONS -- 7 | ----------------- 8 | 9 | Lights = false 10 | MyVehicle = nil 11 | MyVehicleConfig = nil 12 | 13 | ------------------------------------ 14 | ------------------------------------ 15 | ------- LIGHTS STATE HANDLER ------- 16 | ------------------------------------ 17 | ------------------------------------ 18 | 19 | if Config.controlLights then 20 | RegisterCommand('ulc:toggleLights', function() 21 | if Lights then 22 | SetVehicleSiren(MyVehicle, false) 23 | else 24 | SetVehicleSiren(MyVehicle, true) 25 | end 26 | end) 27 | 28 | RegisterKeyMapping('ulc:toggleLights', 'Toggle Emergency Lights', 'keyboard', 'q') 29 | end 30 | 31 | AddEventHandler('ulc:lightsOn', function() 32 | --print("Lights On") 33 | -- set Lights on 34 | Lights = true 35 | setDefaultStages() 36 | -- check if parked or driving for park patterns 37 | TriggerEvent('ulc:checkParkState', GetVehiclePedIsIn(PlayerPedId()), false) 38 | SendNUIMessage({ 39 | type = 'toggleIndicator', 40 | state = Lights 41 | }) 42 | if Config.controlLights then 43 | PlayBeep(true) 44 | end 45 | end) 46 | 47 | AddEventHandler('ulc:lightsOff', function() 48 | --print("Lights Off") 49 | Lights = false 50 | SendNUIMessage({ 51 | type = 'toggleIndicator', 52 | state = Lights 53 | }) 54 | if Config.controlLights then 55 | PlayBeep(false) 56 | end 57 | end) 58 | 59 | -- check if lights are on 10 times a second; 60 | -- used to trigger above events 61 | CreateThread(function() 62 | local sleep = 1000 63 | while true do 64 | Wait(sleep) 65 | if not MyVehicle then 66 | sleep = 1000 67 | goto continue 68 | end 69 | sleep = 100 70 | 71 | if not IsPedInAnyVehicle(PlayerPedId()) then goto continue end 72 | if IsVehicleSirenOn(GetVehiclePedIsIn(PlayerPedId())) then 73 | if not Lights then 74 | TriggerEvent('ulc:lightsOn') 75 | end 76 | else 77 | if Lights then 78 | TriggerEvent('ulc:lightsOff') 79 | end 80 | end 81 | 82 | ::continue:: 83 | end 84 | end) 85 | 86 | --------------------------- 87 | --------------------------- 88 | -------- MAIN CODE -------- 89 | --------------------------- 90 | --------------------------- 91 | 92 | -- this event is called whenever player enters vehicle 93 | RegisterNetEvent('ulc:checkVehicle') 94 | AddEventHandler('ulc:checkVehicle', function() 95 | CreateThread(function() 96 | while not GlobalState.ulcloaded do 97 | print("ULC: Waiting for load.") 98 | Wait(250) 99 | end 100 | print("[ULC:checkVehicle] Checking for vehicle configuration") 101 | local ped = PlayerPedId() 102 | local vehicle = GetVehiclePedIsIn(ped) 103 | local passed, vehicleConfig = GetVehicleFromConfig(vehicle) 104 | 105 | --print(passed, vehicleConfig) 106 | 107 | if passed then 108 | MyVehicle = vehicle 109 | MyVehicleConfig = vehicleConfig 110 | table.sort(MyVehicleConfig.buttons, function(a, b) return a["key"] < b["key"] end) 111 | 112 | 113 | print("[ULC:checkVehicle] Found vehicle, ready to go.") 114 | 115 | -- if i am driver 116 | if ped == GetPedInVehicleSeat(vehicle, -1) then 117 | ULC:PopulateButtons(MyVehicleConfig.buttons) 118 | --ShowHelp() 119 | if not Config.hideHud and ClientPrefs.hideUi == 0 then 120 | ULC:SetDisplay(true) 121 | else 122 | print( 123 | "HUD is hidden. Type /ulc to see if you disabled it. Otherwise, the server owner may have disabled the HUD.") 124 | end 125 | 126 | 127 | TriggerEvent('ulc:CheckCruise') 128 | TriggerEvent('ulc:checkParkState', true) 129 | TriggerEvent('ulc:StartCheckingReverseState') 130 | TriggerEvent("ulc:SetupSignalExtrasTable") 131 | currentStage = 0 132 | end 133 | else 134 | MyVehicle = nil 135 | TriggerEvent('ulc:cleanup') 136 | TriggerEvent('ulc:StopCheckingReverseState') 137 | end 138 | end) 139 | end) 140 | 141 | -- used to hide the hud 142 | RegisterNetEvent('ulc:cleanup') 143 | AddEventHandler('ulc:cleanup', function() 144 | -- MyVehicle = nil 145 | -- MyVehicleConfig = nil 146 | ULC:SetDisplay(false) 147 | -- hide hud 148 | -- SendNUIMessage({ 149 | -- type = 'hideLightsHUD', 150 | -- }) 151 | end) 152 | 153 | ----------------- 154 | -- CONFIG SYNC -- 155 | ----------------- 156 | 157 | RegisterNetEvent('UpdateVehicleConfigs', function(newData) 158 | print("[ULC] Updating vehicle table. Done loading.") 159 | Config.Vehicles = newData 160 | end) 161 | 162 | -- trigger checks when spawning from one vehicle into another directly, or from another seat to driver seat 163 | CreateThread(function() 164 | local lastVehicle 165 | local wasDriving 166 | while true do 167 | Wait(500) 168 | if IsPedInAnyVehicle(PlayerPedId()) then 169 | local currentVehicle = GetVehiclePedIsIn(PlayerPedId(), false) 170 | local driving = GetPedInVehicleSeat(MyVehicle, -1) == PlayerPedId() 171 | if currentVehicle ~= lastVehicle then 172 | TriggerEvent('ulc:checkVehicle') 173 | end 174 | if MyVehicle and not wasDriving and driving then 175 | TriggerEvent('ulc:checkVehicle') 176 | end 177 | lastVehicle = currentVehicle 178 | wasDriving = driving 179 | end 180 | end 181 | end) 182 | 183 | 184 | ------------------------- 185 | ------------------------- 186 | -- AUTO REPAIR HANDLER -- 187 | ------------------------- 188 | ------------------------- 189 | 190 | -- every second set no repair on all vehicles except my own 191 | CreateThread(function() 192 | while true do 193 | Wait(1000) 194 | local vehicles = GetGamePool("CVehicle") 195 | for _, v in pairs(vehicles) do 196 | if v ~= GetVehiclePedIsIn(PlayerPedId(), false) then 197 | SetVehicleAutoRepairDisabled(v, true) 198 | else 199 | --print("Enabling repair for" .. v) 200 | SetVehicleAutoRepairDisabled(v, false) 201 | end 202 | end 203 | end 204 | end) 205 | -------------------------------------------------------------------------------- /ulc/client/c_park.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC]: Park Patterns Loaded") 2 | 3 | local veh = GetVehiclePedIsIn(PlayerPedId()) 4 | parked = false 5 | local lastSync = 0 6 | local effectDelay = 1000 7 | 8 | CreateThread(function() 9 | while true do 10 | if IsPedInAnyVehicle(PlayerPedId()) then 11 | TriggerEvent('ulc:checkParkState', veh, false) 12 | 13 | Wait(Config.ParkSettings.delay * 1000) 14 | else 15 | Wait(2000) 16 | end 17 | end 18 | end) 19 | 20 | RegisterNetEvent("ulc:checkParkState", function(delay) 21 | CreateThread(function() 22 | --print('Checking park state') 23 | 24 | if delay then 25 | --print('Delay...') 26 | Wait(5000) 27 | end 28 | local speed = GetVehicleSpeedConverted(MyVehicle) 29 | 30 | 31 | if speed > Config.ParkSettings.speedThreshold and parked then 32 | TriggerEvent("ulc:vehDrive") 33 | end 34 | if speed < Config.ParkSettings.speedThreshold and not parked then 35 | Wait(effectDelay) 36 | if not parked then -- double checks 37 | TriggerEvent('ulc:vehPark') 38 | end 39 | end 40 | end) 41 | end) 42 | 43 | AddEventHandler('ulc:vehPark', function() 44 | if Lights then 45 | --print('[ulc:vehPark] My vehicle is parked.') 46 | parked = true 47 | 48 | if MyVehicle and MyVehicleConfig.parkConfig.usePark then 49 | -- enable pExtras 50 | for _, v in pairs(MyVehicleConfig.parkConfig.pExtras) do 51 | ULC:SetStage(v, 0, false, true, false, false, true, false) 52 | end 53 | -- disable dExtras 54 | for _, v in pairs(MyVehicleConfig.parkConfig.dExtras) do 55 | ULC:SetStage(v, 1, false, true, false, false, true, false) 56 | end 57 | 58 | -- park pattern sync stuff 59 | if MyVehicleConfig.parkConfig.useSync then 60 | -- cooldown 61 | local gameSeconds = GetGameTimer() / 1000 62 | if gameSeconds >= lastSync + Config.ParkSettings.syncCooldown then 63 | lastSync = gameSeconds 64 | 65 | local loadedVehicles = GetGamePool("CVehicle") 66 | --print(#loadedVehicles .. " vehicles in pool") 67 | local vehsToSync = {} 68 | 69 | for k, v in pairs(loadedVehicles) do 70 | -- don't include my vehicle 71 | if v ~= veh then 72 | local vehCoords = GetEntityCoords(v) 73 | local pedCoords = GetEntityCoords(PlayerPedId()) 74 | local distance = GetDistanceBetweenCoords(vehCoords, pedCoords) 75 | 76 | 77 | if distance < Config.ParkSettings.syncDistance then 78 | if GetVehicleClass(v) == 18 then 79 | -- check if my vehicle is set to sync with this vehicle or if the vehicle is the same model as my vehicle 80 | if IsVehicleInTable(v, MyVehicleConfig.parkConfig.syncWith) or GetEntityModel(v) == GetEntityModel(MyVehicle) then 81 | --print('Vehicle' .. v .. ' should sync with me.') 82 | 83 | local speed = GetVehicleSpeedConverted(veh) 84 | 85 | if speed < Config.ParkSettings.speedThreshold then 86 | --print("Found an eligible sync vehicle.") 87 | table.insert(vehsToSync, v) 88 | end 89 | end 90 | end 91 | end 92 | end 93 | end 94 | if #vehsToSync > 0 then 95 | -- sync my vehicle 96 | SetVehicleSiren(veh, false) 97 | SetVehicleSiren(veh, true) 98 | 99 | -- sync other vehicles on my screen 100 | for k, v in pairs(vehsToSync) do 101 | if IsVehicleSirenOn(v) then 102 | SetVehicleSiren(v, false) 103 | SetVehicleSiren(v, true) 104 | end 105 | end 106 | 107 | -- send sync to other clients nearby 108 | --print("Preparing to send sync to server") 109 | local vehsToSyncNet = {} 110 | for k, v in pairs(vehsToSync) do 111 | --print("Candidate: " .. VehToNet(v)) 112 | table.insert(vehsToSyncNet, VehToNet(v)) 113 | end 114 | TriggerServerEvent("sync:send", vehsToSyncNet) 115 | else --print('Found no vehicles to sync.') 116 | end 117 | else 118 | print("Sync on cooldown, time left: " .. 119 | Config.ParkSettings.syncCooldown - (gameSeconds - lastSync) .. " seconds.") 120 | end 121 | end 122 | end 123 | end 124 | end) 125 | 126 | RegisterNetEvent('ulc:sync:receive', function(vehicles) 127 | --print("[sync:receive] Trying to sync " .. #vehicles .. " vehicles.") 128 | for _, v in pairs(vehicles) do 129 | --print("Attempting to sync: " .. NetToVeh(v)) 130 | SetVehicleSiren(NetToVeh(v), false) 131 | SetVehicleSiren(NetToVeh(v), true) 132 | end 133 | end) 134 | 135 | AddEventHandler('ulc:vehDrive', function() 136 | if Lights then 137 | --print('[ulc:vehDrive] My vehicle is driving.') 138 | parked = false 139 | if MyVehicle and MyVehicleConfig.parkConfig.usePark then 140 | -- disable pExtras 141 | for _, v in pairs(MyVehicleConfig.parkConfig.pExtras) do 142 | ULC:SetStage(v, 1, false, true, false, false, true, false) 143 | end 144 | -- enable dExtras 145 | for _, v in pairs(MyVehicleConfig.parkConfig.dExtras) do 146 | ULC:SetStage(v, 0, false, true, false, false, true, false) 147 | end 148 | end 149 | end 150 | end) 151 | -------------------------------------------------------------------------------- /ulc/client/c_reverse.lua: -------------------------------------------------------------------------------- 1 | --print("[ULC]: Reverse Extras Loaded") 2 | 3 | local reversing = false 4 | local disabledExtras = {} 5 | local timerExpired = false 6 | 7 | function setReverseExtras(newState) 8 | -- set enable extras to match the new state 9 | for _, v in ipairs(MyVehicleConfig.reverseConfig.reverseExtras) do 10 | ULC:SetStage(v, newState, false, true, false, false, true, false) 11 | end 12 | if not MyVehicleConfig.reverseConfig.disableExtras then return end 13 | if newState == 0 then 14 | -- set disable extras off and save the ones we changed 15 | for _, v in ipairs(MyVehicleConfig.reverseConfig.disableExtras) do 16 | --print("Checking extra " .. v .. " for reverse state") 17 | if IsVehicleExtraTurnedOn(MyVehicle, v) then 18 | ULC:SetStage(v, 1, false, true, false, false, true, false) 19 | table.insert(disabledExtras, v) 20 | end 21 | end 22 | else -- newState == 1 23 | -- set the disabled extras back on 24 | for _, v in ipairs(disabledExtras) do 25 | ULC:SetStage(v, 0, false, true, false, false, true, false) 26 | end 27 | disabledExtras = {} 28 | end 29 | end 30 | 31 | AddEventHandler('ulc:StartCheckingReverseState', function() 32 | CreateThread(function() 33 | while true do 34 | Wait(250) 35 | --print("Checking reverse state") 36 | if not IsPedInAnyVehicle(PlayerPedId()) then return end 37 | -- this feels unncessary, but I think some people may not have .reverseConfig 38 | if not MyVehicle then return end 39 | if not MyVehicleConfig.reverseConfig then return end 40 | if not MyVehicleConfig.reverseConfig.useReverse then return end 41 | local gear = GetVehicleCurrentGear(MyVehicle) 42 | if gear == 0 then 43 | if not reversing then 44 | startTimer() 45 | reversing = true 46 | setReverseExtras(0) 47 | end 48 | else 49 | if reversing then 50 | reversing = false 51 | setReverseExtras(1) 52 | end 53 | end 54 | end 55 | end) 56 | end) 57 | 58 | -- handle disabling lights after some time 59 | function startTimer() 60 | -- if disabled in config, don't start timer , if enabled or missing config, start timer 61 | if Config and Config.ReverseSettings and not Config.ReverseSettings.useRandomExpiration then return end 62 | -- timer thread 63 | CreateThread(function() 64 | local speed 65 | local duration = math.random(3, 8) * 1000 66 | local expirationTime 67 | 68 | while true do 69 | --print("Reverse timer tick") 70 | if not MyVehicle then return end 71 | if not MyVehicleConfig.reverseConfig then return end 72 | if not MyVehicleConfig.reverseConfig.useReverse then return end 73 | if not reversing then 74 | timerExpired = false 75 | --print("Not reversing") 76 | return 77 | end 78 | 79 | speed = GetVehicleSpeedConverted(MyVehicle) 80 | 81 | if speed < 0.5 then -- if we are in reverse and stopped 82 | if timerExpired then 83 | goto continue 84 | end 85 | if Config and Config.ReverseSettings then 86 | duration = math.random( 87 | (Config.ReverseSettings.minExpiration or 3) * 1000, 88 | (Config.ReverseSettings.maxExpiration or 8) * 1000 89 | ) 90 | end 91 | expirationTime = GetGameTimer() + duration 92 | while GetGameTimer() < expirationTime do 93 | Wait(500) 94 | --print("Reverse timer active") 95 | 96 | if GetVehicleSpeedConverted(MyVehicle) > 1 then 97 | -- print("[ULC] Reverse: Moving, breaking timer") 98 | break 99 | end 100 | if not reversing then 101 | -- print("[ULC] Reverse: Not reversing, breaking timer") 102 | break 103 | end 104 | if not IsPedInAnyVehicle(PlayerPedId()) then 105 | -- print("[ULC] Reverse: Not in vehicle, breaking timer") 106 | break 107 | end 108 | if GetGameTimer() > expirationTime then 109 | print("[ULC] Reverse: Timer expired, disabling extras") 110 | timerExpired = true 111 | setReverseExtras(1) 112 | break 113 | end 114 | end 115 | else -- if we are in reverse and moving 116 | -- print("[ULC] Reverse: Resetting timer") 117 | setReverseExtras(0) 118 | timerExpired = false 119 | end 120 | 121 | ::continue:: 122 | Wait(500) 123 | end 124 | end) 125 | end 126 | -------------------------------------------------------------------------------- /ulc/client/c_signals.lua: -------------------------------------------------------------------------------- 1 | local combinedExtrasTable = {} 2 | 3 | -- combine all extras from signalConfig into a single array 4 | AddEventHandler("ulc:SetupSignalExtrasTable", function() 5 | print('[ulc:SetupSignalExtrasTable] Setting up signal extras table') 6 | 7 | local extras = {} 8 | 9 | 10 | for side, data in pairs(MyVehicleConfig.signalConfig) do 11 | print('[ulc:SetupSignalExtrasTable] Hello? ' .. side) 12 | if side ~= "useSignals" then 13 | for _, extra in ipairs(data.enable) do 14 | table.insert(extras, { extra = extra, side = side, state = "enable" }) 15 | end 16 | for _, extra in ipairs(data.disable) do 17 | table.insert(extras, { extra = extra, side = side, state = "disable" }) 18 | end 19 | end 20 | end 21 | 22 | -- for side, data in pairs(MyVehicleConfig.signalConfig) do 23 | -- print('[ulc:SetupSignalExtrasTable] Hello? ' .. side) 24 | -- if side ~= "useSignals" then 25 | -- for _, extra in ipairs(data.enable) do 26 | -- table.insert(extras, { extra = extra, side = side, state = "enable" }) 27 | -- end 28 | -- for _, extra in ipairs(data.disable) do 29 | -- table.insert(extras, { extra = extra, side = side, state = "disable" }) 30 | -- end 31 | -- end 32 | -- end 33 | 34 | combinedExtrasTable = extras 35 | 36 | print('[ulc:SetupSignalExtrasTable] Combined extras table:', json.encode(combinedExtrasTable)) 37 | end) 38 | 39 | -- IndicatorState and savedExtraStates remain unchanged 40 | IndicatorState = 0 41 | local savedExtraStates = {} 42 | 43 | local function saveExtraStates() 44 | -- get states of all extras listed in signalConfig 45 | local extras = {} 46 | for _, side in pairs({ "left", "right", "hazard" }) do 47 | for _, extra in pairs(MyVehicleConfig.signalConfig[side].enable) do 48 | extras[extra] = true 49 | end 50 | for _, extra in pairs(MyVehicleConfig.signalConfig[side].disable) do 51 | extras[extra] = false 52 | end 53 | end 54 | 55 | -- save the current state of all extras listed in signalConfig 56 | for extra, _ in pairs(extras) do 57 | savedExtraStates[extra] = IsVehicleExtraTurnedOn(MyVehicle, extra) 58 | end 59 | end 60 | 61 | local function checks() 62 | if not IsPedInAnyVehicle(PlayerPedId()) then return false end 63 | if not MyVehicle then return false end 64 | if not MyVehicleConfig then return false end 65 | if not MyVehicleConfig.signalConfig then return false end 66 | if not MyVehicleConfig.signalConfig.useSignals then return false end 67 | return true 68 | end 69 | 70 | -- loop to get state of indicator lights 71 | CreateThread(function() 72 | local sleep = 500 73 | local oldState 74 | local newState 75 | while true do 76 | if not checks() then 77 | sleep = 500 78 | goto continue 79 | end 80 | 81 | sleep = 20 82 | oldState = IndicatorState 83 | newState = GetVehicleIndicatorLights(GetVehiclePedIsIn(PlayerPedId(), false)) 84 | if newState ~= IndicatorState then 85 | print('[ulc:indicatorStateChanged] Indicator state changed from', oldState, 'to', newState) 86 | TriggerEvent('ulc:indicatorStateChanged', newState, oldState) 87 | IndicatorState = newState 88 | end 89 | 90 | ::continue:: 91 | Wait(sleep) 92 | end 93 | end) 94 | 95 | AddEventHandler("ulc:indicatorStateChanged", function(newIndicatorState, oldIndicatorState) 96 | if not checks() then return end 97 | local config = MyVehicleConfig.signalConfig 98 | 99 | -- action = 1 to disable, 0 to enable 100 | local function setIndicatorExtras(action, indicatorStateName) 101 | -- loop through the combined extras table 102 | for _, data in ipairs(combinedExtrasTable) do 103 | if data.side ~= indicatorStateName then return end 104 | if data.state == "enable" then 105 | if action == 0 then 106 | print('[ulc:indicatorStateChanged] Enabling extra', data.extra) 107 | ULC:SetStage(data.extra, 0, true, false, false, false, false, false) 108 | elseif action == 1 then 109 | print('[ulc:indicatorStateChanged] Disabling extra', data.extra) 110 | ULC:SetStage(data.extra, 1, true, false, false, false, false, false) 111 | end 112 | elseif data.state == "disable" then 113 | if action == 0 then 114 | print('[ulc:indicatorStateChanged] Disabling extra', data.extra) 115 | ULC:SetStage(data.extra, 1, true, false, false, false, false, false) 116 | elseif action == 1 then 117 | print('[ulc:indicatorStateChanged] Enabling extra', data.extra) 118 | ULC:SetStage(data.extra, 0, true, false, false, false, false, false) 119 | end 120 | end 121 | end 122 | end 123 | 124 | if newIndicatorState == 0 then 125 | setIndicatorExtras(1, "left") 126 | setIndicatorExtras(1, "right") 127 | setIndicatorExtras(1, "hazard") 128 | elseif newIndicatorState == 1 then 129 | setIndicatorExtras(0, "left") 130 | setIndicatorExtras(1, "right") 131 | setIndicatorExtras(1, "hazard") 132 | elseif newIndicatorState == 2 then 133 | setIndicatorExtras(1, "left") 134 | setIndicatorExtras(0, "right") 135 | setIndicatorExtras(1, "hazard") 136 | elseif newIndicatorState == 3 then 137 | setIndicatorExtras(0, "left") 138 | setIndicatorExtras(0, "right") 139 | setIndicatorExtras(1, "hazard") 140 | end 141 | end) 142 | -------------------------------------------------------------------------------- /ulc/client/c_stages.lua: -------------------------------------------------------------------------------- 1 | print("[ULC] Stages Loaded") 2 | 3 | -- Definitions 4 | -- # TODO maybe currentStage should not be 0 when entering a vehicle? 5 | --[[ # TODO this file does not handle setting the currentStage, instead ULC:SetStage does, 6 | may want to change this if possible, leads to hacky stuff like in CycleStage function ]] 7 | -- this is set to 0 whenever ulc:checkVehicle is triggered 8 | currentStage = 0 9 | 10 | 11 | -- helpers 12 | local function checks() 13 | if not MyVehicle then return false end 14 | if not MyVehicleConfig.stages then return false end 15 | if not MyVehicleConfig.stages.useStages then return false end 16 | if not MyVehicleConfig.stages.stageKeys then return false end 17 | return true 18 | end 19 | 20 | function getMaxStage() 21 | if not MyVehicle then return end 22 | if not MyVehicleConfig.stages then return end 23 | if not MyVehicleConfig.stages.stageKeys then return end 24 | return #MyVehicleConfig.stages.stageKeys 25 | end 26 | 27 | -- main functions 28 | function stageUp() 29 | print("[ULC:stageUp] Increasing stage from " .. currentStage) 30 | if currentStage == getMaxStage() then 31 | print("Already at max stage") 32 | return 33 | end 34 | 35 | -- this is handled in ULC:SetStage? 36 | -- currentStage = currentStage + 1 37 | -- instead we just want to keep track locally 38 | local nextStage = currentStage + 1 39 | 40 | -- # TODO might want to check if the key is actually a button that exists (maybe do this in the initial checks when script starts?) 41 | local key = MyVehicleConfig.stages.stageKeys[nextStage] 42 | 43 | local extra = GetExtraByKey(key) 44 | local button = GetButtonByExtra(extra) 45 | if not button then 46 | print("[stageUp()] Could not find button for extra " .. extra) 47 | return 48 | end 49 | print("Setting stage to: " .. nextStage .. " using key " .. key .. " with extra " .. extra) 50 | ULC:SetStage(extra, 0, true, false, button.repair or false, false, true, false) 51 | end 52 | 53 | function stageDown() 54 | print("[ULC:stageDown] Decreasing stage from " .. currentStage) 55 | if currentStage == 0 then 56 | print("Already stage 0") 57 | return 58 | end 59 | 60 | -- this is handled in ULC:SetStage? 61 | -- currentStage = currentStage - 1 62 | -- instead we just want to keep track locally 63 | local nextStage = currentStage - 1 64 | 65 | -- # TODO might want to check if the key is actually a button that exists (maybe do this in the initial checks when script starts?) 66 | local key = MyVehicleConfig.stages.stageKeys[nextStage] 67 | local extra = GetExtraByKey(key) 68 | local button = GetButtonByExtra(extra) 69 | if not button then 70 | print("[stageDown()] Could not find button for extra " .. extra) 71 | return 72 | end 73 | print("Setting stage to: " .. nextStage .. " using key " .. key .. " with extra " .. extra) 74 | ULC:SetStage(extra, 0, true, false, button.repair or false, false, true, false) 75 | end 76 | 77 | function cycleStages() 78 | if not checks() then return end 79 | if currentStage == getMaxStage() then 80 | print("Tried to cycle stages at max stage, resetting to 0") 81 | -- we need to turn off the current stage 82 | local key = MyVehicleConfig.stages.stageKeys[currentStage] 83 | local extra = GetExtraByKey(key) 84 | local button = GetButtonByExtra(extra) 85 | if not button then 86 | print("[cycleStages()] Could not find button for extra " .. extra) 87 | return 88 | end 89 | print("Setting stage to: 0 using key " .. key .. " with extra " .. extra) 90 | ULC:SetStage(extra, 1, true, false, button.repair or false, true, true, false) 91 | return 92 | end 93 | stageUp() 94 | end 95 | 96 | -- Keybinds 97 | RegisterKeyMapping("ulc:stage_down", "ULC: Stage Down", "keyboard", "SUBTRACT") 98 | RegisterCommand("ulc:stage_down", function() 99 | if not checks() then return end 100 | stageDown() 101 | end) 102 | 103 | RegisterKeyMapping("ulc:stage_up", "ULC: Stage Up", "keyboard", "ADD") 104 | RegisterCommand("ulc:stage_up", function() 105 | if not checks() then return end 106 | stageUp() 107 | end) 108 | 109 | RegisterKeyMapping("ulc:stage_cycle", "ULC: Cycle Stages", "keyboard", "NUMPAD0") 110 | RegisterCommand("ulc:stage_cycle", function() 111 | if not checks() then return end 112 | cycleStages() 113 | end) 114 | 115 | -------------------- 116 | -------------------- 117 | -- DEFAULT STAGES -- 118 | -------------------- 119 | -------------------- 120 | 121 | -- checks if the button.key is contained in the stageKeys array and returns the index 122 | function getStageFromButton(button) 123 | if not button then return false end 124 | if not MyVehicle then return false end 125 | -- if MyVehicleConfig.stages.stageKeys is nil or doesn't contain the button.key return false 126 | if not MyVehicleConfig.stages.stageKeys then return false end 127 | for i, key in pairs(MyVehicleConfig.stages.stageKeys) do 128 | if key == button.key then 129 | return i 130 | end 131 | end 132 | return false 133 | end 134 | 135 | function setDefaultStages() 136 | -- default stages 137 | if not MyVehicleConfig.defaultStages or false then return end 138 | if not MyVehicleConfig.defaultStages.useDefaults then return end 139 | for _, e in pairs(MyVehicleConfig.defaultStages.enableKeys) do 140 | local button = GetButtonByExtra(GetExtraByKey(e)) 141 | if not button then break end 142 | -- if the button is a stage 143 | local stage = getStageFromButton(button) 144 | -- if the index of this stage = the current stage do nothing 145 | if stage and stage == currentStage then return end 146 | -- if this button is not a stage or the stage is not the current stage proceed normally 147 | ULC:SetStage(GetExtraByKey(e), 0, false, false, button.repair, true, true, false) 148 | end 149 | for _, d in pairs(MyVehicleConfig.defaultStages.disableKeys) do 150 | local button = GetButtonByExtra(GetExtraByKey(d)) 151 | if not button then break end 152 | ULC:SetStage(GetExtraByKey(d), 1, false, false, button.repair, true, true, false) 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /ulc/config.lua: -------------------------------------------------------------------------------- 1 | -- Ultimate Lighting Controller by Dawnstar FiveM 2 | -- Written by Dawnstar 3 | -- Documentation: https://docs.dwnstr.com/ulc/overview 4 | -- For support: https://discord.gg/dwnstr-fivem 5 | 6 | -- Most of these can be left at their default values. 7 | -- View documentation for details on each value 8 | Config = { 9 | -- whether to enable control of lights on/off state using Q key 10 | -- disabled by default to allow other scripts to control lights such as Luxart 11 | -- make sure to disable light controls in other scripts if you enable this 12 | controlLights = false, 13 | 14 | -- HUD SETTINGS 15 | -- global toggle for UI (affects all clients) 16 | hideHud = false, 17 | -- whether to use KPH instead of MPH 18 | useKPH = false, 19 | 20 | -- Park Pattern Settings; 21 | ParkSettings = { 22 | -- extras will toggle below this speed 23 | speedThreshold = 1, 24 | -- time between checks in seconds 25 | -- should not be any lower than .5 seconds 26 | delay = 0.5, 27 | -- distance at which to check for other vehicles to sync patterns with 28 | syncDistance = 32, 29 | -- seconds before a single client triggers sync again 30 | syncCooldown = 4, 31 | }, 32 | 33 | -- Steady Burn Config; 34 | -- changes settings for extras that are enabled at night, or enabled all the time. 35 | SteadyBurnSettings = { 36 | -- hour effect starts (extras are enabled) 37 | nightStartHour = 18, 38 | -- hour effect ends (extras are disabled) 39 | nightEndHour = 6, 40 | }, 41 | 42 | -- Brake Extras/Patterns Config; 43 | -- temporarily empty as of v1.3.0 44 | BrakeSettings = {}, 45 | 46 | -- Reverse Extras/Patterns Config; 47 | -- introduced in v1.8.0 48 | ReverseSettings = { 49 | -- these options control the expiration of the reverse extras 50 | -- if enabled, reverse extras will turn off after a random time between min and max 51 | -- this is to simulate more realistic behavior where the vehicle would shifted out of reverse 52 | -- after being stopped for some time 53 | useRandomExpiration = true, 54 | -- minimum time in seconds extras will stay on after stopping 55 | minExpiration = 3, 56 | -- maximum time in seconds extras will stay on after stopping 57 | maxExpiration = 8, 58 | }, 59 | 60 | -- Import confiurations here 61 | -- Add the resource names of vehicle resources that include a ulc.lua config file 62 | ExternalVehResources = { 63 | -- ex. "my-police-vehicle", 64 | }, 65 | 66 | Vehicles = { 67 | -- this is not required! 68 | -- see documentation for instructions! 69 | -- https://docs.dwnstr.com/ulc/configuration 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ulc/fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | games { 'gta5' } 3 | lua54 'yes' 4 | 5 | name "Ultimate Lighting Controls" 6 | description "The ultimate non-els lighting controller. Documentation: https://docs.dwnstr.com/ulc/overview" 7 | author "Dawnstar" 8 | version "1.9.0" 9 | 10 | ui_page "html/index.html" 11 | 12 | files { 13 | "html/index.html", 14 | "html/assets/*.js", 15 | "html/assets/*.css", 16 | "html/assets/*.png", 17 | "html/assets/*.jpg" 18 | } 19 | 20 | dependencies { 21 | "baseevents", 22 | "/onesync" 23 | } 24 | 25 | shared_scripts { 26 | 'config.lua', 27 | 'shared/shared_functions.lua' 28 | } 29 | 30 | client_scripts { 31 | 'client/c_main.lua', 32 | 'client/c_hud.lua', 33 | 'client/c_buttons.lua', 34 | 'client/c_brake.lua', 35 | 'client/c_blackout.lua', 36 | 'client/c_cruise.lua', 37 | 'client/c_horn.lua', 38 | 'client/c_park.lua', 39 | 'client/c_doors.lua', 40 | 'client/c_reverse.lua', 41 | 'client/c_stages.lua', 42 | 'client/c_beeps.lua', 43 | 'client/c_signals.lua' 44 | 45 | } 46 | 47 | server_scripts { 48 | 'server/s_main.lua', 49 | 'server/s_main.js', 50 | 'server/s_blackout.lua', 51 | } 52 | -------------------------------------------------------------------------------- /ulc/html/assets/button_off.5152e6d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/html/assets/button_off.5152e6d3.png -------------------------------------------------------------------------------- /ulc/html/assets/button_on_amber.bb2616bf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/html/assets/button_on_amber.bb2616bf.png -------------------------------------------------------------------------------- /ulc/html/assets/button_on_blue.2277a1b4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/html/assets/button_on_blue.2277a1b4.png -------------------------------------------------------------------------------- /ulc/html/assets/button_on_green.536ce497.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/html/assets/button_on_green.536ce497.png -------------------------------------------------------------------------------- /ulc/html/assets/button_on_red.2fb72a77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/html/assets/button_on_red.2fb72a77.png -------------------------------------------------------------------------------- /ulc/html/assets/image.7105ce7a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/html/assets/image.7105ce7a.png -------------------------------------------------------------------------------- /ulc/html/assets/index.e1c6fa6f.css: -------------------------------------------------------------------------------- 1 | #root{background-color:#0000!important}body{background-color:#0000!important;overflow:hidden}.background{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:2px;border:10px solid;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/wAALCAB9AXMBAREA/8QAGAABAQEBAQAAAAAAAAAAAAAAAQACAwj/xAAuEAEAAgICAgEEAgIBBAMBAAABABECITFBElFhInGBkTKhQrEDYsHR4RMj8fD/2gAIAQEAAD8A8J42YtNb75l4tUZSp4xrJqplckXx4/qNniBdc3ERav8AHuQXurDmZy3sbJod1d/Ezi1/I+3zNFcVcMf462h6i+Ti14/DBxoxxG799McvpxQdXv5jf0pjVdyvG/puuRqZS8vpy44mmtj+Jn+PG/deoje+33JLu8rr+pXk3kwF/j17jzqte5ZB5UuviT40W1Bo1sPtKrachvj4g3Wi6JrwxyBuopaW/wBwq29GoKa99y5b3GrNvPuZSr1+Y1448rEW/GlZZeXDzXEyfUod631NY1f1D6v3LMtNfiHxVeviNeORjlz16ZfU5Vkc9y+k/wAIOTbpTrUhKW25pycSkG+iFfSrs4ldlV9rlincqeQ169S/y8qbNS/lz+/UtFt2HUQ7dHUHbTweoIU46qLqmoWGq5kmgqvmVcApvccjFOO4UWmQytoot/qTibB2aWPP27lqzHHJcXm+JLj5bqv9SscUTXUmqH+pCXvriX1PZUhpS7gYmXGPEq1qO3TK601VQKOaN69xfqb8twLCo3qlqSvHH4h2/O47/B3K/v8AmQ5GqqVZemvcj+vvAbQ8rPfqN2Nr+pXh3m395CdGuTcsrb+k3JPFD8LcsrXx8afhgvXUdaAqVOTdu9QvKqdH35imPDKrLvUn6mv0yu2q+/zM0nN0++prICmm5G1v8sy1bXTv5mq1Ybgpj49BHJHv8yy8V7uqlrjE3fHxAsfqb+Zp48rpOYULeOMEyHjbHxU5+8P4Nv4qNWb59w8XyvnXMdn01f5gVWzfGoprVyQNetyEWkPzDI5R44ia4v3Is3q3mTZfAcyE3r5JWAnk/ZgfTqkuaqjbcH0d6hnjTVuv9xVtKp/3FUHrrUz4/wDH3/x4vzH7W9VK1f8A4w2xfltNZahtydfvuWzK9f7kLxY+ggq8e+Ipqqqoc6e4/Vjl9QK+oK1633FoCuTmVN2lD2y07b3xDJvKm/vIA657+ZH1KqESr21R+4BV1sd8xBRADvcFb3R7qOPDvRC8dHJ7icf3cLv6r+r1xDGsVzxuLffFXGgdMjxxK3bz6gd5Y6fmJkHR+ZVzanWpYlKbr7SdvFVKgvf4OJe/1IKbrVRxr1YTFeFvlr5iiHkY79+ojuWjiRv6izqoNjbupV4uldyK/kLfqV49ivuWOX005VkPUeBTvqWLWVlHx3JyBvK2pUnO5UDZr2yxbbSpW1w7hWSdnUQvf9epUJ45bYIelriuprxvdw8vIb4hQGy/n1NfS/Uu/wDXzMFfy0WzSPFP7ljRlWPfJGy0GnGWvdfEzi7vi+pYpvcaxyL57hoRv4i2NnTf3l4qrenuVlq16kI2tvqFcY5PGyoujfEsjET6mjj1G/Ra+4a97ku64D11B3vg/wByFW7+xxcN3WRXUdtKf+5F5b8guAf5dn+pqtX1UMsjRdyQtxR+IpiHa/MUXi77g5F7yIZGx49RH2329TJb9r/UU3cRW9PGpeNA3qSb8krXbHy3AX3ZJd8B6lZVOnu4DZd7NJXMVWi6Hg9SDHK7WyHqjVXT3HbS40+mJSfVy+iYbbt7laeueI1lTrddxTWzfLLuy7OoXYlWPUTl8cbmcg559vqauwTXuAOKULfMbMd0PTLL6j6WumXGvED37lunV113DJxSjTdMeb41zLL6m6vV3LINVv2Sv6a9Sza2UxyMchcR2cTIlONa1G9CBuD5B66kKlZO+I6DhZb65P1JXV8QE23x1IWtkbTg16l/ls/ENg2c6jW9rYS/jiKfZkAm7bf1L6e9WQQPXHEtcZFXHGnl6/UHIQPXcrcS3fcsUdpvu+41w3XolT5XelqBl1+Iq4Ypia7gcAl66ZfSVeO/9R7PGyWuktkmVe26WDWAN88ybTy2MecS8f13Ftcen1B1q7eqhldDf9RUocv1DI7yWgriI0+RiVC+mx9yN47/ABUVD+XHFy5XI/vuZKy2th/uaTj/AJKouqjk1b409VCr+VOZao8uDuBocabiPlzlTxG7aIbq71cjduWNsHmrpiePx+WVUY1uKgfVu2FB9KjvmZdPOj9zdBxVMy5N/VqNN/VnuH8vpEOKuL3rffzC7UxDiOIGqNyVyemuoXVaF7h9OTpo+YhjWjj3EG/iTd74f3DVgPxF38svLxy3Tq5COkr/AHAtzTs43G/EePkkIv0dnEtjdcf1A8tVVP8AcdGKb3w+pHLuwbtkuVUVqRk2rf4ippeHmA60NfMt8Y5B/okbzHJsr1xL6ey336iBvn9QQByxeOpB5U4qQPN1gvzEu+pZapcnTHPH6r5f9Q8vLvbJLryNJ1HQW4oe/cMini79vUKehkc1l+Jq0RyeYBze/Ul2fS3xLhpNdQoMXlD3BW9JNJg7rcmsW391JK7qFuWWiWt6BmkNdfaZcXh4i48GJQwxXL0X7kiuv/2BjfJ4nNfM3jZan5OoJuxCVW1lo5uVUuKA8XIMsbF+0gbXZ8kgOOLkYgVjetwrK68j7yxvx/j3z7ldUU0x/koofLDJs4p4PmVJVn/iVt3yeoi3ZIKW+27ljSNZ9bmTxMDx2vfuaQBxHq5Y5fTSprmRldAVWncsqsKZeWOFbGiBWWJ4rvt6kZdoDEKb+P6liXdOgh43tMvxIyOixj5Pidvcmrlzz3Kt27OSWK8X/wC5dC1fHEGubeZcNZO4v7GBpBt/7RcS0b13DQ+Knu77jtpu/mSZIKijvcgDTsiCl8188TOLYpjRBVPqK+Dua4u0fUNccvqQWOnjUa+kcsdQHyfLAKPmRpTLH5+8TKsRMrd6lju15r9shaciqgNdcxQzRsK6kNrVUdrC26E/7R+iw2alj9X15Na4JXbVUfqId4ifBM1eP08HTG3l5kZPZzy1Mh7bP9zbRj1fZcyjfB+pHGymNnAX3fzDoOA7llugxRvmdPMNb/cyFvJvqZpuvLXLEMczzG6hrHVXL6dPFRtMd7LuZKMhLmhOj6iN+TwC8kzy60HqRyqgMRfFLo9MaXE2b4gWqa+Kkad6gc2DqQ5L5Hem42tiX/UrNVv5l4jkFa+8PEcrv/1EtdvzqW2u92yFy9epXkljdfEnGsdc8yyWikR4IJ1evjuLkm646l/Ksv45RQy+nKphvD6HYf3FP+lfUKLsNfE0hknNVwwyCy73xLELBcn5i3ldX+odULfsjjkL3UMqvf3iPR+u5LZRjdf1L4CrKIeKNm5rVI8QFr+P7gvAO+DqRSaGzm5Yj/jiW7ify03bUtKgOmpPL1BunyfGWI97oipTYssnE3WoWt3x6k1VGiu4uwC6+fcqfdsHnE9SuiscartgmrHZ8zVvH+Pct8FLKjuh7hYNXp028yNNYEaH+OP3lVv8dknzHxxLfhjQPgN93eoNiW0SqsrW37QUe8tdRHz2de+oGNvj7ZJV7V9vUcfqL4+ZdFP3kp/5H3B2UXchHV38zWKYt4/mQ8un+pmg0tvv1EKoy/cLF07iXdLQ61JE15LUsf5cJ8fML5DHvdwyq3/639zek+mr/wBzI4+XPOmX/U/b8RDGvIys6fUvKji5dcTOlR6iBeg/cgFMQ7ip5eWQc0ysLEN+oZKPHXMsfHksvgioOn7SVFxXfG+octt6ivI8/EFMevhuP09cWQ8xzqtLykg5xQ1/qOXIVXcXnyP1M+VL5Wj/AFED7Udeob1uq9Ecst3Ip26vkuWk9Qy0AqYvEby1i8Ywpd1vrfUaMnjjmoGFZX+mIeJfJI+oUy43uB5XVEi+cooFfeV1o59HcAff7l2PDNWXTVPZBR5/ELttPtuO/J1dcwt6xyYlvYHqColH4qLt3zVMqfipWn5hxvv0RTX5tl/J8ud6kVe71Isu8effUqxNJZKxaDRx8Q7tV+8qNeJUfHFaXRz7YNe9Q4y+o3NNLdG+Y65CpcF6x/8A7mZ5pCv+8lfJ3bLfipdsXeJvjVQNcj92W8lTS6k1QvEsVu60R8cDHfcT6y+zpmW8st8yyRft6iLXXjdcyDd9HPzK7T6eOWGjQb/3LfvmPx+Ilp4qfrcAvXlaakthv4bh7X9EsbU4361EKPFbX3KuLIC2e+NTRYV/qZcf8q/MW13keR2x+lVbB+YPAVLLTq7i3dePPuFU2Y2v6INhzp5kjS5H2qaCw1+YLT41err1B2Xu+wkNFnL/AFNGshMrs2+oK1VDvdS8dfB3Lb1z/Utp40XIb0fy7lXJ+PvB4q91zEbxqyGrfZJ3F82l+/Mi7c7OIaxODn9ROPfxIvsLeJV3XxDVcstgcPcSnLj/APIXa44xDXlfMyK/y79TTTvKuZKPGXEF+pcTRGuMqgK/V2PqL5G9fDBvIqu5o1xCqusud7l46QLfiZ8snfkTT9N17hTsuIPv4gJkn/G46Nyw4o4NRRq/J1qZ8qKS98zedY5JV2TOGVpRVx8TaKV6liV5/iK0uPQSxRArqH/IGAJ3zJxxryqFfUt8RxL5eZYl5iaeJZhjmmOqg8faN7b33LzvnE5qFeL49Go8f8bkRfpwv+peIAn+RbMoLXFFzQDh5cUTGTXyXxNGI1fcgXJxuqGNUePOpgyWvvNFnD3Jtyq/tK0PlZny89JXU1R5eIVKryBZNt745+YZPLFKxX8VI4xyvnmXktq91B/ljrTE9b4gf86acB+Z/9k=);border-radius:20px;border-image:url(./image.7105ce7a.png) 20 repeat;transition:all .5s ease}.buttons{display:flex;flex-direction:row}.ta{height:24px;width:100%;display:flex;flex-direction:row;justify-content:center;align-items:center}.ta-off{display:none}.button{aspect-ratio:144/121;width:65px;display:flex;justify-content:center;align-items:center;text-align:center;background-image:url(./button_off.5152e6d3.png);background-size:contain;background-repeat:no-repeat;margin:4px;padding:5px;color:#515151;font-family:Arial,Helvetica,sans-serif;font-weight:800;font-size:10pt;line-height:14px}.blue{background-image:url(./button_on_blue.2277a1b4.png);color:#4e598f}.red{background-image:url(./button_on_red.2fb72a77.png);color:#95665d}.amber{background-image:url(./button_on_amber.bb2616bf.png);color:#948255}.green{background-image:url(./button_on_green.536ce497.png);color:#596a2d} 2 | -------------------------------------------------------------------------------- /ulc/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ulc/html/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ulc/server/s_blackout.lua: -------------------------------------------------------------------------------- 1 | 2 | RegisterNetEvent('ulc:setBlackout', function(netId, state) 3 | print("[ULC] Setting blackout to " .. tostring(state) .. " on vehicle " .. tostring(netId)) 4 | local vehicle = NetworkGetEntityFromNetworkId(netId) 5 | Entity(vehicle).state.ulc_blackout = state 6 | end) 7 | 8 | -------------------------------------------------------------------------------- /ulc/server/s_lvc.lua: -------------------------------------------------------------------------------- 1 | print("[ULC] LVC Integrations Loaded") 2 | 3 | -- triggers when player changes main siren state in LVC 4 | -- newState is an int representing the index of a siren in lvc/SIRENS.lua:SIRENS 5 | RegisterNetEvent("lvc:SetLxSirenState_s") 6 | AddEventHandler("lvc:SetLxSirenState_s", function(newState) 7 | local src = source 8 | print("[lvc:SetLxSirenState_s] " .. src .. " " .. newState) 9 | TriggerClientEvent("ulc:LVC_MainSirenStateChange", src, newState) 10 | end) 11 | 12 | -- TODO: this isn't use anywhere yet 13 | -------------------------------------------------------------------------------- /ulc/server/s_main.js: -------------------------------------------------------------------------------- 1 | // This API died :( 2 | 3 | // https.get('https://api.countapi.xyz/hit/dwnstr.com/ulcloadcount/', (res) => { 4 | // const { statusCode } = res; 5 | // const contentType = res.headers['content-type']; 6 | 7 | // let error; 8 | // // Any 2xx status code signals a successful response but 9 | // // here we're only checking for 200. 10 | // if (statusCode !== 200) { 11 | // error = new Error('Request Failed.\n' + 12 | // `Status Code: ${statusCode}`); 13 | // } else if (!/^application\/json/.test(contentType)) { 14 | // error = new Error('Invalid content-type.\n' + 15 | // `Expected application/json but received ${contentType}`); 16 | // } 17 | // if (error) { 18 | // // console.error(error.message); 19 | // // Consume response data to free up memory 20 | // res.resume(); 21 | // return; 22 | // } 23 | 24 | // res.setEncoding('utf8'); 25 | // let rawData = ''; 26 | // res.on('data', (chunk) => { rawData += chunk; }); 27 | // res.on('end', () => { 28 | // try { 29 | // const parsedData = JSON.parse(rawData); 30 | // console.log(`[ULC] ULC has been loaded on servers ${parsedData.value} times :D`); 31 | // } catch (e) { 32 | // // console.error(e.message); 33 | // } 34 | // }); 35 | // }).on('error', (e) => { 36 | // // console.error(`Got error: ${e.message}`); 37 | // }); 38 | 39 | // // Create a local server to receive data from 40 | // const server = http.createServer((req, res) => { 41 | // res.writeHead(200, { 'Content-Type': 'application/json' }); 42 | // res.end(JSON.stringify({ 43 | // data: 'Hello World!', 44 | // })); 45 | // }); 46 | -------------------------------------------------------------------------------- /ulc/server/s_main.lua: -------------------------------------------------------------------------------- 1 | print("Server thread loaded.") 2 | 3 | AddEventHandler('ulc:error', function(error) 4 | print("^1[ULC ERROR] " .. error) 5 | end) 6 | 7 | AddEventHandler('ulc:warn', function(error) 8 | print("^3[ULC WARNING] " .. error) 9 | end) 10 | 11 | local myVersion = GetResourceMetadata("ulc", "version", 0) 12 | local latestVersion = '' 13 | 14 | if GetCurrentResourceName() ~= 'ulc' then 15 | TriggerEvent('ulc:error', "Resource is named incorrectly. Version checks will not work.") 16 | end 17 | 18 | --TODO change loading state to use this instead of events 19 | GlobalState.ulcloaded = false 20 | 21 | PerformHttpRequest("https://api.github.com/repos/Flohhhhh/ultimate-lighting-controller/releases/latest", 22 | function(errorCode, resultData, resultHeaders) 23 | print("[ULC] My Version: [" .. myVersion .. "]") 24 | 25 | local errorString = tostring(errorCode) 26 | if errorString == "403" or errorString == "404" then 27 | print("Got code " .. errorString .. " when trying to get version.") 28 | return 29 | end 30 | 31 | latestVersion = json.decode(resultData).name 32 | print("^0[ULC] Latest Version: [" .. latestVersion .. "]") 33 | 34 | print([[ 35 | ___ ___ ___ ________ 36 | |\ \|\ \ |\ \ |\ ____\ 37 | \ \ \\\ \\ \ \ \ \ \___| 38 | \ \ \\\ \\ \ \ \ \ \ 39 | \ \ \\\ \\ \ \____ \ \ \____ 40 | \ \_______\\ \_______\\ \_______\ 41 | \|_______| \|_______| \|_______| 42 | 43 | ULTIMATE LIGHTING CONTROLLER 44 | by Dawnstar 45 | ^2Loaded 46 | ]]) 47 | if myVersion and ("v" .. myVersion) == latestVersion then 48 | print('[ULC] Up to date!') 49 | else 50 | print("^1[ULC] OUTDATED. A NEW VERSION (" .. latestVersion .. ") IS AVAILABLE.^0") 51 | print("^1[ULC] YOUR VERSION: " .. myVersion .. "^0") 52 | print("[ULC] GET LATEST VERSION HERE: https://github.com/Flohhhhh/ultimate-lighting-controller/releases/") 53 | end 54 | end) 55 | 56 | 57 | local function IsIntInTable(table, int) 58 | for k, v in ipairs(table) do 59 | if v == int then 60 | return true 61 | end 62 | end 63 | return false 64 | end 65 | 66 | if Config.ParkSettings.delay < 0.5 then 67 | TriggerEvent("ulc:warn", 68 | 'Park Pattern delay is too short! This will hurt performance! Recommended values are above 0.5s.') 69 | end 70 | 71 | -- removed v1.7.0 72 | -- if Config.SteadyBurnSettings.delay <= 2 then 73 | -- TriggerEvent("ulc:error", 'Steady burn delay is too short! Steady burns will be unstable or not work!') 74 | -- end 75 | 76 | if Config.SteadyBurnSettings.nightStartHour < Config.SteadyBurnSettings.nightEndHour then 77 | TriggerEvent("ulc:error", 'Steady burn night start hour should be later/higher than night end hour.') 78 | end 79 | 80 | -- removed v1.7.0 81 | -- if Config.SteadyBurnSettings.delay < 2 then 82 | -- TriggerEvent("ulc:error", "Steady burn check delay can never be lower than 2 seconds. Will cause stability issues.") 83 | -- end 84 | 85 | local function CheckData(data, resourceName) 86 | if not data.name and not data.names then 87 | TriggerEvent("ulc:error", "^1Vehicle config in resource \"" .. resourceName .. "\" does not include model names!^0") 88 | return false 89 | elseif data.name then 90 | TriggerEvent("ulc:warn", 91 | "^1Vehicle config in resource \"" .. 92 | resourceName .. "\" uses deprecated 'name' field. Change to > names = {'yourvehicle'}^0") 93 | if type(data.name) ~= "string" then 94 | TriggerEvent("ulc:error", 95 | "^1Vehicle config in resource \"" .. resourceName .. "\" 'name' field can only accept a string.^0") 96 | return false 97 | end 98 | elseif data.names then 99 | if type(data.names) ~= "table" then 100 | TriggerEvent("ulc:error", 101 | "^1Vehicle config in resource \"" .. resourceName .. "\" 'names' field can only accept a table of strings.^0") 102 | return false 103 | end 104 | end 105 | 106 | -- check if data is missing 107 | if not data.parkConfig or not data.brakeConfig or not data.buttons or not data.hornConfig then 108 | TriggerEvent("ulc:error", 109 | "^1Vehicle config in resource \"" .. resourceName .. "\" is missing data or not formatted properly. View docs.^0") 110 | return false 111 | end 112 | 113 | -- check if steady burns are enabled but no extras specified 114 | if (data.steadyBurnConfig.forceOn or data.steadyBurnConfig.useTime) and #data.steadyBurnConfig.sbExtras == 0 then 115 | TriggerEvent("ulc:warn", 116 | 'A config in "' .. resourceName .. '" uses Steady Burns, but no extras were specified (sbExtras = {})') 117 | end 118 | 119 | -- check if park pattern enabled but no extras specified 120 | if data.parkConfig.usePark then 121 | if #data.parkConfig.pExtras == 0 and #data.parkConfig.dExtras == 0 then 122 | TriggerEvent("ulc:warn", 123 | 'A config in "' .. 124 | resourceName .. '" uses Park Patterns, but no park or drive extras were specified (pExtras = {}, dExtras = {})') 125 | end 126 | end 127 | 128 | -- check if brakes enabled but no extras specified 129 | if data.brakeConfig.useBrakes and #data.brakeConfig.brakeExtras == 0 then 130 | TriggerEvent("ulc:warn", 131 | 'A config in "' .. resourceName .. '" uses Brake Pattern, but no brake extras were specified.') 132 | end 133 | 134 | -- check if horn enabled but no extras specified 135 | if data.hornConfig.useHorn and #data.hornConfig.hornExtras == 0 then 136 | TriggerEvent("ulc:warn", 'A config in "' .. resourceName .. '" uses Horn Extras, but no horn extras were specified.') 137 | end 138 | 139 | -- stages 140 | if data.stages then 141 | -- check if stages are enabled but no keys specified 142 | if data.stages.useStages and #data.stages.stageKeys == 0 then 143 | TriggerEvent("ulc:warn", 144 | 'A config in "' .. resourceName .. '" uses Stages, but no keys were specified.') 145 | end 146 | 147 | -- check each key 148 | for _, v in pairs(data.stages.stageKeys) do 149 | -- if key is not a numpad value 150 | if v > 9 then 151 | TriggerEvent("ulc:error", 152 | 'A config in "' .. 153 | resourceName .. 154 | '" has an invalid key in stageKeys (' .. v .. '). Value must be 1-9 representing numpad keys.') 155 | break 156 | end 157 | 158 | -- make sure each item in data.stages.stageKeys corresponds to a button with key = the value 159 | local buttonExists = false 160 | for _, b in pairs(data.buttons) do 161 | if b.key == v then 162 | buttonExists = true 163 | break 164 | end 165 | end 166 | if not buttonExists then 167 | TriggerEvent("ulc:error", 168 | 'A config in "' .. 169 | resourceName .. 170 | '" has a key in stageKeys (' .. v .. ') that does not correspond to a key assigned to a button.') 171 | end 172 | end 173 | end 174 | 175 | 176 | -------------------- 177 | -- DEFAULT STAGES -- 178 | -------------------- 179 | if data.defaultStages or false then 180 | if data.defaultStages.useDefaults then 181 | if #data.defaultStages.enableKeys == 0 and #data.defaultStages.disableKeys == 0 then 182 | TriggerEvent("ulc:warn", 183 | 'A config in "' .. 184 | resourceName .. 185 | '" uses Default Stages, but no keys were specified to enable (enableKeys = {}) or disable (disableKeys = {}).') 186 | else 187 | if #data.defaultStages.enableKeys > 0 then 188 | for _, v in pairs(data.defaultStages.enableKeys) do 189 | if v > 9 then 190 | TriggerEvent("ulc:error", 191 | 'A config in "' .. 192 | resourceName .. 193 | '" has an invalid key in enableKeys = {}. Value must be 1-9 representing numpad keys.') 194 | end 195 | end 196 | end 197 | if #data.defaultStages.disableKeys > 0 then 198 | for _, v in pairs(data.defaultStages.disableKeys) do 199 | if v > 9 then 200 | TriggerEvent("ulc:error", 201 | 'A config in "' .. 202 | resourceName .. '" has an invalid key in disableKeys = {}. Value must be 1-9 representing numpad keys.') 203 | end 204 | end 205 | end 206 | end 207 | end 208 | end 209 | 210 | 211 | -- Buttons 212 | -- check if vehicle uses buttons but hud is disabled 213 | if #data.buttons > 0 and Config.hideHud == true then 214 | TriggerEvent("ulc:warn", 215 | 'A config in "' .. 216 | resourceName .. 217 | '" uses Stage Buttons, but HUD/UI is globally disabled. This is not recommended for user experience.') 218 | end 219 | 220 | local usedButtons = {} 221 | local usedExtras = {} 222 | for i, b in ipairs(data.buttons) do 223 | -- check if key is valid 224 | if b.key > 9 or b.key < 1 then 225 | TriggerEvent('ulc:error', 226 | 'Button ' .. 227 | i .. 228 | ' in a config found in the resource: "' .. 229 | resourceName .. '" has an invalid key. Key must be 1-9 representing number pad keys.') 230 | return false 231 | end 232 | -- check if label is empty 233 | if b.label == '' then 234 | TriggerEvent("ulc:error", 235 | 'A config in "' .. resourceName .. '" has an un-labeled button using extra: ' .. b.extra) 236 | return false 237 | end 238 | if not validateButtonText(b.label) then 239 | TriggerEvent("ulc:warn", 240 | 'A config in "' .. 241 | resourceName .. 242 | '" has a button with label: "' .. 243 | b.label .. 244 | '" which is not valid and will result in a poor user experience. Please make sure there are no more than 3 words and each word is a maximum of 5 characters. Use abbreviations where possible. Ex. "Takedowns" -> "TKD".') 245 | end 246 | if b.color and (b.color ~= 'blue' and b.color ~= 'green' and b.color ~= 'amber' and b.color ~= 'red') then 247 | TriggerEvent("ulc:error", 248 | 'A config in "' .. 249 | resourceName .. '" has a button with an invalid color input: "' .. b.color .. '" is not a supported color.') 250 | end 251 | -- check if any keys are used twice 252 | if IsIntInTable(usedButtons, b.key) then 253 | TriggerEvent("ulc:error", 254 | 'A config in "' .. resourceName .. '" uses key: " .. b.key .. " more than once in button config.') 255 | return false 256 | end 257 | -- check if any extras are used twice 258 | if IsIntInTable(usedExtras, b.extra) then 259 | TriggerEvent("ulc:error", 260 | 'A config in "' .. resourceName .. '" uses extra: " .. b.extra .. " more than once in button config.') 261 | return false 262 | end 263 | end 264 | return true 265 | end 266 | 267 | RegisterNetEvent('baseevents:enteredVehicle') 268 | AddEventHandler('baseevents:enteredVehicle', function() 269 | local src = source 270 | TriggerClientEvent("UpdateVehicleConfigs", src, Config.Vehicles) 271 | TriggerClientEvent('ulc:checkVehicle', src) 272 | end) 273 | 274 | RegisterNetEvent('baseevents:leftVehicle') 275 | AddEventHandler('baseevents:leftVehicle', function() 276 | local src = source 277 | TriggerClientEvent('ulc:cleanup', src) 278 | end) 279 | 280 | RegisterNetEvent('ulc:sync:send') 281 | AddEventHandler('ulc:sync:send', function(vehicles) 282 | print("Player " .. source .. " sent a sync request.") 283 | local players = GetPlayers() 284 | for i, v in ipairs(players) do 285 | if not v == source then 286 | --print("Sending veh sync array to player: " .. v) 287 | TriggerClientEvent('ulc:sync:receive', vehicles) 288 | end 289 | end 290 | end) 291 | 292 | 293 | local function LoadExternalVehicleConfig(resourceName) 294 | local resourceState = GetResourceState(resourceName) 295 | 296 | if resourceState == "missing" then 297 | TriggerEvent("ulc:error", 298 | "^1Couldn't load external ulc.lua file from resource: \"" .. 299 | resourceName .. 300 | "\". Resource is missing. You probably entered the model name in config.lua instead of the resource name.^0") 301 | return 302 | end 303 | 304 | if resourceState == "stopped" then 305 | TriggerEvent("ulc:error", 306 | "^1Couldn't load external ulc.lua file from resource: \"" .. resourceName .. "\". Resource is stopped.^0") 307 | return 308 | end 309 | 310 | if resourceState == "uninitialized" or resourceState == "unknown" then 311 | TriggerEvent("ulc:error", 312 | "^1Couldn't load external ulc.lua file from resource: \"" .. 313 | resourceName .. "\". Resource could not be loaded. Unknown issue.^0") 314 | return 315 | end 316 | 317 | local data = LoadResourceFile(resourceName, "data/ulc.lua") 318 | if not data then 319 | data = LoadResourceFile(resourceName, "ulc.lua") 320 | if not data then 321 | print("Error loading 'ulc.lua' file. Make sure it is at the root of your resource or in the 'data' folder.") 322 | TriggerEvent("ulc:error", '^1Could not load external configuration in: "' .. resourceName .. '"^0') 323 | return 324 | end 325 | end 326 | 327 | local f, err = load(data) 328 | if err then 329 | TriggerEvent("ulc:error", 330 | '^1Could not load external configuration in: "' .. resourceName .. '"; error: "' .. err .. '"^0') 331 | return 332 | end 333 | if not f or not f() then 334 | TriggerEvent("ulc:error", 335 | '^1Could not load external configuration; data loaded from: "' .. resourceName .. '" was nil. ^0') 336 | return 337 | end 338 | 339 | -- NEW STUFF FOR MULTIPLE CONFIGS 340 | local configs = { f() } 341 | for _, v in pairs(configs) do 342 | if CheckData(v, resourceName) then 343 | if v.name then -- if using old single name 344 | print('^2[ULC] Loaded external configuration for "' .. v.name .. '"^0') 345 | elseif v.names then -- if using new table 346 | for _, name in ipairs(v.names) do 347 | print('^2[ULC] Loaded external configuration for "' .. name .. '"^0') 348 | end 349 | end 350 | 351 | table.insert(Config.Vehicles, v) 352 | else 353 | TriggerEvent("ulc:error", '^1Could not load external configuration in "' .. resourceName .. '"^0') 354 | end 355 | end 356 | 357 | -- if CheckData(f(), resourceName) then 358 | -- print('^2Loaded external configuration for "' .. f().name .. '"^0') 359 | -- table.insert(Config.Vehicles, f()) 360 | -- else 361 | -- TriggerEvent("ulc:error", '^1Could not load external configuration for "' .. f().name .. '"^0') 362 | -- end 363 | end 364 | 365 | CreateThread(function() 366 | Wait(2000) 367 | print("[ULC] Checking for external vehicle resources.") 368 | for k, v in ipairs(Config.ExternalVehResources) do 369 | local resourceState = GetResourceState(v) 370 | while resourceState == "starting" do 371 | print("^3[ULC] Waiting for resource: " .. resourceName .. " to load.") 372 | Wait(100) 373 | end 374 | LoadExternalVehicleConfig(v) 375 | end 376 | --TriggerClientEvent('ulc:Loaded', -1) 377 | GlobalState.ulcloaded = true 378 | TriggerClientEvent("UpdateVehicleConfigs", -1, Config.Vehicles) 379 | print("[ULC] Loading complete: " .. 380 | #Config.Vehicles .. " external vehicle configurations loaded. State check: " .. tostring(GlobalState.ulcloaded)) 381 | for _, v in ipairs(Config.Vehicles) do 382 | if v.name then -- if using old single name 383 | print('[ULC] Loaded: ' .. v.name) 384 | elseif v.names then -- if using new table 385 | for _, name in ipairs(v.names) do 386 | print('[ULC] Loaded: ' .. name) 387 | end 388 | end 389 | end 390 | end) 391 | -------------------------------------------------------------------------------- /ulc/shared/shared_functions.lua: -------------------------------------------------------------------------------- 1 | -- Returns: bool (whether vehicle was found), table (vehicle config info) 2 | function GetVehicleFromConfig(vehicle) 3 | for _, v in pairs(Config.Vehicles) do 4 | -- if old method with just a string 5 | if v.name then 6 | -- find which vehicle matches 7 | if GetEntityModel(vehicle) == GetHashKey(v.name) then 8 | --print("Vehicle [" .. v.name .. "] was found in Config.") 9 | return true, v 10 | end 11 | elseif v.names then -- if new method with a table 12 | -- for each name check if it matches the vehicle 13 | for _, n in ipairs(v.names) do 14 | if GetEntityModel(vehicle) == GetHashKey(n) then 15 | --print("Vehicle [" .. v.name .. "] was found in Config.") 16 | return true, v 17 | end 18 | end 19 | end 20 | end 21 | end 22 | 23 | -- Returns whether a vehicle is in a table of vehicle spawn names given a vehicle handle 24 | function IsVehicleInTable(vehicle, table) 25 | --print(table) 26 | for _, v in pairs(table) do 27 | --print(v) 28 | --print(GetHashKey(v)) 29 | --print(vehicle) 30 | --print(GetEntityModel(vehicle)) 31 | if GetEntityModel(vehicle) == GetHashKey(v) then 32 | return true, v 33 | else 34 | --print("Vehicle [" .. v .. "] not found in table.") 35 | end 36 | end 37 | end 38 | 39 | -- Returns the vehicle speed converted to MPH or KPH based on config value 40 | function GetVehicleSpeedConverted(vehicle) 41 | if Config.useKPH then 42 | return GetEntitySpeed(Entity(vehicle)) * 3.6 43 | else 44 | return GetEntitySpeed(Entity(vehicle)) * 2.236936 45 | end 46 | end 47 | 48 | -- returns true when all vehicle doors are fully closed 49 | function AreVehicleDoorsClosed(vehicle) 50 | local result = true 51 | local numberOfDoors = GetNumberOfVehicleDoors(vehicle) 52 | for i = 0, numberOfDoors, 1 do 53 | if GetVehicleDoorAngleRatio(vehicle, i) > 0.0 then 54 | --print("[AreVehicleDoorsClosed()] Door " .. i .. " is open.") 55 | result = false 56 | end 57 | end 58 | return result 59 | end 60 | 61 | -- returns true when vehicle health is above config threshold 62 | function IsVehicleHealthy(vehicle) 63 | local vehHealth = GetVehicleBodyHealth(vehicle) 64 | 65 | if vehHealth > 980 then 66 | return true 67 | else 68 | --print("[IsVehicleHealth())] Vehicle is damaged.") 69 | return false 70 | end 71 | end 72 | 73 | function SortButtonsByKey(arr) 74 | table.sort(arr, function(a, b) 75 | return a["key"] < b["key"] 76 | end) 77 | end 78 | 79 | function formatInt(num) 80 | local formatted = tostring(num) 81 | local length = formatted:len() 82 | 83 | for i = length - 3, 1, -3 do 84 | formatted = formatted:sub(1, i) .. ',' .. formatted:sub(i + 1) 85 | end 86 | 87 | return formatted 88 | end 89 | 90 | function validateButtonText(text) 91 | local count = 0 92 | for word in text:gmatch("%w+") do 93 | count = count + 1 94 | if count > 3 or #word > 5 then 95 | return false 96 | end 97 | end 98 | return true 99 | end 100 | 101 | -- Function to check if a value exists in a table 102 | -- Returns: bool (whether value is contained), int (index of value if contained) 103 | function contains(table, val) 104 | for i, v in ipairs(table) do 105 | if v == val then 106 | return i 107 | end 108 | end 109 | return false 110 | end 111 | -------------------------------------------------------------------------------- /ulc/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ulc/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build --emptyOutDir", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.10.6", 13 | "@mantine/core": "^6.0.0", 14 | "@mantine/hooks": "^6.0.0", 15 | "react": "^18.2.0", 16 | "react-countup": "^6.4.2", 17 | "react-dom": "^18.2.0", 18 | "react-draggable": "^4.4.5" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.0.17", 22 | "@types/react-dom": "^18.0.6", 23 | "@vitejs/plugin-react": "^2.1.0", 24 | "tailwindcss": "^3.3.5", 25 | "typescript": "^4.6.4", 26 | "vite": "^3.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ulc/src/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ulc/src/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | background-color: rgba(0, 0, 0, 0) !important; 3 | } 4 | 5 | body { 6 | background-color: rgba(0, 0, 0, 0) !important; 7 | overflow: hidden; 8 | } 9 | 10 | .background { 11 | 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: center; 16 | 17 | padding: 2px; 18 | border: 10px solid ; 19 | background-image: url('./assets/texture.jpg'); 20 | border-radius: 20px; 21 | border-image: url('./assets/image.png') 20 repeat; 22 | 23 | transition: all .5s ease; 24 | } 25 | 26 | .buttons { 27 | display: flex; 28 | flex-direction: row; 29 | } 30 | 31 | .ta { 32 | height: 24px; 33 | width: 100%; 34 | 35 | display: flex; 36 | flex-direction: row; 37 | justify-content: center; 38 | align-items: center; 39 | 40 | } 41 | 42 | .ta-off { 43 | display: none; 44 | } -------------------------------------------------------------------------------- /ulc/src/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import Draggable from "react-draggable"; 3 | import { Box } from "@mantine/core"; 4 | import "./App.css"; 5 | import StageButton from "./components/StageButton"; 6 | // import TaModule from "./components/TaModule"; 7 | import Menu from "./components/Menu"; 8 | 9 | interface ButtonObject { 10 | extra: number; 11 | numKey: number; 12 | enabled: boolean; 13 | color: string; 14 | label: string; 15 | } 16 | 17 | function App() { 18 | const [test, setTest] = useState(0); 19 | const [opacity, setOpacity] = useState(0); 20 | const [menuOpacity, setMenuOpacity] = useState(0); 21 | const [scale, setScale] = useState(1.0); 22 | // const [taClassString, setTaClassString] = useState("ta ta-off"); 23 | const [useLeftAnchor, setUseLeftAnchor] = useState("false"); 24 | const [hudDisabled, setHudDisabled] = useState(false); 25 | const [showHelp, setShowHelp] = useState(false); 26 | const [x, setX] = useState(0.0); 27 | const [y, setY] = useState(0.0); 28 | const [buttonObjects, setButtonObjects] = useState([]); 29 | 30 | // SENDING DATA TO LUA 31 | 32 | useEffect(() => { 33 | let response = fetch(`https://ulc/saveScale`, { 34 | method: "POST", 35 | headers: { 36 | "Content-Type": "application/json", 37 | }, 38 | body: JSON.stringify({ scale }), 39 | }); 40 | }, [scale]); 41 | 42 | useEffect(() => { 43 | //console.log(`saveAnchor useEffect sending anchor value ${useLeftAnchor} to lua`) 44 | let response = fetch(`https://ulc/saveAnchor`, { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | body: JSON.stringify({ useLeftAnchor }), 50 | }); 51 | }, [useLeftAnchor]); 52 | 53 | useEffect(() => { 54 | let response = fetch(`https://ulc/setHudDisabled`, { 55 | method: "POST", 56 | headers: { 57 | "Content-Type": "application/json", 58 | }, 59 | body: JSON.stringify({ hudDisabled }), 60 | }); 61 | }, [hudDisabled]); 62 | 63 | /////////////// 64 | // FUNCTIONS // 65 | /////////////// 66 | 67 | function addButton( 68 | extra: number, 69 | numKey: number, 70 | enabled: boolean, 71 | color: string, 72 | label: string 73 | ) { 74 | setButtonObjects([ 75 | ...buttonObjects, 76 | { 77 | extra: extra, 78 | numKey: numKey, 79 | enabled: enabled, 80 | color: color, 81 | label: label, 82 | }, 83 | ]); 84 | } 85 | 86 | function setButton(extra: number, newState: boolean) { 87 | // console.log(`Setting buttons! Original buttons: ${buttonObjects}`); 88 | console.log(`Setting button ${extra} to ${newState}`); 89 | let updatedButtons = buttonObjects.map((item) => 90 | item.extra === extra 91 | ? { 92 | ...item, 93 | enabled: newState, 94 | } 95 | : item 96 | ); 97 | console.log(`Updated buttons ${JSON.stringify(updatedButtons)}`); 98 | setButtonObjects(updatedButtons); 99 | } 100 | 101 | // new method for setting buttons, old method has issues when updating multiple buttons at once 102 | // the goal is to only call this function once per stage change 103 | function setButtons(newButtons: { extra: number; newState: boolean }[]) { 104 | // console.log(`Setting buttons!`); 105 | let updatedButtons = buttonObjects.map((item) => { 106 | let found = newButtons.find( 107 | (newButton) => newButton.extra === item.extra 108 | ); 109 | if (found) { 110 | return { 111 | ...item, 112 | enabled: found.newState, 113 | }; 114 | } else { 115 | return item; 116 | } 117 | }); 118 | setButtonObjects(updatedButtons); 119 | } 120 | 121 | // for ta stuff i guess 122 | // function strContains(string1: string, string2: string) { 123 | // if (string1.indexOf(string2) >= 0) { 124 | // return true; 125 | // } else { 126 | // return false; 127 | // } 128 | // } 129 | 130 | // function calculateTaClassString(buttons: any) { 131 | // let result = "ta ta-off"; 132 | // for (let i = 0; i < buttons.length; i++) { 133 | // const element = buttons[i]; 134 | // if (element.label.toUpperCase() === "TA") { 135 | // result = "ta ta-on"; 136 | // } 137 | // } 138 | // return result; 139 | // } 140 | 141 | // DRAGGING UI 142 | 143 | const handleDragEvent = async (e: any, data: any) => { 144 | console.log(~~data.x, ~~data.y); 145 | let newX = ~~data.x; 146 | let newY = ~~data.y; 147 | 148 | setPosition(newX, newY); 149 | //send this position back to lua to save it for later 150 | }; 151 | 152 | function setPosition(newX: number, newY: number) { 153 | setX(newX); 154 | setY(newY); 155 | let response = fetch(`https://ulc/savePosition`, { 156 | method: "POST", 157 | headers: { 158 | "Content-Type": "application/json", 159 | }, 160 | body: JSON.stringify({ newX, newY }), 161 | }); 162 | } 163 | 164 | //////////////////// 165 | // EVENT LISTENER // 166 | //////////////////// 167 | 168 | const handleMessage = (e: any) => { 169 | var data = e.data; 170 | 171 | if (data.type === "showHUD") { 172 | setOpacity(100); 173 | } else if (data.type === "hideHUD") { 174 | setOpacity(0); 175 | } else if (data.type === "setPosition") { 176 | console.log(`Received x: ${data.x} and y: ${data.y} from lua`); 177 | setX(data.x); 178 | setY(data.y); 179 | } else if (data.type === "setScale") { 180 | console.log(`Received scale: ${data.scale} from lua`); 181 | setScale(data.scale); 182 | } else if (data.type === "setAnchor") { 183 | setUseLeftAnchor(data.bool); 184 | } else if (data.type === "showMenu") { 185 | setMenuOpacity(100); 186 | } else if (data.type === "hideMenu") { 187 | setMenuOpacity(0); 188 | } else if (data.type === "setHudDisabled") { 189 | if (data.bool === 1) { 190 | setHudDisabled(true); 191 | } else { 192 | setHudDisabled(false); 193 | } 194 | } else if (data.type === "showHelp") { 195 | setShowHelp(true); 196 | } else if (data.type === "hideHelp") { 197 | setShowHelp(false); 198 | } 199 | 200 | if (data.type === "clearButtons") { 201 | //console.log("Clearing buttons") 202 | setButtonObjects([]); 203 | } 204 | 205 | if (data.type === "populateButtons") { 206 | //console.log(`Populating buttons ${JSON.stringify(data.buttons)}`) 207 | setButtonObjects(data.buttons); 208 | // setTaClassString(calculateTaClassString(data.buttons)); 209 | } 210 | 211 | // takes: extra, state(0 on, 1 off) 212 | if (data.type === "setButton") { 213 | //console.log(`Setting button ${data.extra} ${data.newState}`) 214 | setButton(data.extra, data.newState); 215 | } 216 | 217 | if (data.type === "setButtons") { 218 | // console.log(`Setting buttons ${JSON.stringify(data.buttonStates)}`); 219 | setButtons(data.buttonStates); 220 | } 221 | }; 222 | 223 | useEffect(() => { 224 | //console.log("I am the useEffect") 225 | 226 | window.removeEventListener("message", handleMessage); 227 | window.addEventListener("message", handleMessage); 228 | 229 | return () => { 230 | window.removeEventListener("message", handleMessage); 231 | }; 232 | }, [handleMessage]); 233 | 234 | ///////////////// 235 | // DEFINITIONS // 236 | ///////////////// 237 | 238 | let buttons = buttonObjects.map((buttonObject, index) => ( 239 | <> 240 | 249 | 250 | )); 251 | 252 | return ( 253 | <> 254 | {/* MENU */} 255 | 266 | 267 | {/* HUD */} 268 | { 273 | handleDragEvent(e, data); 274 | }} 275 | > 276 | 286 | {/* 287 | 288 | 289 | 290 | */} 291 |
292 | {/*
293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 |
*/} 301 | 302 |
{buttons}
303 |
304 |
305 |
306 | 307 | ); 308 | } 309 | 310 | export default App; 311 | -------------------------------------------------------------------------------- /ulc/src/src/assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/background.png -------------------------------------------------------------------------------- /ulc/src/src/assets/button_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/button_off.png -------------------------------------------------------------------------------- /ulc/src/src/assets/button_on_amber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/button_on_amber.png -------------------------------------------------------------------------------- /ulc/src/src/assets/button_on_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/button_on_blue.png -------------------------------------------------------------------------------- /ulc/src/src/assets/button_on_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/button_on_green.png -------------------------------------------------------------------------------- /ulc/src/src/assets/button_on_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/button_on_red.png -------------------------------------------------------------------------------- /ulc/src/src/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/image.png -------------------------------------------------------------------------------- /ulc/src/src/assets/ta_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/ta_off.png -------------------------------------------------------------------------------- /ulc/src/src/assets/ta_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/ta_on.png -------------------------------------------------------------------------------- /ulc/src/src/assets/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flohhhhh/ultimate-lighting-controller/d68c5940dfcd01ab44ed2ba9b79d0ec87ba2f2cf/ulc/src/src/assets/texture.jpg -------------------------------------------------------------------------------- /ulc/src/src/components/Menu.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react' 3 | import { TypographyStylesProvider, Box, Button, Center, Container, Divider, Flex, NavLink, SegmentedControl, Slider, Switch, Space, Text } from '@mantine/core' 4 | 5 | function Menu({hudDisabled, setHudDisabled, useLeftAnchor, setUseLeftAnchor, scale, setScale, setPosition, opacity, setMenuOpacity} : any) { 6 | 7 | 8 | return ( 9 | 10 |
11 | ({ 12 | minHeight: '30vh', 13 | background: 'rgba(26,28,32,0.85)', 14 | borderRadius: theme.radius.md, 15 | padding: '10px', 16 | })}> 17 | 26 |
27 | 28 |

ULC SETTINGS

29 |
30 |
31 |
32 | 41 |
42 | Click & drag HUD to reposition! 43 | 44 |
45 |
46 | 47 | 54 | 55 | Anchor Position 56 | 57 | {setUseLeftAnchor(value); setPosition(0.0, 0.0)}} 60 | data={[ 61 | {label: 'Right', value: 'false'}, 62 | {label: 'Left', value: 'true'} 63 | ] 64 | }/> 65 | 66 | 67 | 74 | 75 | HUD Scale 76 | 77 | `${value.toFixed(1)}`} 88 | /> 89 | 90 | 91 | 98 | 99 | Disable HUD 100 | 101 | setHudDisabled(event.currentTarget.checked)}/> 102 | 103 | 104 | 105 | 114 | 124 | 125 | 126 | 127 | 141 | 142 | 143 |
144 |
145 |
146 | ) 147 | } 148 | 149 | export default Menu -------------------------------------------------------------------------------- /ulc/src/src/components/StageButton.css: -------------------------------------------------------------------------------- 1 | 2 | .button { 3 | 4 | aspect-ratio: 144/121; 5 | width: 65px; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | text-align: center; 10 | 11 | background-image: url('../assets/button_off.png'); 12 | background-size: contain; 13 | background-repeat: no-repeat; 14 | 15 | margin: 4px; 16 | padding: 5px; 17 | 18 | color: rgb(81, 81, 81); 19 | font-family: Arial, Helvetica, sans-serif; 20 | font-weight: 800; 21 | font-size: 10pt; 22 | line-height: 14px; 23 | } 24 | 25 | /* this is method to change background image */ 26 | /* just apply second class to the object */ 27 | .blue { 28 | background-image: url('../assets/button_on_blue.png'); 29 | color: #4e598f 30 | } 31 | .red { 32 | background-image: url('../assets/button_on_red.png'); 33 | color: #95665D 34 | } 35 | .amber { 36 | background-image: url('../assets/button_on_amber.png'); 37 | color: #948255 38 | } 39 | .green { 40 | background-image: url('../assets/button_on_green.png'); 41 | color: #596a2d 42 | } -------------------------------------------------------------------------------- /ulc/src/src/components/StageButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react' 3 | import { Box } from '@mantine/core' 4 | import './StageButton.css' 5 | 6 | function StageButton(props: any) { 7 | const { label, numKey, enabled, color, showHelp } = props 8 | const [classString, setClassString ] = useState('button') 9 | 10 | // color strings = 'red', 'blue, 'amber' 11 | useEffect(() => { 12 | if (enabled) { 13 | setClassString(`button ${color}`) 14 | } else { 15 | setClassString(`button`) 16 | } 17 | }), [props] 18 | 19 | if (showHelp) { 20 | return ( 21 |
22 | {`NUM ${numKey}`} 23 |
24 | ) 25 | } else { 26 | return ( 27 |
28 | {label} 29 |
30 | ) 31 | } 32 | 33 | } 34 | 35 | export default StageButton -------------------------------------------------------------------------------- /ulc/src/src/components/TaModule.css: -------------------------------------------------------------------------------- 1 | .ta-module { 2 | aspect-ratio: 256/168; 3 | height: 100%; 4 | 5 | background-image: url('../assets/ta_off.png'); 6 | background-size: contain; 7 | background-repeat: no-repeat; 8 | background-position: center; 9 | } 10 | 11 | .module-on { 12 | background-image: url('../assets/ta_on.png') 13 | } -------------------------------------------------------------------------------- /ulc/src/src/components/TaModule.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react' 3 | import './TaModule.css' 4 | 5 | function TaModule(props : any) { 6 | 7 | const [classString, setClassString ] = useState('ta-module') 8 | 9 | // color strings = 'red', 'blue, 'amber' 10 | useEffect(() => { 11 | if (props.on) { 12 | setClassString(`ta-module module-on`) 13 | } else { 14 | setClassString(`ta-module`) 15 | } 16 | }), [props] 17 | 18 | 19 | return ( 20 |
21 | ) 22 | } 23 | 24 | export default TaModule -------------------------------------------------------------------------------- /ulc/src/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import { MantineProvider } from '@mantine/core' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | ({ 9 | html: { 10 | colorScheme: 'normal' 11 | } 12 | }) 13 | }} > 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /ulc/src/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /ulc/src/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | 10 | -------------------------------------------------------------------------------- /ulc/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /ulc/src/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /ulc/src/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | build: { 8 | outDir: "../html", 9 | }, 10 | base: "", 11 | }); 12 | --------------------------------------------------------------------------------