├── README.md ├── client ├── cl_armory.lua ├── cl_boatshelis.lua ├── cl_bossmenu.lua ├── cl_dutyblips.lua ├── cl_evidence.lua ├── cl_garage.lua ├── cl_main.lua ├── cl_raidgarages.lua └── cl_tools.lua ├── fxmanifest.lua ├── modules ├── client.lua ├── server.lua └── shared.lua ├── server ├── sv_armory.lua ├── sv_bossmenu.lua ├── sv_certs.lua ├── sv_dutyblips.lua ├── sv_evidence.lua └── sv_main.lua └── shared ├── config.lua ├── sh_armory.lua ├── sh_dutyblips.lua ├── sh_garage.lua └── sh_toolsconfig.lua /README.md: -------------------------------------------------------------------------------- 1 |
2 |

xt-pdextras

3 |
4 | xT Development Discord
5 |
6 | 7 | ## Features: 8 |
9 | Police Tools 10 | 14 |
15 | 16 |
17 | Police Certifications 18 | 22 |
23 | 24 |
25 | Boss Menus 26 | 32 |
33 | 34 |
35 | Evidence Lockers 36 | 40 |
41 | 42 |
43 | Armory 44 | 49 |
50 | 51 |
52 | Police Garages 53 | 56 |
57 | 58 |
59 | Police Garages / Boats / Helis 60 | 63 |
64 | 65 |
66 | Commands 67 | 71 |
72 | 73 |
74 | Duty Blips 75 | 79 |
80 | 81 | ## Dependencies: 82 | - ox_inventory 83 | - ox_target 84 | - ox_lib 85 | - oxmysql 86 | 87 | ## Setup: 88 | - PD Certifications Usage: Add the following to `qb-core > server > player.lua` inside your existing playerdata 89 | ```lua 90 | PlayerData.metadata['pdcerts'] = PlayerData.metadata['pdcerts'] or { 91 | ['airone'] = false, -- Air-1 92 | ['mbu'] = false, -- Motorbike Unit 93 | ['fto'] = false, -- Field Training Officer 94 | ['hsu'] = false, -- High Speed Unit 95 | ['classthree'] = false, -- Class 3 96 | ['classtwo'] = false, -- Class 2 97 | ['alr'] = false, -- Armalite Rifle 98 | ['canine'] = false, -- Canine 99 | ['swat'] = false -- SWAT 100 | } 101 | ``` 102 | - Boss Menu Usage: Add the following function to `qb-phone > server > employment.lua` 103 | ```lua 104 | local function JobsHandler(source, Job, CID, grade) 105 | local src = source 106 | local srcPlayer = QBCore.Functions.GetPlayer(src) 107 | 108 | if not srcPlayer then return print("no source") end 109 | 110 | local srcCID = srcPlayer.PlayerData.citizenid 111 | 112 | if not Job or not CID or not CachedJobs[Job] then return end 113 | local Player = QBCore.Functions.GetPlayerByCitizenId(CID) 114 | if not CachedJobs[Job].employees[CID] then return notifyPlayer(src, "Citizen is not employed at the job...") end 115 | if tonumber(grade) > tonumber(CachedJobs[Job].employees[srcCID].grade) then return notifyPlayer(src, "You cannot promote someone higher than you...") end 116 | 117 | CachedJobs[Job].employees[CID].grade = tonumber(grade) 118 | 119 | MySQL.update('UPDATE player_jobs SET employees = ? WHERE jobname = ?', { json.encode(CachedJobs[Job].employees), Job }) 120 | 121 | TriggerClientEvent("qb-phone:client:JobsHandler", -1, Job, CachedJobs[Job].employees) 122 | 123 | if Player and CachedPlayers[CID] then 124 | CachedPlayers[CID][Job] = CachedJobs[Job].employees[CID] 125 | 126 | local newGrade = type(CachedJobs[Job].employees[CID].grade) ~= "number" and tonumber(CachedJobs[Job].employees[CID].grade) or CachedJobs[Job].employees[CID].grade 127 | Player.Functions.SetJob(Job, newGrade) 128 | 129 | TriggerClientEvent('qb-phone:client:MyJobsHandler', Player.PlayerData.source, Job, CachedPlayers[CID][Job], CachedJobs[Job].employees) 130 | end 131 | end 132 | exports('JobsHandler', JobsHandler) 133 | ``` 134 | 135 | - Raid Garages Usage: Find the `PublicGarage` function in qb-garages and replace it with the following: 136 | ```lua 137 | local function PublicGarage(garageName, type) 138 | local garage = Config.Garages[garageName] 139 | local categories = garage.vehicleCategories 140 | local superCategory = GetSuperCategoryFromCategories(categories) 141 | local isHidden = true 142 | if PlayerJob.name == 'police' and type ~= 'depot' then isHidden = false end 143 | 144 | exports['qb-menu']:openMenu({ 145 | { 146 | header = garage.label, 147 | isMenuHeader = true, 148 | }, 149 | { 150 | header = 'Raid Garage', 151 | txt = 'Search for a citizen\'s vehicles', 152 | icon = 'fas fa-magnifying-glass', 153 | hidden = isHidden, 154 | params = { 155 | event = 'xt-pdextras:client:raidGarage', 156 | args = { 157 | garage = garage, 158 | garageId = garageName, 159 | categories = categories, 160 | superCategory = superCategory, 161 | type = type, 162 | } 163 | } 164 | }, 165 | { 166 | header = Lang:t("menu.text.vehicles"), 167 | txt = Lang:t("menu.text.vehicles"), 168 | params = { 169 | event = "qb-garages:client:GarageMenu", 170 | args = { 171 | garageId = garageName, 172 | garage = garage, 173 | categories = categories, 174 | header = Lang:t("menu.header."..garage.type.."_"..superCategory, {value = garage.label}), 175 | superCategory = superCategory, 176 | type = type 177 | } 178 | } 179 | }, 180 | { 181 | header = Lang:t("menu.leave.car"), 182 | txt = "", 183 | params = { 184 | event = 'qb-menu:closeMenu' 185 | } 186 | }, 187 | }) 188 | end 189 | ``` 190 | 191 | -------------------------------------------------------------------------------- /client/cl_armory.lua: -------------------------------------------------------------------------------- 1 | local xTc = require 'modules.client' 2 | 3 | -- Armory Menu -- 4 | AddEventHandler('xt-pdextras:client:OpenArmory', function(location) 5 | local ArmoryMenu = {} 6 | for x = 1, #Config.Armory.items do 7 | local Armory = Config.Armory.items[x] 8 | ArmoryMenu[#ArmoryMenu+1] = { 9 | title = Armory.title, 10 | icon = Armory.icon, 11 | event = 'xt-pdextras:client:ArmoryCategory', 12 | args = { 13 | category = x, 14 | location = location 15 | } 16 | } 17 | end 18 | 19 | ArmoryMenu[#ArmoryMenu+1] = { 20 | title = 'Restock Inventory', 21 | icon = 'fas fa-retweet', 22 | event = 'xt-pdextras:client:Restock', 23 | args = location 24 | } 25 | 26 | lib.registerContext({ 27 | id = 'pd_armroy_menu', 28 | title = Config.Armory.title, 29 | options = ArmoryMenu 30 | }) 31 | lib.showContext('pd_armroy_menu') 32 | end) 33 | 34 | -- Armory Category Menu -- 35 | AddEventHandler('xt-pdextras:client:ArmoryCategory', function(categoryInfo) 36 | local playerCerts, playerRank = lib.callback.await('xt-pdextras:server:CertsAndRank') 37 | local Items = Config.Armory.items[categoryInfo.category].items 38 | local ArmoryCategory = {} 39 | for x = 1, #Items do 40 | local Item = Items[x] 41 | local image 42 | 43 | if sharedItems[Item.item]?.client?.image then 44 | image = "https://cfx-nui-"..sharedItems[Item.item].client.image:gsub('^url%(nui://(.-)%)$', '%1') 45 | else 46 | image = "https://cfx-nui-ox_inventory/web/images/".. Item.item .. ".png" 47 | end 48 | 49 | if Config.Armory.items[categoryInfo.category].type == 'ammo' then 50 | ArmoryCategory[#ArmoryCategory+1] = { 51 | title = Item.label, 52 | icon = 'fas fa-bullseye', 53 | event = 'xt-pdextras:client:InputItemAmount', 54 | args = { 55 | item = Item, 56 | location = categoryInfo.location, 57 | categoryInfo = categoryInfo 58 | } 59 | } 60 | else 61 | if (Item.rank == 0 or playerRank >= Item.rank) and (Item.cert == 'none' or playerCerts[Item.cert]) then 62 | ArmoryCategory[#ArmoryCategory+1] = { 63 | title = sharedItems[Item.item].label, 64 | icon = image, 65 | image = image, 66 | event = 'xt-pdextras:client:InputItemAmount', 67 | metadata = { sharedItems[Item.item].description }, 68 | args = { 69 | item = Item, 70 | location = categoryInfo.location, 71 | categoryInfo = categoryInfo 72 | } 73 | } 74 | end 75 | end 76 | end 77 | lib.registerContext({ 78 | id = 'pd_armory_category', 79 | title = Config.Armory.items[categoryInfo.category].title, 80 | menu = 'pd_armroy_menu', 81 | options = ArmoryCategory 82 | }) 83 | lib.showContext('pd_armory_category') 84 | end) 85 | 86 | -- Input Amount of Item -- 87 | AddEventHandler('xt-pdextras:client:InputItemAmount', function(info) 88 | if info.item.type == 'weapon' then 89 | TriggerServerEvent('xt-pdextras:server:GetArmoryItem', info, 1) 90 | else 91 | local input = lib.inputDialog('Dialog title', { 92 | {type = 'number', label = 'Item Amount', description = '', icon = 'hashtag'}, 93 | }) 94 | if not input then return end 95 | TriggerServerEvent('xt-pdextras:server:GetArmoryItem', info, input[1]) 96 | end 97 | end) 98 | 99 | -- Restock Inventory -- 100 | AddEventHandler('xt-pdextras:client:Restock', function(location) 101 | local PlayerCoords = GetEntityCoords(cache.ped) 102 | local armoryCoords = Config.Armory.locations[location].coords 103 | local dist = #(PlayerCoords - vector3(armoryCoords.x, armoryCoords.y, armoryCoords.z)) 104 | 105 | if dist >= 6 then return end 106 | xTc.Emote('adjust') 107 | QBCore.Functions.Progressbar('pd_restock', 'Restocking Inventory...', 5000, false, true, { 108 | disableMovement = true, 109 | disableCarMovement = true, 110 | disableMouse = false, 111 | disableCombat = true, 112 | }, {}, {}, {}, function() 113 | xTc.EndEmote() 114 | TriggerServerEvent('xt-pdextras:server:Restock', location) 115 | end, function() 116 | xTc.EndEmote() 117 | QBCore.Functions.Notify('Canceled...', 'error') 118 | end) 119 | end) -------------------------------------------------------------------------------- /client/cl_boatshelis.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | 3 | -- Helicopter / Boat Menu -- 4 | AddEventHandler('xt-pdextras:client:HeliBoatMenu', function(station, label) 5 | local playerCerts, playerRank = lib.callback.await('xt-pdextras:server:CertsAndRank') 6 | local stationInfo = Config.BoatsAndHelis[station] 7 | local HeliMenu = {} 8 | for x = 1, #stationInfo.vehicles do 9 | local vehicle = stationInfo.vehicles[x] 10 | if (vehicle.rank == 0 or playerRank >= vehicle.rank) and (vehicle.cert == 'none' or playerCerts[vehicle.cert]) then 11 | HeliMenu[#HeliMenu + 1] = { 12 | title = vehicle.name, 13 | arrow = true, 14 | icon = stationInfo.ped.icon, 15 | event = 'xt-pdextras:client:SpawnHeliOrBoat', 16 | args = { 17 | vehicle = vehicle.model, 18 | livery = vehicle.livery, 19 | coords = stationInfo.spawn, 20 | station = station 21 | } 22 | } 23 | end 24 | end 25 | 26 | lib.registerContext({ 27 | id = 'heli_menu', 28 | title = label, 29 | options = HeliMenu 30 | }) 31 | lib.showContext('heli_menu') 32 | end) 33 | 34 | -- Spawn Boat/Heli -- 35 | AddEventHandler('xt-pdextras:client:SpawnHeliOrBoat',function(data) 36 | if not Utils.CheckJob(Config.PoliceJobs) then return end 37 | 38 | QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(netId) 39 | local veh = NetToVeh(netId) 40 | SetVehicleLivery(veh, data.livery) 41 | SetVehicleNumberPlateText(veh, Config.VehiclePlates..tostring(math.random(1000, 9999))) 42 | Wait(100) 43 | SetVehicleFixed(veh) 44 | exports[Config.Fuel]:SetFuel(veh, 100) 45 | TriggerEvent('vehiclekeys:client:SetOwner', QBCore.Functions.GetPlate(veh)) 46 | end, data.vehicle, data.coords, true) 47 | end) -------------------------------------------------------------------------------- /client/cl_bossmenu.lua: -------------------------------------------------------------------------------- 1 | -- Main Menu -- 2 | AddEventHandler('xt-pdextras:client:BossMenu', function(menuID) 3 | local account = lib.callback.await('xt-pdextras:server:GetFunds', false, menuID) 4 | local BossMenu = { 5 | { 6 | title = 'Society Funds: $'..account, 7 | args = menuID, 8 | icon = 'fas fa-dollar-sign' 9 | }, 10 | { 11 | title = 'View Officers', 12 | description = 'View all officers in the department', 13 | event = 'xt-pdextras:client:ViewOfficersMenu', 14 | args = menuID, 15 | icon = 'fas fa-people-group' 16 | }, 17 | { 18 | title = 'View Officers by Rank', 19 | description = 'View department ranks and officers with each rank', 20 | event = 'xt-pdextras:client:RankMenu', 21 | args = menuID, 22 | icon = 'fas fa-people-group' 23 | }, 24 | { 25 | title = 'Hire Officer', 26 | description = 'Hire a new officer', 27 | event = 'xt-pdextras:client:HireOfficer', 28 | args = Config.BossMenus[menuID].job, 29 | icon = 'fas fa-user-plus' 30 | }, 31 | { 32 | title = 'Grant / Revoke Certification', 33 | description = 'Grant or revoke a PD certification', 34 | event = 'xt-pdextras:client:CertificationMenu', 35 | icon = 'fas fa-certificate' 36 | } 37 | } 38 | 39 | lib.registerContext({ 40 | id = 'pd_boss_menu', 41 | title = 'PD Boss Menu', 42 | options = BossMenu 43 | }) 44 | lib.showContext('pd_boss_menu') 45 | end) 46 | 47 | -- Hire an Officer -- 48 | AddEventHandler('xt-pdextras:client:HireOfficer', function(job) 49 | local options = {} 50 | local ranks = {} 51 | local rank = -1 52 | 53 | for x, t in ipairs(QBCore.Shared.Jobs[job].grades) do 54 | table.insert(ranks, tonumber(x)) 55 | end 56 | 57 | for x = 1, #ranks do 58 | local Grade = QBCore.Shared.Jobs[job].grades[tostring(ranks[x])] 59 | options[#options+1] = { value = tostring(ranks[x]), label = ranks[x]..' | '..Grade.name } 60 | end 61 | 62 | local input = lib.inputDialog('Hire Officer', { 63 | { type = 'number', label = 'Player ID', description = '', icon = 'hashtag' }, 64 | { type = 'select', label = 'Rank', options = options }, 65 | }) 66 | 67 | if not input then return end 68 | local confirmation = lib.alertDialog({ 69 | header = 'Confirm Hiring Citizen', 70 | size = 'xs', 71 | centered = true, 72 | cancel = true, 73 | labels = { 74 | confirm = 'Hire Citizen', 75 | cancel = 'Cancel' 76 | } 77 | }) 78 | if not confirmation then lib.showContext('pd_boss_menu') return end 79 | TriggerServerEvent('xt-pdextras:server:HireOfficer', input, job) 80 | end) 81 | 82 | -- View Officers -- 83 | AddEventHandler('xt-pdextras:client:ViewOfficersMenu', function(menuID) 84 | local Officers = lib.callback.await('xt-pdextras:server:GetOfficers', false, menuID) 85 | local job = Config.BossMenus[menuID].job 86 | local PlayerJob = QBCore.Functions.GetPlayerData().job 87 | local OfficersMenu = {} 88 | 89 | for x, t in pairs(Officers) do 90 | if t.grade <= tonumber(PlayerJob.grade.level) then 91 | OfficersMenu[#OfficersMenu+1] = { 92 | title = t.name, 93 | description = QBCore.Shared.Jobs[job].grades[tostring(t.grade)].name, 94 | event = 'xt-pdextras:client:OfficerInfoMenu', 95 | args = { officer = t, job = job }, 96 | icon = 'fas fa-user' 97 | } 98 | end 99 | end 100 | 101 | lib.registerContext({ 102 | id = 'pd_officers_menu', 103 | title = 'Officers', 104 | menu = 'pd_boss_menu', 105 | hasSearch = true, 106 | options = OfficersMenu 107 | }) 108 | lib.showContext('pd_officers_menu') 109 | end) 110 | 111 | -- Select Rank to View Officers -- 112 | AddEventHandler('xt-pdextras:client:RankMenu', function(menuID) 113 | local job = Config.BossMenus[menuID].job 114 | local RanksMenu = {} 115 | local ranks = {} 116 | 117 | for x, t in pairs(QBCore.Shared.Jobs[job].grades) do 118 | table.insert(ranks, tonumber(x)) 119 | end 120 | 121 | table.sort(ranks) 122 | for x = 1, #ranks do 123 | local Grade = QBCore.Shared.Jobs[job].grades[tostring(ranks[x])] 124 | RanksMenu[#RanksMenu+1] = { 125 | title = ranks[x]..' | '..Grade.name, 126 | event = 'xt-pdextras:client:ViewOfficersRankMenu', 127 | args = { rank = ranks[x], menu = menuID } 128 | } 129 | end 130 | 131 | lib.registerContext({ 132 | id = 'ranks_menu', 133 | title = 'View Officers by Rank', 134 | menu = 'pd_boss_menu', 135 | options = RanksMenu 136 | }) 137 | lib.showContext('ranks_menu') 138 | end) 139 | 140 | -- View Officers by Rank Menu -- 141 | AddEventHandler('xt-pdextras:client:ViewOfficersRankMenu', function(data) 142 | local Officers = lib.callback.await('xt-pdextras:server:GetOfficers', false, data.menu) 143 | local job = Config.BossMenus[data.menu].job 144 | local OfficersByRankMenu = {} 145 | 146 | for x, t in pairs(Officers) do 147 | if t.grade == data.rank then 148 | OfficersByRankMenu[#OfficersByRankMenu+1] = { 149 | title = t.name, 150 | description = QBCore.Shared.Jobs[job].grades[tostring(t.grade)].name, 151 | event = 'xt-pdextras:client:OfficerInfoMenu', 152 | args = { officer = t, job = job }, 153 | icon = 'fas fa-user' 154 | } 155 | end 156 | end 157 | 158 | lib.registerContext({ 159 | id = 'pd_officers_menu', 160 | title = QBCore.Shared.Jobs[job].grades[tostring(data.rank)].name..' Officers', 161 | menu = 'ranks_menu', 162 | hasSearch = true, 163 | options = OfficersByRankMenu 164 | }) 165 | lib.showContext('pd_officers_menu') 166 | end) 167 | 168 | -- View Individual Officer -- 169 | AddEventHandler('xt-pdextras:client:OfficerInfoMenu', function(data) 170 | local PlayerData = QBCore.Functions.GetPlayerData() 171 | local OfficerInfoMenu = { 172 | { 173 | title = 'Change Rank', 174 | event = 'xt-pdextras:client:ChangeRank', 175 | args = { job = data.job, officer = data.officer }, 176 | icon = 'fas fa-ranking-star' 177 | }, 178 | { 179 | title = 'View Officer Certifications', 180 | event = 'xt-pdextras:client:GetPlayerCertMenu', 181 | args = { officer = data.officer }, 182 | icon = 'fas fa-certificate' 183 | }, 184 | } 185 | 186 | if data.officer.cid ~= PlayerData.citizenid then 187 | OfficerInfoMenu[#OfficerInfoMenu+1] = { 188 | title = 'Fire Officer', 189 | event = 'xt-pdextras:client:FireOfficerConfirmation', 190 | args = { job = data.job, cid = data.officer.cid, name = data.officer.name }, 191 | icon = 'fas fa-user-slash' 192 | } 193 | end 194 | 195 | lib.registerContext({ 196 | id = 'officers_info_menu', 197 | title = data.officer.name, 198 | menu = 'pd_officers_menu', 199 | options = OfficerInfoMenu 200 | }) 201 | lib.showContext('officers_info_menu') 202 | end) 203 | 204 | -- View Officers Certifications -- 205 | AddEventHandler('xt-pdextras:client:GetPlayerCertMenu', function(data) 206 | local playerCerts, playerRank = lib.callback.await('xt-pdextras:server:CertsAndRank', false, data.officer.cid) 207 | local OfficerCertsMenu = {} 208 | local hasCert = '' 209 | 210 | if playerCerts then 211 | for x, t in pairs(playerCerts) do 212 | if t then hasCert = '✅' else hasCert = '❌' end 213 | OfficerCertsMenu[#OfficerCertsMenu+1] = { 214 | title = hasCert..' | '..Config.Certifications.certs[x], 215 | } 216 | end 217 | else 218 | OfficerCertsMenu[#OfficerCertsMenu+1] = { 219 | title = '❌ | NONE FOUND', 220 | } 221 | end 222 | 223 | lib.registerContext({ 224 | id = 'officer_certs_menu', 225 | title = data.officer.name..' Certifications', 226 | menu = 'officers_info_menu', 227 | options = OfficerCertsMenu 228 | }) 229 | lib.showContext('officer_certs_menu') 230 | end) 231 | 232 | -- Change Officer Rank Menu -- 233 | AddEventHandler('xt-pdextras:client:ChangeRank', function(data) 234 | local OfficerRanksMenu = {} 235 | local ranks = {} 236 | 237 | for x, t in pairs(QBCore.Shared.Jobs[data.job].grades) do 238 | table.insert(ranks, tonumber(x)) 239 | end 240 | 241 | table.sort(ranks) 242 | for x = 1, #ranks do 243 | local Grade = QBCore.Shared.Jobs[data.job].grades[tostring(ranks[x])] 244 | OfficerRanksMenu[#OfficerRanksMenu+1] = { 245 | title = ranks[x]..' | '..Grade.name, 246 | event = 'xt-pdextras:client:ChangeRankConfirmation', 247 | args = { job = data.job, cid = data.officer.cid, grade = ranks[x], name = data.officer.name } 248 | } 249 | end 250 | 251 | lib.registerContext({ 252 | id = 'officer_rank_menu', 253 | title = data.officer.name..' | '..QBCore.Shared.Jobs[data.job].grades[tostring(data.officer.grade)].name, 254 | menu = 'officers_info_menu', 255 | options = OfficerRanksMenu 256 | }) 257 | lib.showContext('officer_rank_menu') 258 | end) 259 | 260 | -- Grant / Revoke Certs -- 261 | RegisterNetEvent('xt-pdextras:client:CertificationMenu', function() 262 | lib.registerContext({ 263 | id = 'cert_menu', 264 | title = 'PD Certifications Menu', 265 | menu = 'pd_boss_menu', 266 | options = { 267 | { 268 | title = 'Grant a Certification', 269 | event = 'xt-pdextras:client:GrantCert', 270 | icon = 'fas fa-check' 271 | }, 272 | { 273 | title = 'Revoke a Certification', 274 | event = 'xt-pdextras:client:RevokeCert', 275 | icon = 'fas fa-xmark' 276 | }, 277 | } 278 | }) 279 | lib.showContext('cert_menu') 280 | end) 281 | 282 | -- View Personal Certs -- 283 | RegisterNetEvent('xt-pdextras:client:ViewCerts', function(certs) 284 | local CertsMenu = {} 285 | local hasCert = '' 286 | 287 | for x, t in pairs(certs) do 288 | if t then hasCert = '✅' else hasCert = '❌' end 289 | CertsMenu[#CertsMenu+1] = { 290 | title = hasCert..' | '..Config.Certifications.certs[x] 291 | } 292 | end 293 | lib.registerContext({ 294 | id = 'certs_menu', 295 | title = 'My PD Certifications', 296 | options = CertsMenu 297 | }) 298 | lib.showContext('certs_menu') 299 | end) 300 | 301 | -- Grant Certs -- 302 | AddEventHandler('xt-pdextras:client:GrantCert', function() 303 | local info = {} 304 | for x, t in pairs(Config.Certifications.certs) do info[#info+1] = { value = x, label = t } end 305 | local input = lib.inputDialog('Grant Certification', { 306 | { type = 'number', label = 'Player ID', description = '', icon = 'hashtag' }, 307 | { type = 'select', label = 'Certification', options = info }, 308 | }) 309 | 310 | if not input then return end 311 | TriggerServerEvent('xt-pdextras:server:GrantCert', input) 312 | end) 313 | 314 | -- Revoke Certs -- 315 | AddEventHandler('xt-pdextras:client:RevokeCert', function() 316 | local info = {} 317 | for x, t in pairs(Config.Certifications.certs) do info[#info+1] = { value = x, label = t } end 318 | local input = lib.inputDialog('Revoke Certification', { 319 | { type = 'number', label = 'Player ID', description = '', icon = 'hashtag' }, 320 | { type = 'select', label = 'Certification', options = info }, 321 | }) 322 | if not input then return end 323 | TriggerServerEvent('xt-pdextras:server:RevokeCert', input) 324 | end) 325 | 326 | -- Confirm Firing -- 327 | AddEventHandler('xt-pdextras:client:FireOfficerConfirmation', function(data) 328 | local alert = lib.alertDialog({ 329 | header = 'Firing Officer', 330 | content = ('Are you sure you want to fire %s?'):format(data.name), 331 | size = 'xs', 332 | centered = true, 333 | cancel = true, 334 | }) 335 | if not alert then lib.showContext('officers_info_menu') return end 336 | TriggerServerEvent('xt-pdextras:server:FireOfficer', data) 337 | end) 338 | 339 | -- Confirm Rank Change -- 340 | AddEventHandler('xt-pdextras:client:ChangeRankConfirmation', function(data) 341 | local confirmation = lib.alertDialog({ 342 | header = 'Confirm Rank Change', 343 | content = ('Are you sure you want to change %s\'s rank to %s?'):format(data.name, QBCore.Shared.Jobs[data.job].grades[tostring(data.grade)].name), 344 | size = 'xs', 345 | centered = true, 346 | cancel = true, 347 | labels = { 348 | confirm = 'Confirm Change', 349 | cancel = 'Cancel' 350 | } 351 | }) 352 | if not confirmation then lib.showContext('pd_boss_menu') return end 353 | TriggerServerEvent('xt-pdextras:server:ChangeRank', data) 354 | end) -------------------------------------------------------------------------------- /client/cl_dutyblips.lua: -------------------------------------------------------------------------------- 1 | -- Some code is reused from qb-policejob, but im not a braindead dummy and made it better 2 | local xTc = require 'modules.client' 3 | 4 | officerBlips = {} 5 | 6 | -- Cache Vehilce Class -- 7 | lib.onCache('vehicle', function(value) 8 | if not PlayerJob.type == "leo" and not PlayerJob.type == "medical" then return end 9 | local newClass = nil 10 | if value then newClass = GetVehicleClass(value) end 11 | TriggerServerEvent('xt-pdextras:server:updateVehClass', newClass) 12 | end) 13 | 14 | -- Get Info From Config -- 15 | local function getBlipInfo(vehClass) 16 | local callback = nil 17 | for x, t in pairs(Config.DutyBlips) do 18 | if t.classes then 19 | for _, k in ipairs(t.classes) do 20 | if vehClass == k then 21 | callback = Config.DutyBlips[x] 22 | break 23 | end 24 | end 25 | end 26 | end 27 | return callback 28 | end 29 | 30 | -- Get Color Based on Job -- 31 | local function getBlipColor(job) 32 | local callback = nil 33 | for x, t in pairs(Config.JobColors) do 34 | if job == x then 35 | callback = t.color 36 | break 37 | end 38 | end 39 | return callback 40 | end 41 | 42 | -- Create Blips -- 43 | local function CreateDutyBlips(playerId, playerLabel, playerJob, playerLocation, vehClass) 44 | if DoesBlipExist(officerBlips[playerId]) then 45 | SetBlipCoords(officerBlips[playerId], playerLocation.x, playerLocation.y, playerLocation.z) 46 | else 47 | officerBlips[playerId] = AddBlipForCoord(playerLocation.x, playerLocation.y, playerLocation.z) 48 | end 49 | 50 | local blipInfo 51 | local blipColor = getBlipColor(playerJob) 52 | 53 | if not vehClass then 54 | blipInfo = Config.DutyBlips['none'] 55 | else 56 | blipInfo = getBlipInfo(vehClass) 57 | end 58 | 59 | SetBlipSprite(officerBlips[playerId], blipInfo.sprite) 60 | 61 | ShowHeadingIndicatorOnBlip(officerBlips[playerId], true) 62 | SetBlipRotation(officerBlips[playerId], math.ceil(playerLocation.w)) 63 | SetBlipScale(officerBlips[playerId], blipInfo.size) 64 | SetBlipColour(officerBlips[playerId], blipColor) 65 | SetBlipAsShortRange(officerBlips[playerId], false) 66 | SetBlipCategory(officerBlips[playerId], 255) 67 | BeginTextCommandSetBlipName('STRING') 68 | AddTextComponentString(playerLabel) 69 | EndTextCommandSetBlipName(officerBlips[playerId]) 70 | end 71 | 72 | -- Remove Non Existent Blips, Create New Blips -- 73 | RegisterNetEvent('xt-pdextras:client:updateDutyBlips', function(blipsData) 74 | for x = 0, #officerBlips do 75 | if DoesBlipExist(officerBlips[x]) then 76 | for _, data in pairs(blipsData) do 77 | if blipsData.source == x then 78 | break 79 | end 80 | end 81 | RemoveBlip(officerBlips[x]) 82 | officerBlips[x] = nil 83 | end 84 | end 85 | 86 | for _, data in pairs(blipsData) do 87 | local id = GetPlayerFromServerId(data.source) 88 | if id ~= PlayerId() then -- Ignore your own blip 89 | CreateDutyBlips(id, data.label, data.job, data.location, data.vehClass) 90 | end 91 | end 92 | end) 93 | 94 | 95 | 96 | RegisterNetEvent('QBCore:Client:SetDuty', function(duty) -- Send info to server based on duty status 97 | local Player = QBCore.Functions.GetPlayerData() 98 | if not Player then return end 99 | xTc.syncBlip(Player) 100 | end) 101 | 102 | RegisterNetEvent('QRCore:Client:OnJobUpdate', function(job) 103 | xTc.syncBlip(nil, job) 104 | end) 105 | -------------------------------------------------------------------------------- /client/cl_evidence.lua: -------------------------------------------------------------------------------- 1 | local xTc = require 'modules.client' 2 | local evidenceroom = {} 3 | 4 | -- Evidence Room Menu -- 5 | AddEventHandler('xt-pdextras:client:EvidenceRoom', function(ID) 6 | local EvidenceRoomMenu = { 7 | { 8 | title = 'Personal Locker', 9 | description = 'Access your personal locker', 10 | arrow = true, 11 | icon = 'fas fa-user-lock', 12 | event = 'xt-pdextras:client:OpenPDLocker', 13 | args = ID 14 | }, 15 | { 16 | title = 'Evidence Lockers', 17 | description = 'Access evidence lockers', 18 | arrow = true, 19 | icon = 'fas fa-fingerprint', 20 | event = 'xt-pdextras:client:AccessEvidenceLockers', 21 | args = ID 22 | }, 23 | { 24 | title = 'Trash Bin', 25 | description = 'Emptied Every '..Config.WipeTrashInterval..' Minute(s)', 26 | arrow = true, 27 | icon = 'fas fa-trash', 28 | event = 'xt-pdextras:client:OpenTrash', 29 | args = ID 30 | }, 31 | } 32 | lib.registerContext({ 33 | id = 'evidence_room', 34 | title = 'Evidence Room', 35 | options = EvidenceRoomMenu 36 | }) 37 | lib.showContext('evidence_room') 38 | end) 39 | 40 | -- Open Trash Bin -- 41 | AddEventHandler('xt-pdextras:client:OpenTrash',function(LOCATION) 42 | if not exports.ox_inventory:openInventory('stash', {id = 'PD-Trash-'..LOCATION}) then 43 | TriggerServerEvent('xt-pdextras:server:RegisterTrash', LOCATION) 44 | exports.ox_inventory:openInventory('stash', {id = 'PD-Trash-'..LOCATION}) 45 | end 46 | end) 47 | 48 | -- Open a Evidence Locker -- 49 | RegisterNetEvent('xt-pdextras:client:OpenEvidenceLocker',function(DRAWER, LOCATION) 50 | if not exports.ox_inventory:openInventory('stash', {id = 'PD-Evidence-'..DRAWER}) then 51 | TriggerServerEvent('xt-pdextras:server:RegisterEvidenceLocker', DRAWER, LOCATION) 52 | exports.ox_inventory:openInventory('stash', {id = 'PD-Evidence-'..DRAWER}) 53 | end 54 | end) 55 | 56 | -- Open a PD Locker -- 57 | AddEventHandler('xt-pdextras:client:OpenPDLocker',function(LOCATION, CID) 58 | if not CID then 59 | CID = QBCore.Functions.GetPlayerData().citizenid 60 | end 61 | 62 | if not exports.ox_inventory:openInventory('stash', {id = 'PD-Locker-'..CID}) then 63 | TriggerServerEvent('xt-pdextras:server:RegisterPDLocker', LOCATION, CID) 64 | exports.ox_inventory:openInventory('stash', {id = 'PD-Locker-'..CID}) 65 | end 66 | end) 67 | 68 | -- Open a Evidence Locker -- 69 | AddEventHandler('xt-pdextras:client:AccessEvidenceLockers',function(LOCATION) 70 | local input = lib.inputDialog('Evidence locker', { 71 | { type = 'number', label = 'Case / Evidence Number' }, 72 | }) 73 | if not input[1] then QBCore.Functions.Notify('Invalid Input', 'error') return end 74 | local lockerNumber = tonumber(input[1]) 75 | QBCore.Functions.Notify('Opened Evidence Locker: '..lockerNumber) 76 | 77 | 78 | TriggerServerEvent("InteractSound_SV:PlayOnSource", "LockerOpen", 0.3) 79 | TriggerEvent('xt-pdextras:client:OpenEvidenceLocker', lockerNumber, LOCATION) 80 | end) -------------------------------------------------------------------------------- /client/cl_garage.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | 3 | -- Open PD Garage Menu -- 4 | AddEventHandler('xt-pdextras:client:PDGarageMenu', function(station, label) 5 | local playerCerts, playerRank = lib.callback.await('xt-pdextras:server:CertsAndRank') 6 | local GarageMenu = {} 7 | for x = 1, #Config.Garages[station].Vehicles do 8 | local vehicle = Config.Garages[station].Vehicles[x] 9 | if (vehicle.rank == 0 or playerRank >= vehicle.rank) and (vehicle.cert == 'none' or playerCerts[vehicle.cert]) then 10 | GarageMenu[#GarageMenu + 1] = { 11 | title = sharedVehicles[vehicle.model]?.name or GetDisplayNameFromVehicleModel(vehicle.model), 12 | arrow = true, 13 | icon = 'fas fa-car', 14 | event = 'xt-pdextras:client:SpawnVehicle', 15 | args = { 16 | vehicle = x, 17 | station = station 18 | } 19 | } 20 | end 21 | end 22 | lib.registerContext({ 23 | id = 'garage_menu', 24 | title = label, 25 | options = GarageMenu 26 | }) 27 | lib.showContext('garage_menu') 28 | end) 29 | 30 | -- Spawn PD Vehicle -- 31 | AddEventHandler('xt-pdextras:client:SpawnVehicle',function(data) 32 | if not Utils.CheckJob(Config.PoliceJobs) then return end 33 | 34 | local stationInfo = Config.Garages[data.station] 35 | local vehInfo = Config.Garages[data.station].Vehicles[data.vehicle] 36 | QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(netId) 37 | local veh = NetToVeh(netId) 38 | SetVehicleNumberPlateText(veh, Config.VehiclePlates..tostring(math.random(1000, 9999))) 39 | 40 | -- Peformance Mods 41 | Wait(100) 42 | SetVehicleModKit(veh, 0) 43 | SetVehicleMod(veh, 11, vehInfo.engine, false) --engine 44 | SetVehicleMod(veh, 12, vehInfo.brakes, false) --brakes 45 | SetVehicleMod(veh, 13, vehInfo.transmission, false) --transmission 46 | SetVehicleMod(veh, 15, vehInfo.suspension, false) --suspension 47 | SetVehicleMod(veh, 16, vehInfo.armor, false) --armor 48 | ToggleVehicleMod(veh, 18, vehInfo.turbo) --turbo 49 | 50 | SetVehicleFixed(veh) 51 | exports[Config.Fuel]:SetFuel(veh, 100) 52 | 53 | Config.GetVehicleKeys(QBCore.Functions.GetPlate(veh)) 54 | end, vehInfo.model, stationInfo.VehicleSpawn, true) 55 | end) -------------------------------------------------------------------------------- /client/cl_main.lua: -------------------------------------------------------------------------------- 1 | local xTc = require 'modules.client' 2 | local Utils = require 'modules.shared' 3 | 4 | Player = QBCore.Functions.GetPlayerData() 5 | PlayerJob = QBCore.Functions.GetPlayerData().job 6 | onDuty = false 7 | CurrentCops = 0 8 | 9 | ReturnPoints = {} 10 | HeliAndBoatPeds = {} 11 | PDBossMenus = {} 12 | ArmoryZone = {} 13 | EvidencePeds = {} 14 | GaragePeds = {} 15 | 16 | -- Fine Player Menu -- 17 | RegisterNetEvent('xt-pdextras:client:FinePlayerMenu', function() 18 | if not Utils.CheckJob(Config.PoliceJobs) then return end 19 | 20 | xTc.Emote('tablet2') 21 | local input = lib.inputDialog('Fine Player', { 22 | {type = 'number', label = 'Player ID', description = '', icon = 'hashtag'}, 23 | {type = 'number', label = 'Fine Amount', description = '', icon = 'hashtag'}, 24 | {type = 'input', label = 'Fine Reason', description = ''}, 25 | }) 26 | 27 | if not input then xTc.EndEmote() return end 28 | if not input[1] then RSClientNotify('Enter a player ID!', 'error') xTc.EndEmote() return end 29 | if input[2] <= 0 then RSClientNotify('You can\'t send a fine for zero or less!', 'error') xTc.EndEmote() return end 30 | TriggerServerEvent('xt-pdextras:server:FinePlayer', input) 31 | xTc.EndEmote() 32 | end) 33 | 34 | -- Player Shit -- 35 | local function playerLoaded() 36 | Player = QBCore.Functions.GetPlayerData() 37 | PlayerJob = QBCore.Functions.GetPlayerData().job 38 | if not PlayerJob.type == "leo" and not PlayerJob.type == "medical" then return end 39 | 40 | xTc.syncBlip(Player) 41 | if Config.UseArmory then xTc.CreatePDArmory() end 42 | if Config.UseBossMenus then xTc.CreatePDBossMenus() end 43 | if Config.UseEvidence then xTc.EvidenceRoomPed() end 44 | if Config.UseGarages then xTc.GaragePeds() end 45 | if Config.UseBoatHeliLocations then 46 | xTc.HeliBoatPeds() 47 | xTc.ReturnVehiclePoints() 48 | end 49 | end 50 | 51 | -- Player Unload -- 52 | local function playerUnload() 53 | if not PlayerJob.type == "leo" and not PlayerJob.type == "medical" then return end 54 | 55 | local ID = PlayerId() 56 | Player = {} 57 | PlayerJob = {} 58 | xTc.removeOfficerBlip(ID) 59 | if Config.UseArmory then xTc.CleanupPDArmory() end 60 | if Config.UseBossMenus then xTc.CleanupPDBossMenus() end 61 | if Config.UseEvidence then xTc.RemoveEvidenceRoomPed() end 62 | if Config.UseGarages then xTc.RemoveGaragePeds() end 63 | if Config.UseBoatHeliLocations then 64 | xTc.RemoveVehicleReturnPoints() 65 | xTc.HeliBoatCleanup() 66 | end 67 | end 68 | 69 | AddEventHandler('onResourceStart', function(resource) if resource == GetCurrentResourceName() then playerLoaded() end end) 70 | AddEventHandler('onResourceStop', function(resource) if resource == GetCurrentResourceName() then playerUnload() end end) 71 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', playerLoaded) 72 | RegisterNetEvent('QBCore:Client:OnPlayerUnload', playerUnload) 73 | RegisterNetEvent('QBCore:Client:OnJobUpdate', function(job) PlayerJob = job end) 74 | RegisterNetEvent('police:SetCopCount', function(amount) CurrentCops = amount end) 75 | RegisterNetEvent('QBCore:Client:SetDuty', function(duty) onDuty = duty end) -------------------------------------------------------------------------------- /client/cl_raidgarages.lua: -------------------------------------------------------------------------------- 1 | local function getVehIcon(name) 2 | local class = GetVehicleClassFromName(name) 3 | local callback = 'fas fa-car' 4 | 5 | local classes = { 6 | [8] = 'fas fa-motorcycle', 7 | [9] = 'fas fa-truck-monster', 8 | [10] = 'fas fa-truck-moving', 9 | [11] = 'fas fa-truck', 10 | [12] = 'fas fa-van-shuttle', 11 | [13] = 'fas fa-bicycle', 12 | [14] = 'fas fa-ship', 13 | [15] = 'fas fa-helicopter', 14 | [16] = 'fas fa-plane', 15 | [17] = 'fas fa-truck', 16 | [18] = 'fas fa-taxi', 17 | [19] = 'fas fa-truck-moving', 18 | [20] = 'fas fa-truck-moving', 19 | } 20 | 21 | if classes[class] then callback = classes[class] end 22 | return callback 23 | end 24 | 25 | AddEventHandler('xt-pdextras:client:raidGarage', function(data) 26 | local garageType = data.type 27 | local garageId = data.garageId 28 | local garage = data.garage 29 | local categories = data.categories and data.categories or {'car'} 30 | local header = data.header 31 | local superCategory = data.superCategory 32 | 33 | local input = lib.inputDialog('Raid Citizen\'s Garage', { { type = 'input', label = 'Citizen\'s State ID', required = true } }) 34 | if not input then return end 35 | 36 | local pData = { string.upper(input[1]), garageId } 37 | local playerVehicles = lib.callback.await('xt-pdextras:server:getPlayerVehiclesAtGarage', 1000, pData) 38 | if not playerVehicles then return QBCore.Functions.Notify('No vehicles found!', 'error', 3000) end 39 | 40 | local menu = {} 41 | for x = 1, #playerVehicles do 42 | local vehInfo = playerVehicles[x] 43 | local vehIcon = getVehIcon(vehInfo.vehicle) 44 | local vehTitle = sharedVehicles[vehInfo.vehicle].name 45 | local vehDescription = 46 | ('Fuel: %s%s'):format(vehInfo.fuel, '%')..' \n'.. 47 | ('Body: %s%s'):format(math.ceil(vehInfo.body/100), '%')..' \n'.. 48 | ('Engine: %s%s'):format(math.ceil(vehInfo.engine/100), '%') 49 | 50 | local isDisabled = false 51 | if vehInfo.state == 0 then 52 | vehTitle = vehTitle..' ( OUT / DEPOT )' 53 | isDisabled = true 54 | elseif vehInfo.state == 2 then 55 | vehTitle = vehTitle..' ( POLICE IMPOUNDED )' 56 | isDisabled = true 57 | end 58 | 59 | menu[#menu+1] = { 60 | title = vehTitle, 61 | description = vehDescription, 62 | icon = vehIcon, 63 | disabled = isDisabled, 64 | event = 'qb-garages:client:TakeOutGarage', 65 | args = { 66 | vehicle = vehInfo, 67 | vehicleModel = vehInfo.vehicle, 68 | type = garageType, 69 | garage = garage, 70 | superCategory = superCategory, 71 | } 72 | } 73 | end 74 | 75 | lib.registerContext({ 76 | id = 'raid_garage_menu', 77 | title = ('%s Vehicles'):format(input[1]), 78 | options = menu 79 | }) 80 | lib.showContext('raid_garage_menu') 81 | end) -------------------------------------------------------------------------------- /client/cl_tools.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | local WearingNightVision = false 3 | local Thermal = false 4 | local ShowThermalUI = false 5 | 6 | -- Check if Player is Wearing Night Vision Goggles -- 7 | local function WearingNV() 8 | local callback = false 9 | local NV = GetPedPropIndex(cache.ped, 0) 10 | for _, x in pairs(Config.Nightvision) do 11 | if NV == x then callback = true break end 12 | end 13 | return callback 14 | end 15 | 16 | -- Toggle Thermal Vision -- 17 | local function ToggleThermal() 18 | if not WearingNV() then return end 19 | Thermal = not Thermal 20 | lib.requestAnimDict('mp_masks@low_car@ds@') 21 | TaskPlayAnim(cache.ped, 'mp_masks@low_car@ds@', 'put_on_mask', 1.0, 1.0, 1000, 50, 0, false, false, false) 22 | Wait(1000) 23 | SetSeethrough(Thermal) 24 | end 25 | 26 | -- Toggle Night Vision -- 27 | local function ToggleNightVision() 28 | WearingNightVision = not WearingNightVision 29 | 30 | if ShowThermalUI then 31 | lib.hideTextUI() 32 | ShowThermalUI = false 33 | end 34 | 35 | if WearingNightVision then 36 | lib.requestAnimDict('mp_masks@low_car@ds@') 37 | TaskPlayAnim(cache.ped, 'mp_masks@low_car@ds@', 'put_on_mask', 1.0, 1.0, 1000, 50, 0, false, false, false) 38 | Wait(1000) 39 | SetPedPropIndex(cache.ped, 0, 116, 0, true) 40 | SetTimecycleModifier("nightvision") 41 | SetTimecycleModifierStrength(1.0) 42 | else 43 | lib.requestAnimDict('missfbi4') 44 | TaskPlayAnim(cache.ped, 'missfbi4', 'takeoff_mask', 1.0, 1.0, 2000, 50, 0, false, false, false) 45 | Wait(1000) 46 | SetPedPropIndex(cache.ped, 0, 117, 0, true) 47 | ClearTimecycleModifier() 48 | end 49 | 50 | CreateThread(function() 51 | while WearingNightVision do 52 | if not WearingNV() then 53 | if ShowThermalUI then 54 | lib.hideTextUI() 55 | ShowThermalUI = false 56 | end 57 | ClearTimecycleModifier() 58 | break 59 | end 60 | 61 | if not ShowThermalUI then 62 | lib.showTextUI('UP - Toggle Thermal', {position = 'left-center'}) 63 | ShowThermalUI = true 64 | end 65 | 66 | if IsControlJustPressed(0, Config.ThermalKey) then 67 | ToggleThermal() 68 | end 69 | Wait(10) 70 | end 71 | end) 72 | end 73 | 74 | -- Night Vision Item -- 75 | exports('nightvision', function(data, slot) 76 | local playerPed = cache.ped 77 | ToggleNightVision() 78 | end) 79 | 80 | -- Slim Jims -- 81 | exports('slimjim', function(data, slot) 82 | local ped = cache.ped 83 | local pCoords = GetEntityCoords(ped) 84 | local veh, vehcoords = lib.getClosestVehicle(pCoords, 2.5, false) 85 | if not veh then QBCore.Functions.Notify('No vehicle nearby!', 'error') return end 86 | local difficulty = Config.SlimJim.difficulty[math.random(1, #Config.SlimJim.difficulty)] 87 | local keys = Config.SlimJim.keys 88 | 89 | local success = lib.skillCheck(difficulty, keys) 90 | Config.SlimJimSuccess(success, veh, vehcoords) 91 | end) 92 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | use_experimental_fxv2_oal 'yes' 3 | lua54 'yes' 4 | game 'gta5' 5 | 6 | description 'Police QoL Additions | xT Development' 7 | 8 | shared_scripts { '@ox_lib/init.lua', 'modules/shared.lua', 'shared/*.lua' } 9 | client_scripts { 'modules/client.lua', 'client/*.lua' } 10 | server_scripts { '@oxmysql/lib/MySQL.lua', 'modules/server.lua', 'server/*.lua' } 11 | files { 'locales/*.json' } -------------------------------------------------------------------------------- /modules/client.lua: -------------------------------------------------------------------------------- 1 | local scully = GetResourceState('scully_emotemenu') 2 | local rpemotes = GetResourceState('rpemotes') 3 | local shown = false 4 | 5 | local xTc = {} 6 | 7 | -- Play Emote -- 8 | function xTc.Emote(emote) 9 | if scully == 'started' or scully == 'starting' then 10 | exports.scully_emotemenu:playEmoteByCommand(emote) 11 | end 12 | if rpemotes == 'started' or rpemotes == 'starting' then 13 | TriggerEvent('animations:client:EmoteCommandStart', {emote}) 14 | end 15 | end 16 | 17 | -- End Emote -- 18 | function xTc.EndEmote() 19 | if scully == 'started' or scully == 'starting' then 20 | exports.scully_emotemenu:cancelEmote() 21 | end 22 | if rpemotes == 'started' or rpemotes == 'starting' then 23 | TriggerEvent('animations:client:EmoteCommandStart', {'c'}) 24 | end 25 | end 26 | 27 | -- Create Vehicle 28 | function xTc.CreateVehicle(hash, coords, plate, givekeys, locked) 29 | local model = GetHashKey(hash) 30 | 31 | lib.requestModel(model) 32 | local vehicleID = CreateVehicle(model, coords.x, coords.y, coords.z, false, false) 33 | while not DoesEntityExist(vehicleID) do Wait(50) end 34 | SetModelAsNoLongerNeeded(model) 35 | SetVehicleOnGroundProperly(vehicleID) 36 | SetEntityInvincible(vehicleID, true) 37 | SetVehicleDirtLevel(vehicleID, 0.0) 38 | if locked then SetVehicleDoorsLocked(vehicleID, 2) else SetVehicleDoorsLocked(vehicleID, 1) end 39 | SetEntityHeading(vehicleID, coords.w) 40 | FreezeEntityPosition(vehicleID, true) 41 | SetVehicleNumberPlateText(vehicleID, plate) 42 | if givekeys then 43 | TriggerEvent('vehiclekeys:client:SetOwner', QBCore.Functions.GetPlate(vehicleID)) 44 | end 45 | return vehicleID 46 | end 47 | 48 | -- Spawn Ped -- 49 | function xTc.Ped(model, coords, scenario) 50 | model = type(model) == 'string' and GetHashKey(model) or model 51 | 52 | lib.requestModel(model) 53 | local pedId = CreatePed(0, model, coords.x, coords.y, coords.z - 1, coords.w, false, false) 54 | TaskStartScenarioInPlace(pedId, scenario, 0, true) 55 | FreezeEntityPosition(pedId, true) 56 | SetEntityInvincible(pedId, true) 57 | SetBlockingOfNonTemporaryEvents(pedId, true) 58 | return pedId 59 | end 60 | 61 | -- Create Boss Menus -- 62 | function xTc.CreatePDBossMenus() 63 | for x = 1, #Config.BossMenus do 64 | local Location = Config.BossMenus[x] 65 | PDBossMenus[x] = exports.ox_target:addSphereZone({ 66 | coords = Location.coords, 67 | radius = Location.radius, 68 | debug = Config.DebugPoly, 69 | drawSprite = true, 70 | options = { 71 | { 72 | name = 'pdBossMenu', 73 | label = Location.label, 74 | icon = 'fas fa-briefcase', 75 | event = "xt-pdextras:client:BossMenu", 76 | onSelect = function() 77 | TriggerEvent('xt-pdextras:client:BossMenu', x) 78 | end, 79 | canInteract = function() 80 | local playerCerts, playerRank = lib.callback.await('xt-pdextras:server:CertsAndRank', false) 81 | local callback = false 82 | if (Location.rank == 0 or playerRank >= Location.rank) and (Location.cert == 'none' or playerCerts[Location.cert]) then callback = true end 83 | return callback 84 | end, 85 | groups = Location.job, 86 | distance = 2.5 87 | } 88 | } 89 | }) 90 | end 91 | end 92 | 93 | -- Remove Boss Menus -- 94 | function xTc.CleanupPDBossMenus() 95 | for x = 1, #PDBossMenus do 96 | exports.ox_target:removeZone(PDBossMenus[x]) 97 | end 98 | end 99 | 100 | -- Heli/Boat Peds -- 101 | function xTc.HeliBoatPeds() 102 | for x = 1, #Config.BoatsAndHelis do 103 | if DoesEntityExist(HeliAndBoatPeds[x]) then return end 104 | 105 | local location = Config.BoatsAndHelis[x] 106 | local label = location.label 107 | local ped = Config.BoatsAndHelis[x].ped 108 | HeliAndBoatPeds[x] = xTc.Ped(ped.model, ped.coords, ped.scenario) 109 | 110 | exports.ox_target:addLocalEntity(HeliAndBoatPeds[x], { 111 | { 112 | label = 'Access ' .. label, 113 | icon = ped.icon, 114 | onSelect = function() 115 | TriggerEvent('xt-pdextras:client:HeliBoatMenu', x, label) 116 | end, 117 | groups = location.job, 118 | distance = 2.0 119 | }, 120 | }) 121 | end 122 | end 123 | 124 | -- Remove Heli/Boat Peds -- 125 | function xTc.HeliBoatCleanup() 126 | for x = 1, #HeliAndBoatPeds do 127 | if DoesEntityExist(HeliAndBoatPeds[x]) then 128 | DeletePed(HeliAndBoatPeds[x]) 129 | end 130 | end 131 | end 132 | 133 | -- Remove Points to Return Vehicles -- 134 | function xTc.RemoveVehicleReturnPoints() 135 | for r = 1, #ReturnPoints do 136 | for s = 1, #ReturnPoints[r] do 137 | ReturnPoints[r][s]:remove() 138 | end 139 | end 140 | end 141 | 142 | -- Enter Return Vehicle Point -- 143 | function xTc.EnterReturnPoint(class) 144 | local ped = cache.ped 145 | local vehicle = GetVehiclePedIsIn(ped, false) 146 | if vehicle == 0 then return end 147 | local vehClass = GetVehicleClass(vehicle) 148 | if vehClass ~= class then return end 149 | if not shown then 150 | lib.showTextUI('[E] - Store Vehicle', {position = 'left-center'}) 151 | shown = true 152 | end 153 | end 154 | 155 | -- Exit Return Vehicle Point -- 156 | function xTc.ExitReturnPoint() 157 | if shown then 158 | lib.hideTextUI() 159 | shown = false 160 | end 161 | end 162 | 163 | -- Inside Return Vehicle Point -- 164 | function xTc.InsideReturnPoint() 165 | local ped = cache.ped 166 | local veh = GetVehiclePedIsIn(ped, false) 167 | 168 | if veh == 0 then 169 | if shown then 170 | lib.hideTextUI() 171 | shown = false 172 | end 173 | return 174 | end 175 | 176 | if not shown then 177 | lib.showTextUI('[E] - Store Vehicle', {position = 'left-center'}) 178 | shown = true 179 | end 180 | 181 | if IsControlJustPressed(0, 38) then 182 | QBCore.Functions.DeleteVehicle(veh) 183 | lib.hideTextUI() 184 | shown = false 185 | end 186 | end 187 | 188 | -- Create Points to Return Vehicles -- 189 | function xTc.ReturnVehiclePoints() 190 | for x = 1, #Config.BoatsAndHelis do 191 | ReturnPoints[x] = {} 192 | for t = 1, #Config.BoatsAndHelis[x].returnLocations do 193 | local Location = Config.BoatsAndHelis[x].returnLocations[t] 194 | local vehClass = Config.BoatsAndHelis[x].class 195 | 196 | ReturnPoints[x][t] = lib.zones.poly({ 197 | points = Location.coords, 198 | thickness = Location.radius, 199 | debug = Config.DebugPoly, 200 | inside = function() 201 | xTc.InsideReturnPoint() 202 | end, 203 | onEnter = function() 204 | xTc.EnterReturnPoint(vehClass) 205 | end, 206 | onExit = xTc.ExitReturnPoint 207 | }) 208 | end 209 | end 210 | end 211 | 212 | -- Armory Zones -- 213 | function xTc.CreatePDArmory() 214 | for x = 1, #Config.Armory.locations do 215 | local Armory = Config.Armory.locations[x] 216 | ArmoryZone[x] = exports.ox_target:addBoxZone({ 217 | coords = Armory.coords, 218 | size = Armory.size, 219 | rotation = Armory.heading, 220 | debug = Config.DebugPoly, 221 | options = { 222 | { 223 | icon = Config.Armory.icon, 224 | label = Config.Armory.title, 225 | onSelect = function() 226 | TriggerEvent('xt-pdextras:client:OpenArmory', x) 227 | end, 228 | groups = Armory.job, 229 | distance = 2.0 230 | } 231 | } 232 | }) 233 | end 234 | end 235 | 236 | -- Remove Armory Zones -- 237 | function xTc.CleanupPDArmory() 238 | for x = 1, #ArmoryZone do 239 | exports.ox_target:removeZone(ArmoryZone[x]) 240 | end 241 | end 242 | 243 | -- Create Evidence Peds -- 244 | function xTc.EvidenceRoomPed() 245 | for x = 1, #Config.EvidenceRooms do 246 | local Evidence = Config.EvidenceRooms[x] 247 | EvidencePeds[x] = xTc.Ped(Evidence.model, Evidence.coords, Evidence.scenario) 248 | exports.ox_target:addLocalEntity(EvidencePeds[x], { 249 | { 250 | type = "client", 251 | icon = 'fas fa-inbox', 252 | label = Evidence.label, 253 | onSelect = function() 254 | TriggerEvent('xt-pdextras:client:EvidenceRoom', x) 255 | end, 256 | groups = 'police', 257 | distance = 2.5 258 | }, 259 | }) 260 | end 261 | end 262 | 263 | -- Remove Evidence Peds -- 264 | function xTc.RemoveEvidenceRoomPed() 265 | for x = 1, #EvidencePeds do 266 | if DoesEntityExist(EvidencePeds[x]) then 267 | DeletePed(EvidencePeds[x]) 268 | end 269 | end 270 | end 271 | 272 | -- Garage Peds -- 273 | function xTc.GaragePeds() 274 | for x = 1, #Config.Garages do 275 | if DoesEntityExist(GaragePeds[x]) then return end 276 | local location = Config.Garages[x] 277 | local label = location.label 278 | local ped = Config.Garages[x].Ped 279 | local pedModel = ped.model 280 | GaragePeds[x] = xTc.Ped(pedModel, ped.coords, ped.scenario) 281 | exports.ox_target:addLocalEntity(GaragePeds[x], { 282 | { 283 | type = "client", 284 | icon = 'fas fa-warehouse', 285 | label = 'Access ' .. label, 286 | onSelect = function() 287 | TriggerEvent('xt-pdextras:client:PDGarageMenu', x, label) 288 | end, 289 | groups = location.job, 290 | distance = 2.5 291 | }, 292 | }) 293 | end 294 | end 295 | 296 | -- Remove Evidence Peds -- 297 | function xTc.RemoveGaragePeds() 298 | for x = 1, #GaragePeds do 299 | if DoesEntityExist(GaragePeds[x]) then 300 | DeletePed(GaragePeds[x]) 301 | end 302 | end 303 | end 304 | 305 | function xTc.syncBlip(player, job) -- Sync blips w duty/job changes + reources start/player load 306 | local job = player.job or job 307 | 308 | if job.type == "leo" or job.type == "medical" then 309 | local callSign = player.metadata['callsign'] 310 | if not job.onduty then 311 | TriggerServerEvent('xt-pdextras:server:updateBlipInfo') -- Remove blip if it exist 312 | else 313 | TriggerServerEvent('xt-pdextras:server:updateBlipInfo', job.name, callSign) -- Set blip info 314 | if cache.vehicle then 315 | local class = GetVehicleClass(cache.vehicle) 316 | TriggerServerEvent('xt-pdextras:server:updateVehClass', class) 317 | end 318 | end 319 | end 320 | end 321 | 322 | function xTc.removeOfficerBlip(ID) 323 | if DoesBlipExist(officerBlips[ID]) then 324 | RemoveBlip(ID) 325 | end 326 | end 327 | 328 | function xTc.removeAllDutyBlips() 329 | if not officerBlips then return end 330 | 331 | for x = 0, #officerBlips do 332 | if DoesBlipExist(officerBlips[x]) then 333 | RemoveBlip(x) 334 | end 335 | end 336 | end 337 | 338 | return xTc 339 | -------------------------------------------------------------------------------- /modules/server.lua: -------------------------------------------------------------------------------- 1 | local xTs = {} 2 | 3 | -- Return Player Certs and Rank -- 4 | function xTs.CertsAndRank(source, target) 5 | local PlayerMetadata 6 | local PlayerJob 7 | if not target then 8 | local src = source 9 | local Player = QBCore.Functions.GetPlayer(src) 10 | PlayerMetadata = Player.PlayerData.metadata 11 | PlayerJob = Player.PlayerData.job 12 | else 13 | local findPlayer = MySQL.query.await("SELECT * FROM `players` WHERE citizenid = ?", { target }) 14 | if findPlayer[1] then 15 | PlayerMetadata = json.decode(findPlayer[1].metadata) 16 | PlayerJob = json.decode(findPlayer[1].job) 17 | end 18 | end 19 | 20 | if not Player then return end 21 | local certs = PlayerMetadata["pdcerts"] 22 | local rank = PlayerJob.grade.level 23 | 24 | return certs, rank 25 | end 26 | 27 | -- Get All Officers -- 28 | function xTs.GetOfficers(source, menuID) 29 | local src = source 30 | local Player = QBCore.Functions.GetPlayer(src) 31 | if not Player then return end 32 | local Employees = {} 33 | 34 | local jobs = MySQL.query.await("SELECT * FROM `player_jobs` WHERE jobname = ?", { Config.BossMenus[menuID].job }) 35 | if jobs[1] then Employees = json.decode(jobs[1].employees) end 36 | return Employees 37 | end 38 | 39 | -- Get Job Funds -- 40 | function xTs.GetFunds(source, menuID) 41 | local src = source 42 | local Player = QBCore.Functions.GetPlayer(src) 43 | local callback = nil 44 | if not Player then return end 45 | 46 | local account = MySQL.query.await("SELECT * FROM `bank_accounts_new` WHERE id = ?", { Config.BossMenus[menuID].job }) 47 | if account[1] then callback = account[1].amount end 48 | return callback 49 | end 50 | 51 | -- Server Log -- 52 | function xTs.Log(logName, color, title, text) 53 | TriggerEvent("qb-log:server:CreateLog", logName, title, color, text, false, 'xt-pdextras') 54 | end 55 | 56 | function xTs.ClosestEvidenceLocker(source, distance) 57 | local pCoords = GetEntityCoords(GetPlayerPed(source)) 58 | local callback = nil 59 | for x = 1, #Config.EvidenceRooms do 60 | local evidenceCoords = vec3(Config.EvidenceRooms[x].coords.x, Config.EvidenceRooms[x].coords.y, Config.EvidenceRooms[x].coords.z) 61 | local dist = #(evidenceCoords - pCoords) 62 | if dist <= distance then 63 | callback = x 64 | break 65 | end 66 | end 67 | return callback 68 | end 69 | 70 | return xTs -------------------------------------------------------------------------------- /modules/shared.lua: -------------------------------------------------------------------------------- 1 | local Utils = {} 2 | 3 | -- Debug Print -- 4 | function Utils.Debug(type, debugTxt) 5 | if debugTxt == nil then debugTxt = '' end 6 | if Config.Debug then 7 | print("^2xT Debug ^7| "..type.." | ^2"..debugTxt) 8 | end 9 | end 10 | 11 | -- Check Job Client/Server -- 12 | function Utils.CheckJob(job, src) 13 | local checkType = IsDuplicityVersion() and 'server' or 'client' 14 | local callback = false 15 | if checkType == 'client' then -- Checks for client/server 16 | local Player = QBCore.Functions.GetPlayerData() 17 | if type(job) == 'string' then -- Checks for string or table of jobs 18 | if Player.job.name == job then 19 | callback = true 20 | end 21 | elseif type(job) == 'table' then 22 | for _,v in pairs(job) do 23 | if Player.job.name == v then 24 | callback = true 25 | break 26 | end 27 | end 28 | end 29 | else 30 | local Player 31 | if src then 32 | Player = QBCore.Functions.GetPlayer(src) 33 | else 34 | Player = QBCore.Functions.GetPlayer(source) 35 | end 36 | if type(job) == 'string' then 37 | if Player.PlayerData.job.name == job then 38 | callback = true 39 | end 40 | elseif type(job) == 'table' then 41 | for _,v in pairs(job) do 42 | if Player.PlayerData.job.name == v then 43 | callback = true 44 | break 45 | end 46 | end 47 | end 48 | end 49 | return callback 50 | end 51 | 52 | return Utils -------------------------------------------------------------------------------- /server/sv_armory.lua: -------------------------------------------------------------------------------- 1 | -- Get Inventory Item -- 2 | RegisterNetEvent('xt-pdextras:server:GetArmoryItem', function(armoryData, amount) 3 | local src = source 4 | local Player = QBCore.Functions.GetPlayer(src) 5 | if not Player then return end 6 | local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) 7 | local armoryCoords = Config.Armory.locations[armoryData.location].coords 8 | local dist = #(PlayerCoords - vector3(armoryCoords.x, armoryCoords.y, armoryCoords.z)) 9 | if dist >= 5 then return end 10 | if armoryData.item.type == 'weapon' then 11 | local info = { attachments = armoryData.item.attachments } 12 | if exports.ox_inventory:AddItem(src, armoryData.item.item, amount, info) then 13 | TriggerClientEvent('xt-pdextras:client:ArmoryCategory', src, armoryData.categoryInfo) 14 | return 15 | end 16 | else 17 | if exports.ox_inventory:AddItem(src, armoryData.item.item, amount) then 18 | TriggerClientEvent('xt-pdextras:client:ArmoryCategory', src, armoryData.categoryInfo) 19 | return 20 | end 21 | end 22 | end) 23 | 24 | -- Restock Inventory -- 25 | RegisterNetEvent('xt-pdextras:server:Restock', function(location) 26 | local src = source 27 | local Player = QBCore.Functions.GetPlayer(src) 28 | if not Player then return end 29 | local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) 30 | local armoryCoords = Config.Armory.locations[location].coords 31 | local dist = #(PlayerCoords - vector3(armoryCoords.x, armoryCoords.y, armoryCoords.z)) 32 | local itemCount = 0 33 | if dist >= 5 then return end 34 | 35 | for x = 1, #Config.Armory.restock do 36 | local Item = Config.Armory.restock[x] 37 | local Amount = exports.ox_inventory:Search(src, 'count', Item.item) 38 | if Amount ~= nil then 39 | if Amount < Item.max then 40 | local difference = (Item.max - Amount) 41 | if Item.type == 'weapon' and Item.attachments ~= 'none' then 42 | local info = { attachments = Item.attachments } 43 | if exports.ox_inventory:AddItem(src, Item.item, Item.amount, info) then 44 | itemCount = itemCount + 1 45 | if itemCount == #Config.Armory.restock then return end 46 | end 47 | else 48 | if Item.type == 'weapon' and Item.attachments ~= 'none' then 49 | local info = { attachments = Item.attachments } 50 | if exports.ox_inventory:AddItem(src, Item.item, Item.amount, info) then 51 | itemCount = itemCount + 1 52 | if itemCount == #Config.Armory.restock then return end 53 | end 54 | else 55 | if exports.ox_inventory:AddItem(src, Item.item, difference) then 56 | itemCount = itemCount + 1 57 | if itemCount == #Config.Armory.restock then return end 58 | end 59 | end 60 | end 61 | else 62 | itemCount = itemCount + 1 63 | if itemCount == #Config.Armory.restock then return end 64 | end 65 | else 66 | if Item.type == 'weapon' and Item.attachments ~= 'none' then 67 | local info = { attachments = Item.attachments } 68 | if exports.ox_inventory:AddItem(src, Item.item, Item.amount, info) then 69 | itemCount = itemCount + 1 70 | if itemCount == #Config.Armory.restock then return end 71 | end 72 | else 73 | if exports.ox_inventory:AddItem(src, Item.item, Item.amount) then 74 | itemCount = itemCount + 1 75 | if itemCount == #Config.Armory.restock then return end 76 | end 77 | end 78 | end 79 | end 80 | end) -------------------------------------------------------------------------------- /server/sv_bossmenu.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | local xTs = require 'modules.server' 3 | 4 | -- Fire -- 5 | RegisterNetEvent('xt-pdextras:server:FireOfficer', function(data) 6 | local src = source 7 | local Player = QBCore.Functions.GetPlayer(src) 8 | local TargetPlayer = QBCore.Functions.GetPlayerByCitizenId(data.cid) 9 | local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) 10 | local TargetCoords = GetEntityCoords(GetPlayerPed(TargetPlayer.PlayerData.source)) 11 | local dist = #(PlayerCoords - TargetCoords) 12 | if dist >= 10 then return end 13 | 14 | if Config.RenewedPhone then 15 | exports['qb-phone']:fireUser(data.job, data.cid) 16 | else 17 | TriggerEvent('qb-bossmenu:server:FireEmployee', data.cid) 18 | end 19 | 20 | QBCore.Functions.Notify(src, TargetPlayer.PlayerData.charinfo.firstname ..' ' ..TargetPlayer.PlayerData.charinfo.lastname..' was fired!', 'success') 21 | 22 | xTs.Log('pdbossmenu', 'blue', 'Officer Fired', 23 | '**Officer:** '..Player.PlayerData.charinfo.firstname..' ' ..Player.PlayerData.charinfo.lastname ..' \n'.. 24 | '**Fired:** '..TargetPlayer.PlayerData.charinfo.firstname ..' ' ..TargetPlayer.PlayerData.charinfo.lastname) 25 | end) 26 | 27 | -- Hire -- 28 | RegisterNetEvent('xt-pdextras:server:HireOfficer', function(data, job) 29 | local src = source 30 | local Player = QBCore.Functions.GetPlayer(src) 31 | local TargetPlayer = QBCore.Functions.GetPlayer(tonumber(data[1])) 32 | local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) 33 | local TargetCoords = GetEntityCoords(GetPlayerPed(TargetPlayer.PlayerData.source)) 34 | local dist = #(PlayerCoords - TargetCoords) 35 | if dist >= 10 then return end 36 | 37 | if Config.RenewedPhone then 38 | exports['qb-phone']:hireUser(job, TargetPlayer.PlayerData.citizenid, data[2]) 39 | else 40 | TriggerEvent('qb-bossmenu:server:HireEmployee', tonumber(data[1])) 41 | end 42 | 43 | QBCore.Functions.Notify(src, TargetPlayer.PlayerData.charinfo.firstname ..' ' ..TargetPlayer.PlayerData.charinfo.lastname..' was hired!', 'success') 44 | QBCore.Functions.Notify(tonumber(data[1]), 'You were hired as a '..QBCore.Shared.Jobs[data.job].grades[tostring(data.grade)].name..'!', 'success') 45 | 46 | xTs.Log('pdbossmenu', 'blue', 'Officer Hired', 47 | '**Officer:** '..Player.PlayerData.charinfo.firstname..' ' ..Player.PlayerData.charinfo.lastname ..' \n'.. 48 | '**Hired:** '..TargetPlayer.PlayerData.charinfo.firstname ..' ' ..TargetPlayer.PlayerData.charinfo.lastname ..' \n'.. 49 | '**Rank:** '..QBCore.Shared.Jobs[job].grades[data[2]].name) 50 | end) 51 | 52 | -- Change Rank -- 53 | RegisterNetEvent('xt-pdextras:server:ChangeRank', function(data) 54 | local gradeName = QBCore.Shared.Jobs[data.job].grades[tostring(data.grade)].name 55 | 56 | if Config.RenewedPhone then 57 | exports['qb-phone']:JobsHandler(source, data.job, data.cid, tostring(data.grade)) 58 | else 59 | local data = { 60 | cid = data.cid, 61 | grade = data.grade, 62 | gradename = gradeName 63 | } 64 | TriggerEvent('qb-bossmenu:server:GradeUpdate', data) 65 | QBCore.Functions.Notify(source, data.name..' Updated: '..gradeName, 'success') 66 | end 67 | end) 68 | 69 | -- Returns Officers -- 70 | lib.callback.register('xt-pdextras:server:GetOfficers', function(source, menuID) return xTs.GetOfficers(source, menuID) end) 71 | 72 | -- Returns Funds -- 73 | lib.callback.register('xt-pdextras:server:GetFunds', function(source, menuID) return xTs.GetFunds(source, menuID) end) -------------------------------------------------------------------------------- /server/sv_certs.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | local xTs = require 'modules.server' 3 | 4 | -- Grant Certs -- 5 | RegisterNetEvent('xt-pdextras:server:GrantCert', function(data) 6 | local src = source 7 | local Player = QBCore.Functions.GetPlayer(src) 8 | local targetPlayer = QBCore.Functions.GetPlayer(tonumber(data[1])) 9 | 10 | if not targetPlayer then 11 | QBCore.Functions.Notify(src, 'Invalid ID', 'error') 12 | return 13 | end 14 | 15 | local certs = targetPlayer.PlayerData.metadata["pdcerts"] 16 | local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) 17 | local TargetCoords = GetEntityCoords(GetPlayerPed(targetPlayer.PlayerData.source)) 18 | local dist = #(PlayerCoords - vector3(TargetCoords.x, TargetCoords.y, TargetCoords.z)) 19 | if dist >= 10 then return end 20 | 21 | if certs[data[2]] then QBCore.Functions.Notify(src, 'They are already certified!', 'error') return end 22 | certs[data[2]] = true 23 | targetPlayer.Functions.SetMetaData('pdcerts', certs) 24 | QBCore.Functions.Notify(src, 'Certification Granted', 'success') 25 | 26 | xTs.Log(Config.Webhooks.Certs.name, Config.Webhooks.Certs.color, 'PD Certification Granted', 27 | '**Officer:** '..Player.PlayerData.charinfo.firstname..' ' ..Player.PlayerData.charinfo.lastname ..' \n'.. 28 | '**Player:** '..targetPlayer.PlayerData.charinfo.firstname ..' ' ..targetPlayer.PlayerData.charinfo.lastname ..' \n'.. 29 | '**Certification:** '..Config.Certifications.certs[data[2]]) 30 | end) 31 | 32 | -- Revoke Cert -- 33 | RegisterNetEvent('xt-pdextras:server:RevokeCert', function(data) 34 | local src = source 35 | local Player = QBCore.Functions.GetPlayer(src) 36 | local targetPlayer = QBCore.Functions.GetPlayer(tonumber(data[1])) 37 | 38 | if not targetPlayer then 39 | QBCore.Functions.Notify(src, 'Invalid ID', 'error') 40 | return 41 | end 42 | 43 | local certs = targetPlayer.PlayerData.metadata["pdcerts"] 44 | local PlayerCoords = GetEntityCoords(GetPlayerPed(src)) 45 | local TargetCoords = GetEntityCoords(GetPlayerPed(targetPlayer.PlayerData.source)) 46 | local dist = #(PlayerCoords - vector3(TargetCoords.x, TargetCoords.y, TargetCoords.z)) 47 | if dist >= 10 then return end 48 | 49 | if not certs[data[2]] then QBCore.Functions.Notify(src, 'They do not have this certification!', 'error') return end 50 | certs[data[2]] = false 51 | targetPlayer.Functions.SetMetaData('pdcerts', certs) 52 | QBCore.Functions.Notify(src, 'Certification Revoked', 'success') 53 | 54 | xTs.Log(Config.Webhooks.Certs.name, Config.Webhooks.Certs.color, 'PD Certification Revoked', 55 | '**Officer:** '..Player.PlayerData.charinfo.firstname..' ' ..Player.PlayerData.charinfo.lastname ..' \n'.. 56 | '**Player:** '..targetPlayer.PlayerData.charinfo.firstname ..' ' ..targetPlayer.PlayerData.charinfo.lastname ..' \n'.. 57 | '**Certification:** '..Config.Certifications.certs[data[2]]) 58 | end) 59 | 60 | 61 | -- Grant / Revoke Certs -- 62 | lib.addCommand(Config.Certifications.command, { 63 | help = 'PD Certifications (Police Command Only)', 64 | params = {}, 65 | restricted = false 66 | }, function(source) 67 | local src = source 68 | local Player = QBCore.Functions.GetPlayer(src) 69 | if not Utils.CheckJob(Config.PoliceJobs, src) then QBCore.Functions.Notify(src, 'You can not use this command!', 'error') return end 70 | if Player.PlayerData.job.grade.level <= Config.Certifications.reqRank then QBCore.Functions.Notify(src, 'You don\'t have the required rank to use this!', 'error') return end 71 | TriggerClientEvent('xt-pdextras:client:CertificationMenu', src) 72 | end) 73 | 74 | -- View Certs Command -- 75 | lib.addCommand(Config.Certifications.viewCommand, { 76 | help = 'View PD Certifications (Police Command Only)', 77 | params = {}, 78 | restricted = false 79 | }, function(source) 80 | local src = source 81 | local Player = QBCore.Functions.GetPlayer(src) 82 | if not Utils.CheckJob(Config.PoliceJobs, src) then QBCore.Functions.Notify(src, 'You can not use this command!', 'error') return end 83 | 84 | local certs = Player.PlayerData.metadata["pdcerts"] 85 | TriggerClientEvent('xt-pdextras:client:ViewCerts', src, certs) 86 | end) -------------------------------------------------------------------------------- /server/sv_dutyblips.lua: -------------------------------------------------------------------------------- 1 | -- Some code is reused from qb-policejob, but im not a braindead dummy and made it better 2 | 3 | local GPSBlips = {} 4 | 5 | -- Update Officer Blips -- 6 | local function UpdateBlips() 7 | local officerBlips = {} 8 | local foundSource = nil 9 | 10 | for x, t in pairs(GPSBlips) do 11 | local ply = x 12 | local pPed = GetPlayerPed(ply) 13 | local coords, heading = GetEntityCoords(pPed), GetEntityHeading(pPed) 14 | 15 | officerBlips[ply] = { 16 | source = ply, 17 | label = t.callsign, 18 | vehClass = t.vehClass, 19 | job = t.job, 20 | location = { 21 | x = coords.x, 22 | y = coords.y, 23 | z = coords.z, 24 | w = heading 25 | } 26 | } 27 | end 28 | for x, t in pairs(GPSBlips) do 29 | if x == t.source then 30 | TriggerClientEvent("xt-pdextras:client:updateDutyBlips", t.source, officerBlips) 31 | end 32 | end 33 | end 34 | 35 | -- Get Veh Class from Cache -- 36 | RegisterNetEvent('xt-pdextras:server:updateVehClass',function(vehClass) 37 | local src = source 38 | if not GPSBlips[src] then GPSBlips[src] = {} end 39 | GPSBlips[src].vehClass = vehClass 40 | end) 41 | 42 | -- Update Officer Blip Info -- 43 | RegisterNetEvent('xt-pdextras:server:updateBlipInfo',function(job, callsign) 44 | local src = source 45 | if not job and not callsign then 46 | if GPSBlips[src] then 47 | GPSBlips[src] = nil 48 | return 49 | end 50 | end 51 | 52 | if not GPSBlips[src] then GPSBlips[src] = {} end 53 | GPSBlips[src].source = src 54 | GPSBlips[src].job = job 55 | GPSBlips[src].callsign = callsign 56 | end) 57 | 58 | -- Constant Blips Update -- 59 | SetInterval(function() 60 | UpdateBlips() 61 | end, 1000) -------------------------------------------------------------------------------- /server/sv_evidence.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | local xTs = require 'modules.server' 3 | 4 | -- Open Personal or Other Locker -- 5 | RegisterNetEvent('xt-pdextras:server:RegisterPDLocker', function(LOCATION, CID) 6 | local src = source 7 | local Player = QBCore.Functions.GetPlayer(src) 8 | if not Player then return end 9 | local EvidenceInfo = Config.EvidenceRooms[LOCATION] 10 | local pCoords = GetEntityCoords(GetPlayerPed(src)) 11 | local lockerCoords = vec3(EvidenceInfo.coords.x, EvidenceInfo.coords.y, EvidenceInfo.coords.z) 12 | local dist = #(lockerCoords - pCoords) 13 | if dist >= 5 then return end 14 | 15 | exports.ox_inventory:RegisterStash('PD-Locker-'..CID, 'PD Locker '..CID, Config.EvidenceLockerSize.slots, Config.EvidenceLockerSize.size) 16 | end) 17 | 18 | -- Open Evidence Locker -- 19 | RegisterNetEvent('xt-pdextras:server:RegisterEvidenceLocker', function(DRAWER, LOCATION) 20 | local src = source 21 | local EvidenceInfo = Config.EvidenceRooms[LOCATION] 22 | local pCoords = GetEntityCoords(GetPlayerPed(src)) 23 | local lockerCoords = vec3(EvidenceInfo.coords.x, EvidenceInfo.coords.y, EvidenceInfo.coords.z) 24 | local dist = #(lockerCoords - pCoords) 25 | if dist >= 5 then return end 26 | 27 | exports.ox_inventory:RegisterStash('PD-Evidence-'..DRAWER, 'PD Evidence '..DRAWER, Config.EvidenceLockerSize.slots, Config.EvidenceLockerSize.size) 28 | end) 29 | 30 | -- Open Trash Can -- 31 | RegisterNetEvent('xt-pdextras:server:RegisterTrash', function(LOCATION) 32 | local src = source 33 | local TrashInfo = Config.EvidenceRooms[LOCATION] 34 | local pCoords = GetEntityCoords(GetPlayerPed(src)) 35 | local TrashCoords = vec3(TrashInfo.coords.x, TrashInfo.coords.y, TrashInfo.coords.z) 36 | local dist = #(TrashCoords - pCoords) 37 | if dist >= 5 then return end 38 | 39 | exports.ox_inventory:RegisterStash('PD-Trash-'..LOCATION, 'PD Trash '..LOCATION, Config.EvidenceLockerSize.slots, Config.EvidenceLockerSize.size) 40 | end) 41 | 42 | -- Evidence Command -- 43 | lib.addCommand(Config.EvidenceCommand, { 44 | help = 'Open / Create an Evidence Locker', 45 | params = { 46 | { 47 | name = 'locker', 48 | type = 'number', 49 | help = 'Evidence Locker #', 50 | optional = false, 51 | }, 52 | }, 53 | restricted = false 54 | }, function(source, args, raw) 55 | if not Utils.CheckJob(Config.PoliceJobs, source) then return end 56 | local closestLocker = xTs.ClosestEvidenceLocker(source, Config.EvidenceCommandDistance) 57 | if not closestLocker then QBCore.Functions.Notify(source, 'You are not near an evidence locker!', 'error') end 58 | TriggerClientEvent('xt-pdextras:client:OpenEvidenceLocker', source, args.locker, closestLocker) 59 | end) 60 | 61 | -- Empty Trash Cans -- 62 | lib.cron.new(('*/%s * * * *'):format(Config.WipeTrashInterval), function() 63 | for x = 1, #Config.EvidenceRooms do 64 | local PDTrash = MySQL.scalar.await('SELECT `name` FROM `ox_inventory` WHERE `name` = ? LIMIT 1', { ('PD-Trash-%s'):format(x) }) 65 | if PDTrash then 66 | exports.ox_inventory:ClearInventory(PDTrash) 67 | Utils.Debug(('%s Emptied'):format(PDTrash)) 68 | end 69 | end 70 | end) -------------------------------------------------------------------------------- /server/sv_main.lua: -------------------------------------------------------------------------------- 1 | local Utils = require 'modules.shared' 2 | local xTs = require 'modules.server' 3 | 4 | -- Get All Officers -- 5 | lib.callback.register('xt-pdextras:server:CertsAndRank', function(source, target) return xTs.CertsAndRank(source, target) end) 6 | 7 | -- Get Player Vehicles in Specific Garage -- 8 | lib.callback.register('xt-pdextras:server:getPlayerVehiclesAtGarage', function(source, data) 9 | local vehicles = MySQL.query.await('SELECT * FROM `player_vehicles` WHERE `citizenid` = ? AND `garage` = ?', { data[1], data[2] }) 10 | if vehicles and vehicles[1] then 11 | return vehicles 12 | end 13 | return nil 14 | end) 15 | 16 | -- Fine Player -- 17 | if Config.FineCommand.enable then 18 | RegisterNetEvent('xt-pdextras:server:FinePlayer', function(fine) 19 | local src = source 20 | local player = QBCore.Functions.GetPlayer(src) 21 | local targetPlayer = QBCore.Functions.GetPlayer(tonumber(fine[1])) 22 | 23 | if not targetPlayer then QBCore.Functions.Notify(src, 'Invalid ID', 'error') return end 24 | 25 | local pCoords = GetEntityCoords(GetPlayerPed(src)) 26 | local targetCoords = GetEntityCoords(GetPlayerPed(targetPlayer.PlayerData.source)) 27 | local dist = #(pCoords - targetCoords) 28 | 29 | if dist >= 6 then QBCore.Functions.Notify(src, 'You\'re too far away!', 'error') return end 30 | 31 | if targetPlayer.Functions.RemoveMoney('bank', math.ceil(fine[2]), 'paid-fine', 'xt-pdextras') then 32 | if Config.FineCommand.commission.enable then player.Functions.AddMoney('bank', math.ceil((fine[2] * Config.FineCommand.commission.amount)), 'fine-commission', 'xt-pdextras') end 33 | xTs.Log(Config.Webhooks.Fines.name, Config.Webhooks.Fines.color, Config.Webhooks.Fines.title, 34 | '**Officer:** '..player.PlayerData.charinfo.firstname..' ' ..player.PlayerData.charinfo.lastname ..' \n'.. 35 | '**Player:** '..targetPlayer.PlayerData.charinfo.firstname ..' ' ..targetPlayer.PlayerData.charinfo.lastname ..' \n'.. 36 | '**Fine Amount:** $'..math.ceil(fine[2])..' \n'.. 37 | '**Fine Reason: **'..fine[3]) 38 | end 39 | end) 40 | 41 | lib.addCommand(Config.FineCommand.command, { 42 | help = 'Fine a Player (Police Only)', 43 | params = {}, 44 | restricted = false 45 | }, function(source) 46 | local src = source 47 | TriggerClientEvent('xt-pdextras:client:FinePlayerMenu', src) 48 | end) 49 | end -------------------------------------------------------------------------------- /shared/config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | -- Debug Configs -- 4 | Config.Debug = true 5 | Config.DebugPoly = false 6 | 7 | -- General Configs -- 8 | Config.RenewedPhone = true -- Enable if using Renewed-Phone / Uses qb-management if false 9 | Config.PoliceJobs = { 'police' } -- Set to the name of your police job(s) 10 | Config.Fuel = 'cdn-fuel' -- Set to the name of your fuel script 11 | 12 | -- Enable / Disable Features -- 13 | Config.UseArmory = true -- Toggle armory usage 14 | Config.UseBoatHeliLocations = true -- Toggle helis/boats usage 15 | Config.UseBossMenus = true -- Toggle bossmenu usage 16 | Config.UseEvidence = true -- Toggle evidence/trash usage 17 | Config.UseGarages = true -- Toggle garage usage 18 | 19 | -- Fines Config -- 20 | Config.FineCommand = { 21 | enable = true, 22 | command = 'fine', 23 | commission = { enable = true, amount = 0.05 } -- 5% of Fine 24 | } 25 | 26 | -- Webhooks Config -- 27 | Config.Webhooks = { 28 | Fines = { name = 'fines', title = 'Fined Player', color = 'green' }, 29 | Certs = { name = 'pdcerts', color = 'green' }, 30 | } 31 | 32 | -- Boss Menu Configs -- 33 | Config.BossMenus = { 34 | { label = 'PD Management', job = 'police', rank = 3, cert = 'none', coords = vec3(461.42, -986.25, 30.73), radius = 0.4 }, 35 | { label = 'PD Management', job = 'police', rank = 4, cert = 'none', coords = vec3(447.05, -974.0, 30.5), radius = 0.4 } 36 | } 37 | 38 | -- Evidence Rooms Config -- 39 | Config.EvidenceRooms = { -- Locker locations (Spawn Peds) 40 | { label = 'Mission Row Evidence Room', model = 'a_f_y_femaleagent', coords = vec4(465.27, -990.02, 24.91, 91.33), scenario = 'WORLD_HUMAN_CLIPBOARD' }, 41 | } 42 | Config.EvidenceLockerSize = { slots = 50, size = 100000 } -- Size of lockers 43 | Config.EvidenceCommand = 'evidence' -- Ex: /evidence 1 -- Opens Evidence 1 44 | Config.EvidenceCommandDistance = 10 -- Evidence command is usable if player is within this distance from any accessible evidence locker 45 | Config.WipeTrashInterval = 1 -- Minutes between clearing the trash cans 46 | 47 | -- Grant / Revoke Certifications -- 48 | Config.Certifications = { 49 | command = 'pdcerts', -- Grant/Revoke Certs Menu 50 | viewCommand = 'viewcerts', -- View personal certs 51 | reqRank = 4, -- Required rank to grant/revoke certs 52 | certs = { -- Metadata values 53 | ['airone'] = 'Air One', -- Air-1 54 | ['mbu'] = 'Motorbike Unit', -- Motorbike Unit 55 | ['fto'] = 'Field Training Officer', -- Field Training Officer 56 | ['hsu'] = 'High Speed Unit', -- High Speed Unit 57 | ['classthree'] = 'Class 3 Weapons', -- Class 3 58 | ['classtwo'] = 'Class 2 Weapons', -- Class 2 59 | ['alr'] = 'Armalite Rifle', -- Armalite Rifle 60 | ['canine'] = 'Canine', -- Canine 61 | ['swat'] = 'S.W.A.T', -- SWAT 62 | } 63 | } 64 | 65 | -------------------------------------------- 66 | 67 | QBCore = exports['qb-core']:GetCoreObject() 68 | sharedItems = exports.ox_inventory:Items() 69 | sharedVehicles = QBCore.Shared.Vehicles -------------------------------------------------------------------------------- /shared/sh_armory.lua: -------------------------------------------------------------------------------- 1 | Config = Config or {} 2 | 3 | -- Armory Config -- 4 | -- locations 5 | -- Quick Restock Config 6 | -- Armory Items & Categories 7 | 8 | Config.Armory = { 9 | title = 'Police Armory', 10 | icon = 'fas fa-handcuffs', 11 | locations = { 12 | { job = 'police', coords = vector3(458.67, -978.52, 30.69), size = vec3(7.0, 0.6, 1.0), heading = 90 } 13 | }, 14 | restock = { -- Restock Inventory w/ These Items 15 | { 16 | item = 'WEAPON_COMBATPISTOL', 17 | amount = 1, 18 | max = 1, 19 | type = 'weapon', 20 | attachments = { 21 | { component = "COMPONENT_AT_PI_FLSH", label = "Flashlight" }, 22 | } 23 | }, 24 | { 25 | item = 'ammo-9', 26 | amount = 100, 27 | max = 100, 28 | type = 'item' 29 | }, 30 | { 31 | item = 'ifaks', 32 | amount = 15, 33 | max = 15, 34 | type = 'item' 35 | }, 36 | { 37 | item = 'armor', 38 | amount = 5, 39 | max = 5, 40 | type = 'item' 41 | }, 42 | { 43 | item = 'WEAPON_STUNGUN', 44 | amount = 1, 45 | max = 1, 46 | type = 'weapon', 47 | attachments = 'none' 48 | }, 49 | { 50 | item = 'handcuffs', 51 | amount = 1, 52 | max = 1, 53 | type = 'item', 54 | }, 55 | }, 56 | items = { 57 | { 58 | title = 'Weapons', 59 | type = 'weapons', 60 | icon = 'fas fa-gun', 61 | items = { 62 | { 63 | type = 'weapon', 64 | item = 'WEAPON_COMBATPISTOL', 65 | rank = 0, 66 | cert = 'none', 67 | attachments = { 68 | { component = "COMPONENT_AT_PI_FLSH", label = "Flashlight" }, 69 | } 70 | }, 71 | { 72 | type = 'weapon', 73 | item = 'WEAPON_STUNGUN', 74 | rank = 0, 75 | cert = 'none', 76 | attachments = { 77 | { component = "COMPONENT_AT_PI_FLSH", label = "Flashlight" }, 78 | } 79 | }, 80 | { 81 | type = 'weapon', 82 | item = 'WEAPON_NIGHTSTICK', 83 | rank = 0, 84 | cert = 'none', 85 | }, 86 | } 87 | }, 88 | { 89 | title = 'Ammo', 90 | type = 'ammo', 91 | icon = 'fas fa-bullseye', 92 | items = { 93 | { label = 'Pistol Ammo', item = 'ammo-9' }, 94 | { label = 'SMG Ammo', item = 'ammo-rifle' }, 95 | { label = 'Rifle Ammo', item = 'ammo-rifle2' }, 96 | { label = 'Shotgun Ammo', item = 'ammo-shotgun' }, 97 | } 98 | }, 99 | { 100 | title = 'Medical Items', 101 | type = 'medical', 102 | icon = 'fas fa-hospital', 103 | items = { 104 | { 105 | type = 'item', 106 | item = 'ifaks', 107 | rank = 0, 108 | cert = 'none', 109 | }, 110 | { 111 | type = 'item', 112 | item = 'armor', 113 | rank = 0, 114 | cert = 'none', 115 | }, 116 | } 117 | }, 118 | { 119 | title = 'Police Tools & Equipment', 120 | type = 'tools', 121 | icon = 'fas fa-certificate', 122 | items = { 123 | { 124 | type = 'item', 125 | item = 'handcuffs', 126 | rank = 0, 127 | cert = 'none', 128 | }, 129 | { 130 | type = 'item', 131 | item = 'WEAPON_FLASHLIGHT', 132 | rank = 0, 133 | cert = 'none', 134 | }, 135 | { 136 | type = 'item', 137 | item = 'empty_evidence_bag', 138 | rank = 0, 139 | cert = 'none', 140 | }, 141 | { 142 | type = 'item', 143 | item = 'police_stormram', 144 | rank = 0, 145 | cert = 'none', 146 | }, 147 | } 148 | }, 149 | } 150 | } -------------------------------------------------------------------------------- /shared/sh_dutyblips.lua: -------------------------------------------------------------------------------- 1 | Config = Config or {} 2 | 3 | -- Colors of Blips Based on Job -- 4 | Config.JobColors = { 5 | ['police'] = { 6 | color = 26, 7 | } 8 | } 9 | 10 | -- Blips Sprite / Size Based on Vehicle Class -- 11 | Config.DutyBlips = { 12 | ['none'] = { -- Walking 13 | size = 1.0, 14 | sprite = 126 15 | }, 16 | ['veh'] = { -- Other vehicles 17 | classes = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20, 21, 22 }, 18 | size = 1.0, 19 | sprite = 225 20 | }, 21 | ['pdveh'] = { -- PD vehicles 22 | classes = { 18, 19 }, 23 | size = 1.0, 24 | sprite = 56 25 | }, 26 | ['bike'] = { -- Motorbike 27 | classes = { 13 }, 28 | size = 1.0, 29 | sprite = 226 30 | }, 31 | ['heli'] = { -- Helicopters 32 | classes = { 15 }, 33 | size = 1.0, 34 | sprite = 43 35 | }, 36 | ['plane'] = { -- Planes 37 | classes = { 16 }, 38 | size = 1.0, 39 | sprite = 307 40 | }, 41 | ['boat'] = { -- Boats 42 | classes = { 14 }, 43 | size = 1.0, 44 | sprite = 427 45 | }, 46 | } -------------------------------------------------------------------------------- /shared/sh_garage.lua: -------------------------------------------------------------------------------- 1 | Config = Config or {} 2 | 3 | -- Get Vehicle Keys Function -- 4 | function Config.GetVehicleKeys(PLATE) 5 | -- Add your own acquired vehicle keys -- 6 | 7 | end 8 | 9 | -- Garages Config -- 10 | Config.VehiclePlates = 'LSPD' -- Start of Plates 11 | Config.Garages = { 12 | { 13 | label = 'Mission Row Garage', 14 | job = 'police', 15 | Ped = { model = 's_m_y_cop_01', coords = vec4(452.5, -993.56, 26.0, 179.85), scenario = 'WORLD_HUMAN_CLIPBOARD' }, 16 | VehicleSpawn = vec4(447.34, -997.31, 25.15, 179.28), 17 | Vehicles = { -- Vehicle model, required rank, required cert, and vehicle mods 18 | { model = 'police', rank = 0, cert = 'none', engine = 3, brakes = 2, transmission = 2, suspension = 2, armor = 2, turbo = true }, 19 | { model = 'police2', rank = 0, cert = 'none', engine = 3, brakes = 2, transmission = 2, suspension = 2, armor = 2, turbo = true }, 20 | { model = 'police3', rank = 0, cert = 'none', engine = 3, brakes = 2, transmission = 2, suspension = 2, armor = 2, turbo = true }, 21 | { model = 'policeb', rank = 0, cert = 'mbu', engine = 3, brakes = 2, transmission = 2, suspension = 2, armor = 2, turbo = true }, 22 | } 23 | }, 24 | } 25 | 26 | -- Boats & Helis Config -- 27 | Config.BoatsAndHelis = { 28 | { 29 | label = 'Mission Row Helipad', 30 | job = 'police', 31 | spawn = vec4(449.9, -981.46, 44.06, 360.0), 32 | class = 15, -- Vehicle Class Allowed 33 | ped = { 34 | model = 's_m_y_cop_01', 35 | scenario = 'WORLD_HUMAN_CLIPBOARD', 36 | coords = vec4(463.67, -982.35, 43.69, 92.01), 37 | icon = 'fas fa-helicopter' 38 | }, 39 | returnLocations = { 40 | { 41 | coords = { 42 | vec(455.12957763672, -986.68438720703, 43), 43 | vec(454.99450683594, -975.41595458984, 43), 44 | vec(443.66360473633, -975.45849609375, 43), 45 | vec(443.81823730469, -986.82061767578, 43) 46 | }, 47 | radius = 10 48 | } 49 | }, 50 | vehicles = { 51 | { name = 'PD Maverick', model = 'polmav', livery = 0, rank = 0, cert = 'none' }, 52 | }, 53 | }, 54 | { 55 | label = 'PD Boats Dock', 56 | job = 'police', 57 | spawn = vec4(-785.92, -1438.53, 0.12, 138.35), 58 | class = 14, -- Vehicle Class Allowed 59 | ped = { 60 | model = 's_m_y_cop_01', 61 | scenario = 'WORLD_HUMAN_CLIPBOARD', 62 | coords = vec4(-782.71, -1441.87, 1.6, 283.12), 63 | icon = 'fas fa-anchor' 64 | }, 65 | returnLocations = { 66 | { 67 | coords = { 68 | vec(-792.79852294922, -1452.7060546875, -3), 69 | vec(-771.24548339844, -1427.0616455078, -3), 70 | vec(-776.81048583984, -1422.7752685547, -3), 71 | vec(-798.68078613281, -1447.5490722656, -3) 72 | }, 73 | radius = 10 74 | } 75 | }, 76 | vehicles = { 77 | { name = 'PD Dinghy', model = 'dinghy3', rank = 0, cert = 'none' }, 78 | { name = 'PD Predator', model = 'predator', rank = 0, cert = 'none' }, 79 | }, 80 | }, 81 | } -------------------------------------------------------------------------------- /shared/sh_toolsconfig.lua: -------------------------------------------------------------------------------- 1 | Config = Config or {} 2 | 3 | -- Nightvision Configuration -- 4 | Config.Nightvision = { 116, 117 } -- Night vision goggles clothing indexes 5 | Config.ThermalKey = 172 -- Toggle Thermal (Up Arrow) 6 | 7 | -- Slim Jim Configuration -- 8 | Config.SlimJim = { 9 | difficulty = { -- Choose random difficulty 10 | {'easy', 'easy'} 11 | }, 12 | keys = { 'e' } 13 | } 14 | 15 | -- Slim Jim Success Function -- 16 | function Config.SlimJimSuccess(success, vehicle, vehCoords) 17 | local chance = math.random(1, 100) 18 | local pdChance = 50 19 | if success then 20 | QBCore.Functions.Notify('You unlocked the door!', 'success') 21 | pdChance = math.random(10, 20) 22 | else 23 | QBCore.Functions.Notify('You failed to unlock the door!', 'error') 24 | pdChance = math.random(50, 80) 25 | end 26 | 27 | if chance <= pdChance then exports['ps-dispatch']:CarJacking(vehicle) end 28 | end --------------------------------------------------------------------------------