├── .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 | 
4 |
5 |
6 |
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,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);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 |