├── LICENSE ├── README.md ├── client.lua ├── colors.json ├── config.lua ├── credits.txt ├── fxmanifest.lua ├── html ├── app.js ├── index.html └── style.css ├── server.lua ├── version └── version.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 finalLy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fl-dispatch 2 | 3 | An Event-Based Advanced FiveM QBCore Job Dispatch / 10-System Script, 4 | 5 | Originally Made by NevoG, 6 | Link to old script [here](https://forum.cfx.re/t/release-fivem-advanced-active-officers/1798459). 7 | 8 | ## Support 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
Personal Account
Discord Server
19 | 20 | ## Requirements 21 | 22 | - qb-core 23 | - qb-policejob 24 | - pma-voice 25 | 26 | ## Code Changes Requirements 27 | 28 | Add this line at `qb-core/server/player.lua` line 272: 29 | 30 | ```lua 31 | TriggerEvent('QBCore:Server:OnMetaDataUpdate', self.PlayerData.source, meta, val) 32 | ``` 33 | 34 | Your method should look like this: 35 | 36 | ```lua 37 | function self.Functions.SetMetaData(meta, val) 38 | if not meta or type(meta) ~= 'string' then return end 39 | if meta == 'hunger' or meta == 'thirst' then 40 | val = val > 100 and 100 or val 41 | end 42 | self.PlayerData.metadata[meta] = val 43 | self.Functions.UpdatePlayerData() 44 | 45 | -- Triggering our event: 46 | TriggerEvent('QBCore:Server:OnMetaDataUpdate', self.PlayerData.source, meta, val) 47 | end 48 | ``` 49 | 50 | ## Keybinds 51 | 52 | - F3 - Open Active Officers List 53 | 54 | ## Commands 55 | 56 | - /dispatch 0 - Drag Menu 57 | - /dispatch - Toggle Menu 58 | - /callsign `[callsign]` - To set your callsign (QBCore Command) 59 | 60 | ## FAQ 61 | 62 | ### 1. How can I change the default keybinds? 63 | Head to **`config.lua`** and under `Config.ToggleKey` change it to the key you desire. 64 | 65 | Also, you can change it on the client-side (only for you) in the Game Settings. 66 | 67 | ### 2. How can I add another job? 68 | This is a frequently asked question, and the answer is simple. For this example, we'll add job support for `beanmachine`. 69 | 70 | ### Job Config Editor 71 | 72 | Head to our FL-Dispatch Config Editor Repository right [here](https://github.com/finalLy134/fl-dispatch-editor.github.io). 73 | 74 | ### Manually 75 | 76 | #### A. Head to `config.lua` 77 | Add your new job to the `Config.Jobs` table. It should look like this: 78 | ```lua 79 | Config.Jobs = { "police", "ambulance", "taxi", "beanmachine" } 80 | ``` 81 | 82 | #### B. Head to `colors.json` 83 | Add the colors for your `beanmachine` job. Copy the format of the `ambulance` job or any other job and modify as needed. 84 | We would want to modify the job's **name**, **label** and **different colors**: 85 | ```lua 86 | "beanmachine": { 87 | "label": "Active Beanmachine Workers", 88 | "colors": { 89 | "backgroundColor": "#682900", 90 | "foregroundColor": "white" 91 | }, 92 | "ranges": [ 93 | { 94 | "start": 1, 95 | "end": 19, 96 | "colors": { 97 | "backgroundColor": "#003F68", 98 | "foregroundColor": "white" 99 | } 100 | }, 101 | { 102 | "start": 31, 103 | "end": 39, 104 | "colors": { 105 | "backgroundColor": "#6F5900", 106 | "foregroundColor": "white" 107 | } 108 | } 109 | ], 110 | "special": [ 111 | { 112 | "prefix": "W", 113 | "colors": { 114 | "backgroundColor": "#50008F", 115 | "foregroundColor": "white" 116 | } 117 | } 118 | ] 119 | } 120 | ``` 121 | Feel free to edit until you get the desired result. If you encounter any issues, you can copy the original content from the source code or contact me for assistance. 122 | 123 | ### 3. It shows I have an update, how can I update the script without losing my config/colors modifications? 124 | Simply save your current `colors.json` and `config.lua` somewhere in your computer. 125 | 126 | Install the newer version of fl-dispatch, upload it to your server resources folder and drag the older config files into the newer version folder. 127 | But of course, check if there was an update to the config files and if so include the newer text content into your older version ones. 128 | And you're done, you have successfully updated fl-dispatch. 129 | 130 | ## Preview 131 | 132 | ![fl-dispatch-preview](https://github.com/finalLy134/fl-dispatch/assets/60448180/f9345bbf-a1d7-4929-92ad-e4490b4b69c9) 133 | 134 | ## License 135 | 136 | This project is under MIT license. See the file [LICENSE](LICENSE) for more details. 137 | -------------------------------------------------------------------------------- /client.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports[Config.Core]:GetCoreObject() 2 | 3 | local PlayerJob = {} 4 | local Enabled = false 5 | 6 | CreateThread(function() 7 | Wait(10) 8 | while not QBCore.Functions.GetPlayerData().job do 9 | Wait(10) 10 | end 11 | 12 | PlayerJob = QBCore.Functions.GetPlayerData().job 13 | TriggerServerEvent("fl-dispatch:server:refresh") 14 | end) 15 | 16 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded') 17 | AddEventHandler('QBCore:Client:OnPlayerLoaded', function() 18 | PlayerJob = QBCore.Functions.GetPlayerData().job 19 | TriggerServerEvent("fl-dispatch:server:refresh") 20 | end) 21 | 22 | RegisterNetEvent('QBCore:Client:OnJobUpdate') 23 | AddEventHandler('QBCore:Client:OnJobUpdate', function(jobInfo) 24 | PlayerJob = jobInfo 25 | if Enabled and not IsJobAllowed(PlayerJob.name) then 26 | SendNUIMessage({ action = "close" }) 27 | end 28 | 29 | TriggerServerEvent("fl-dispatch:server:refresh") 30 | end) 31 | 32 | RegisterNetEvent("fl-dispatch:client:open") 33 | AddEventHandler("fl-dispatch:client:open", function(type) 34 | if type == 'toggle' then 35 | if Enabled then 36 | Enabled = false 37 | SendNUIMessage({ action = 'close' }) 38 | else 39 | Enabled = true 40 | SendNUIMessage({ action = 'open' }) 41 | end 42 | elseif type == 'drag' then 43 | SetNuiFocus(true, true) 44 | SendNUIMessage({ action = 'drag' }) 45 | elseif type == 'force_exit' then 46 | Enabled = false 47 | SendNUIMessage({ action = 'close' }) 48 | end 49 | end) 50 | 51 | RegisterNUICallback("Close", function() 52 | SetNuiFocus(false, false) 53 | end) 54 | 55 | RegisterNetEvent("fl-dispatch:client:refresh") 56 | AddEventHandler("fl-dispatch:client:refresh", function(data) 57 | local id = GetPlayerServerId(PlayerId()) 58 | local filteredData = {} 59 | 60 | for _, jobData in pairs(data) do 61 | if jobData.job == PlayerJob.name then 62 | for i, v in ipairs(jobData.players) do 63 | if v.src == id then 64 | jobData.players[i].me = true 65 | end 66 | end 67 | table.insert(filteredData, jobData) 68 | end 69 | end 70 | 71 | SendNUIMessage({ 72 | action = 'refresh', 73 | data = filteredData 74 | }) 75 | end) 76 | 77 | RegisterNetEvent('fl-dispatch:client:removePlayer') 78 | AddEventHandler('fl-dispatch:client:removePlayer', function(src) 79 | SendNUIMessage({ 80 | action = "removePlayer", 81 | data = { 82 | src = src, 83 | }, 84 | }) 85 | TriggerServerEvent("fl-dispatch:server:refresh") 86 | end) 87 | 88 | RegisterNetEvent("fl-dispatch:client:setTalkingOnRadio") 89 | AddEventHandler("fl-dispatch:client:setTalkingOnRadio", function(src, talking) 90 | SendNUIMessage({ 91 | action = "setTalkingOnRadio", 92 | data = { 93 | src = src, 94 | talking = talking 95 | }, 96 | }) 97 | end) 98 | 99 | RegisterNetEvent("fl-dispatch:client:setPlayerRadio") 100 | AddEventHandler("fl-dispatch:client:setPlayerRadio", function(src, channel) 101 | SendNUIMessage({ 102 | action = "setPlayerRadio", 103 | data = { 104 | src = src, 105 | channel = channel 106 | }, 107 | }) 108 | end) 109 | 110 | RegisterCommand("+dispatch", function() 111 | TriggerServerEvent('fl-dispatch:server:open', GetPlayerServerId(PlayerId()), {}) 112 | end, false) 113 | 114 | RegisterKeyMapping('+dispatch', 'Opens Job Dispatch List', Config.ToggleKey['group'], Config.ToggleKey['key']) 115 | 116 | function IsJobAllowed(job) 117 | for _, allowedJob in ipairs(Config.Jobs) do 118 | if allowedJob == job then 119 | return true 120 | end 121 | end 122 | return false 123 | end 124 | -------------------------------------------------------------------------------- /colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultColors": { 3 | "backgroundColor": "rgb(47, 69, 86)", 4 | "foregroundColor": "white" 5 | }, 6 | "jobs": { 7 | "police": { 8 | "label": "Active Officers", 9 | "colors": { 10 | "backgroundColor": "rgb(47, 69, 86)", 11 | "foregroundColor": "white" 12 | }, 13 | "special": [ 14 | { 15 | "prefix": "S", 16 | "colors": { 17 | "backgroundColor": "red", 18 | "foregroundColor": "white" 19 | } 20 | }, 21 | { 22 | "prefix": "F", 23 | "colors": { 24 | "backgroundColor": "green", 25 | "foregroundColor": "white" 26 | } 27 | }, 28 | { 29 | "prefix": "V", 30 | "colors": { 31 | "backgroundColor": "#BA7D45", 32 | "foregroundColor": "white" 33 | } 34 | }, 35 | { 36 | "prefix": "H", 37 | "colors": { 38 | "backgroundColor": "#636364", 39 | "foregroundColor": "white" 40 | } 41 | } 42 | ], 43 | "ranges": [ 44 | { 45 | "start": 200, 46 | "end": 210, 47 | "colors": { 48 | "backgroundColor": "rgb(235, 1, 2)", 49 | "foregroundColor": "white" 50 | } 51 | }, 52 | { 53 | "start": 300, 54 | "end": 330, 55 | "colors": { 56 | "backgroundColor": "rgb(20, 10, 11)", 57 | "foregroundColor": "white" 58 | } 59 | }, 60 | { 61 | "start": 330, 62 | "end": 390, 63 | "colors": { 64 | "backgroundColor": "rgb(125, 121, 119)", 65 | "foregroundColor": "white" 66 | } 67 | }, 68 | { 69 | "start": 400, 70 | "end": 440, 71 | "colors": { 72 | "backgroundColor": "rgb(210, 76, 3)", 73 | "foregroundColor": "white" 74 | } 75 | }, 76 | { 77 | "start": 450, 78 | "end": 490, 79 | "colors": { 80 | "backgroundColor": "rgb(3, 61, 215)", 81 | "foregroundColor": "white" 82 | } 83 | }, 84 | { 85 | "start": 500, 86 | "end": 550, 87 | "colors": { 88 | "backgroundColor": "rgb(3, 97, 0)", 89 | "foregroundColor": "white" 90 | } 91 | } 92 | ] 93 | }, 94 | "ambulance": { 95 | "label": "Active EMS", 96 | "colors": { 97 | "backgroundColor": "red", 98 | "foregroundColor": "white" 99 | }, 100 | "ranges": [ 101 | { 102 | "start": 1, 103 | "end": 19, 104 | "colors": { 105 | "backgroundColor": "#2e54d1", 106 | "foregroundColor": "white" 107 | } 108 | }, 109 | { 110 | "start": 31, 111 | "end": 39, 112 | "colors": { 113 | "backgroundColor": "#2e54d1", 114 | "foregroundColor": "white" 115 | } 116 | }, 117 | { 118 | "start": 21, 119 | "end": 29, 120 | "colors": { 121 | "backgroundColor": "#10a9ef", 122 | "foregroundColor": "white" 123 | } 124 | }, 125 | { 126 | "start": 91, 127 | "end": 99, 128 | "colors": { 129 | "backgroundColor": "#AB1150", 130 | "foregroundColor": "white" 131 | } 132 | } 133 | ], 134 | "special": [ 135 | { 136 | "prefix": "A", 137 | "colors": { 138 | "backgroundColor": "#11ab6c", 139 | "foregroundColor": "white" 140 | } 141 | }, 142 | { 143 | "prefix": "DELTA", 144 | "colors": { 145 | "backgroundColor": "#FF9700", 146 | "foregroundColor": "white" 147 | } 148 | }, 149 | { 150 | "prefix": "OMEGA", 151 | "colors": { 152 | "backgroundColor": "#EF5610", 153 | "foregroundColor": "white" 154 | } 155 | }, 156 | { 157 | "prefix": "PD", 158 | "colors": { 159 | "backgroundColor": "#DE212A", 160 | "foregroundColor": "white" 161 | } 162 | } 163 | ] 164 | }, 165 | "taxi": { 166 | "label": "Active Drivers", 167 | "colors": { 168 | "backgroundColor": "yellow", 169 | "foregroundColor": "black" 170 | }, 171 | "special": [ 172 | { 173 | "prefix": "ALPHA", 174 | "colors": { 175 | "backgroundColor": "#FF6F00", 176 | "foregroundColor": "white" 177 | } 178 | }, 179 | { 180 | "prefix": "METRO", 181 | "colors": { 182 | "backgroundColor": "#0090FF", 183 | "foregroundColor": "white" 184 | } 185 | } 186 | ] 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | Config.Core = 'qb-core' 4 | 5 | Config.ToggleKey = { 6 | ['group'] = 'keyboard', 7 | ['key'] = 'F3' 8 | } 9 | 10 | Config.Jobs = { "police", "ambulance", "taxi" } 11 | -------------------------------------------------------------------------------- /credits.txt: -------------------------------------------------------------------------------- 1 | The original creator of the base of this script is NevoG, 2 | This script was converted from ESX to QBCore in 2022 by finalLy (me) and I continued to program it after that. 3 | I kept adding new features to the script and I just really loved the concept. 4 | This script is purely for the community and I have never done anything profitable with it. -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'bodacious' 2 | game "gta5" 3 | lua54 'yes' 4 | 5 | author "finalLy#1138" 6 | description "QB Advanced Dispatch System" 7 | repository "https://github.com/finalLy134/fl-dispatch" 8 | 9 | version "2.1.0" 10 | 11 | ui_page "html/index.html" 12 | 13 | client_script "client.lua" 14 | server_script { "version.lua", "server.lua" } 15 | shared_script "config.lua" 16 | 17 | files { 18 | "html/*.html", 19 | "html/*.css", 20 | "html/*.js", 21 | "colors.json" 22 | } 23 | -------------------------------------------------------------------------------- /html/app.js: -------------------------------------------------------------------------------- 1 | let configData = null; 2 | 3 | $(document).ready(function () { 4 | fetch("../colors.json") 5 | .then((response) => { 6 | if (!response.ok) { 7 | throw new Error(`HTTP error! Status: ${response.status}`); 8 | } 9 | return response.json(); 10 | }) 11 | .then((data) => (configData = data)) 12 | .catch((error) => { 13 | console.error("Error fetching colors.json:", error); 14 | }); 15 | 16 | $("body").on("keyup", function (key) { 17 | if (key.which === 113 || key.which == 27 || key.which == 90) { 18 | $.post(`http://${GetParentResourceName()}/Close`); 19 | } 20 | }); 21 | }); 22 | 23 | window.addEventListener("message", function (event) { 24 | let action = event.data.action; 25 | let data = event.data.data; 26 | 27 | if (action == "open") { 28 | $(".app").fadeIn(500); 29 | } else if (action == "drag") { 30 | $(".app").fadeIn(500); 31 | $(".app").draggable({ 32 | handle: ".title", 33 | containment: "window", 34 | }); 35 | } else if (action == "close") { 36 | $(".app").fadeOut(500); 37 | } else if (action == "refresh") { 38 | $(".players").html(""); 39 | 40 | for (var jobData of data) { 41 | $(".title").text( 42 | jobData.players.length + " " + configData.jobs[jobData.job].label 43 | ); 44 | 45 | var sortedPlayers = jobData.players.sort((a, b) => { 46 | return a.callsign.localeCompare(b.callsign); 47 | }); 48 | 49 | for (var player of sortedPlayers) { 50 | html = ` 51 |
52 | ${player.callsign} 58 | ${ 59 | player.name 60 | } | 61 | ${ 62 | player.grade 63 | } - 66 | ${player.channel == 0 ? "Off" : player.channel + "hz"} 67 |
`; 68 | 69 | $(".players").append(html); 70 | } 71 | } 72 | } else if (action == "removePlayer") { 73 | removePlayer(data.src); 74 | } else if (action == "setTalkingOnRadio") { 75 | setTalkingOnRadio(data.src, data.talking); 76 | } else if (action == "setPlayerRadio") { 77 | setPlayerRadio(data.src, data.channel); 78 | } 79 | }); 80 | 81 | function removePlayer(src) { 82 | var playerElement = $(`#player-id-${src}`); 83 | 84 | if (playerElement.length) { 85 | playerElement.remove(); 86 | } 87 | } 88 | 89 | function setTalkingOnRadio(src, talking) { 90 | var playerElement = $(`#player-id-${src}`); 91 | 92 | if (playerElement.length) { 93 | var statusElement = playerElement.find(".status"); 94 | if (talking) { 95 | statusElement.addClass("radio-talking"); 96 | } else { 97 | statusElement.removeClass("radio-talking"); 98 | } 99 | } else { 100 | console.error(`Player element with src ${src} not found.`); 101 | } 102 | } 103 | 104 | function setPlayerRadio(src, channel) { 105 | var playerElement = $(`#player-id-${src}`); 106 | 107 | if (playerElement.length) { 108 | var statusElement = playerElement.find(".status"); 109 | statusElement.text(channel == 0 ? "Off" : channel + "hz"); 110 | } else { 111 | console.error(`Player element with src ${src} not found.`); 112 | } 113 | } 114 | 115 | function getCallsignColors(callsign, job) { 116 | if (!configData) { 117 | console.error("Config data not available yet."); 118 | return; 119 | } 120 | 121 | const defaultColors = configData.defaultColors; 122 | const colors = { 123 | fg: defaultColors.foregroundColor, 124 | bg: defaultColors.backgroundColor, 125 | }; 126 | 127 | const jobConfig = configData.jobs[job]; 128 | 129 | if (jobConfig) { 130 | const jobColors = jobConfig.colors; 131 | 132 | if (jobColors) { 133 | colors.fg = jobColors.foregroundColor; 134 | colors.bg = jobColors.backgroundColor; 135 | } 136 | 137 | const jobRanges = jobConfig.ranges; 138 | const jobSpecials = jobConfig.special; 139 | 140 | const callsignNumber = parseInt(callsign); 141 | 142 | if (!isNaN(callsignNumber)) { 143 | if (jobRanges) { 144 | for (const range of jobRanges) { 145 | if (callsignNumber >= range.start && callsignNumber <= range.end) { 146 | colors.fg = range.colors.foregroundColor; 147 | colors.bg = range.colors.backgroundColor; 148 | break; 149 | } 150 | } 151 | } 152 | } else if (typeof callsign === "string") { 153 | if (jobSpecials) { 154 | for (const prefixData of jobSpecials) { 155 | const prefix = prefixData.prefix; 156 | if (callsign.startsWith(prefix)) { 157 | colors.fg = prefixData.colors.foregroundColor; 158 | colors.bg = prefixData.colors.backgroundColor; 159 | break; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | return colors; 167 | } 168 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | FL Dispatch 16 | 17 | 18 | 19 |
20 |
0 Active Player
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /html/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: calibri; 3 | color: white; 4 | } 5 | 6 | * { 7 | user-select: none; 8 | } 9 | 10 | .app { 11 | display: none; 12 | box-sizing: border-box; 13 | position: absolute; 14 | right: 5%; 15 | top: 40%; 16 | height: auto; 17 | width: 17%; 18 | flex-direction: column; 19 | 20 | -webkit-box-shadow: 0px 0px 21px -1px rgba(0, 0, 0, 0.68); 21 | -moz-box-shadow: 0px 0px 21px -1px rgba(0, 0, 0, 0.68); 22 | box-shadow: 0px 0px 21px -1px rgba(0, 0, 0, 0.68); 23 | background-color: rgba(8, 9, 10, 0.8); 24 | } 25 | 26 | .title { 27 | padding: 3%; 28 | text-align: center; 29 | background-color: rgba(5, 4, 5, 0.38); 30 | color: lightblue; 31 | font-weight: bold; 32 | font-size: 1.5vh; 33 | } 34 | 35 | .players { 36 | padding-bottom: 2.5%; 37 | padding-top: 1.5%; 38 | padding-right: 1.5%; 39 | padding-left: 1.5%; 40 | text-align: left; 41 | } 42 | 43 | .player { 44 | font-size: 1.4vh; 45 | padding: 1.5%; 46 | margin-left: 1%; 47 | margin-top: 0.5%; 48 | margin-bottom: 0.5%; 49 | white-space: nowrap; 50 | } 51 | 52 | .players .callsign { 53 | font-size: 1.2vh; 54 | padding: 1.4%; 55 | background-color: rgba(100, 100, 255, 0.6); 56 | border-radius: 3px; 57 | margin-left: 1%; 58 | } 59 | 60 | .players .name { 61 | margin-left: 1%; 62 | } 63 | 64 | .me-bold { 65 | font-weight: bold !important; 66 | } 67 | 68 | .duty { 69 | color: rgb(0, 170, 0); 70 | } 71 | 72 | .offduty { 73 | color: rgb(250, 0, 0); 74 | } 75 | 76 | .status { 77 | color: rgb(255, 255, 255); 78 | } 79 | 80 | .radio-talking { 81 | color: rgb(159, 24, 24) !important; 82 | } 83 | -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports[Config.Core]:GetCoreObject() 2 | 3 | local pTalking = {} 4 | 5 | QBCore.Commands.Add('dispatch', "Opens Job Dispatch List", {}, false, function(source, args) 6 | TriggerEvent('fl-dispatch:server:open', source, args) 7 | end) 8 | 9 | RegisterServerEvent('fl-dispatch:server:open', function(source, args) 10 | local src = source 11 | local Player = QBCore.Functions.GetPlayer(src) 12 | 13 | if IsJobAllowed(Player.PlayerData.job.name) and Player.PlayerData.job.onduty then 14 | local type = "toggle" 15 | TriggerEvent("fl-dispatch:server:refresh") 16 | 17 | if args[1] == "0" then 18 | type = "drag" 19 | end 20 | 21 | TriggerClientEvent("fl-dispatch:client:open", src, type) 22 | end 23 | end) 24 | 25 | RegisterNetEvent('pma-voice:setTalkingOnRadio') 26 | AddEventHandler('pma-voice:setTalkingOnRadio', function(talking) 27 | local src = source 28 | local channel = GetRadioChannel(src) 29 | local players = exports['pma-voice']:getPlayersInRadioChannel(channel) 30 | 31 | pTalking[tostring(src)] = talking 32 | 33 | for playerSrc, _ in pairs(players) do 34 | TriggerClientEvent('fl-dispatch:client:setTalkingOnRadio', playerSrc, src, talking) 35 | end 36 | end) 37 | 38 | RegisterNetEvent('pma-voice:setPlayerRadio', function(channel) 39 | local src = source 40 | TriggerClientEvent('fl-dispatch:client:setPlayerRadio', -1, src, channel) 41 | TriggerClientEvent('fl-dispatch:client:setTalkingOnRadio', -1, src, false) 42 | end) 43 | 44 | RegisterNetEvent('QBCore:Server:OnPlayerUnload', function(source) 45 | Wait(3500) 46 | local src = source 47 | TriggerClientEvent('fl-dispatch:client:removePlayer', -1, src) 48 | end) 49 | 50 | AddEventHandler('playerDropped', function(reason) 51 | local src = source 52 | TriggerClientEvent('fl-dispatch:client:removePlayer', -1, src) 53 | end) 54 | 55 | AddEventHandler('QBCore:Server:OnMetaDataUpdate', function(source, meta, val) 56 | local src = source 57 | local Player = QBCore.Functions.GetPlayer(src) 58 | 59 | if IsJobAllowed(Player.PlayerData.job.name) and Player.PlayerData.job.onduty then 60 | TriggerEvent("fl-dispatch:server:refresh") 61 | end 62 | end) 63 | 64 | function GetRadioChannel(source) 65 | return Player(source).state['radioChannel'] 66 | end 67 | 68 | RegisterServerEvent("fl-dispatch:server:refresh") 69 | AddEventHandler("fl-dispatch:server:refresh", function(ch) 70 | local data = {} 71 | 72 | for _, job in ipairs(Config.Jobs) do 73 | local jobData = { 74 | job = job, 75 | players = {} 76 | } 77 | 78 | for k, v in pairs(QBCore.Functions.GetPlayers()) do 79 | local Player = QBCore.Functions.GetPlayer(v) 80 | if Player and Player.PlayerData.job.name == job and Player.PlayerData.job.onduty then 81 | local name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname 82 | local grade = Player.PlayerData.job.grade.name 83 | local callsign = Player.PlayerData.metadata['callsign'] 84 | local isTalking = pTalking[tostring(v)] or false 85 | local channel = 0 86 | 87 | if ch then 88 | channel = ch 89 | else 90 | channel = GetRadioChannel(v) 91 | end 92 | 93 | table.insert(jobData.players, { 94 | src = v, 95 | name = name, 96 | grade = grade, 97 | channel = channel, 98 | callsign = callsign, 99 | isTalking = isTalking, 100 | }) 101 | end 102 | end 103 | 104 | table.insert(data, jobData) 105 | end 106 | 107 | TriggerClientEvent("fl-dispatch:client:refresh", -1, data) 108 | end) 109 | 110 | function IsJobAllowed(job) 111 | for _, allowedJob in ipairs(Config.Jobs) do 112 | if allowedJob == job then 113 | return true 114 | end 115 | end 116 | return false 117 | end 118 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 2.1.2 2 | -------------------------------------------------------------------------------- /version.lua: -------------------------------------------------------------------------------- 1 | local function compareVersions(version1, version2) 2 | if version1 == version2 then 3 | return true 4 | else 5 | return false 6 | end 7 | end 8 | 9 | local function parseVersion(content) 10 | return string.match(content, "%d+%.%d+%.%d+") 11 | end 12 | 13 | local function base64Decode(data) 14 | local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 15 | data = string.gsub(data, "[^" .. b .. "=]", "") 16 | return (data:gsub(".", function(x) 17 | if (x == "=") then return "" end 18 | local r, f = "", (b:find(x) - 1) 19 | for i = 6, 1, -1 do r = r .. (f % 2 ^ i - f % 2 ^ (i - 1) > 0 and "1" or "0") end 20 | return r 21 | end):gsub("%d%d%d?%d?%d?%d?%d?%d?", function(x) 22 | if (#x ~= 8) then return "" end 23 | local c = 0 24 | for i = 1, 8 do c = c + (x:sub(i, i) == "1" and 2 ^ (8 - i) or 0) end 25 | return string.char(c) 26 | end)) 27 | end 28 | 29 | local function FetchFileFromGitHub(owner, repo, path, callback) 30 | local url = ("https://api.github.com/repos/%s/%s/contents/%s"):format(owner, repo, path) 31 | 32 | PerformHttpRequest(url, function(errorCode, resultData, resultHeaders) 33 | if errorCode == 200 then 34 | local response = json.decode(resultData) 35 | local content = base64Decode(response.content) 36 | callback(content) 37 | else 38 | print("Error fetching file:", errorCode) 39 | callback(nil) 40 | end 41 | end) 42 | end 43 | 44 | CreateThread(function() 45 | FetchFileFromGitHub("finalLy134", "fl-dispatch", "version", function(fileContent) 46 | if fileContent then 47 | local current = LoadResourceFile(GetCurrentResourceName(), "version") 48 | 49 | if current then 50 | local currentVersion = parseVersion(current) 51 | local fileVersion = parseVersion(fileContent) 52 | 53 | if currentVersion and fileVersion and compareVersions(currentVersion, fileVersion) then 54 | print("FL-Dispatch is up to date! (Version " .. currentVersion .. ")") 55 | else 56 | print( 57 | "FL-Dispatch isn't up to date. Please visit https://github.com/finalLy134/fl-dispatch and install the newer version.") 58 | end 59 | else 60 | print("Failed to open the version file, was it deleted?") 61 | end 62 | else 63 | print( 64 | "Failed to run a version check, please check if there's an update on https://github.com/finalLy134/fl-dispatch.") 65 | end 66 | end) 67 | end) 68 | --------------------------------------------------------------------------------