├── 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 |
6 |
7 | ## Features:
8 |
9 | Police Tools
10 |
11 | - Night vision goggles w/ thermal vision toggle
12 | - Slimjims - Easier "lockpick" for Police to open vehicles
13 |
14 |
15 |
16 |
17 | Police Certifications
18 |
19 | - Granted / Revoked by Officers w/ access to the bossmenus
20 | - View your own certs w/ a command
21 |
22 |
23 |
24 |
25 | Boss Menus
26 |
27 | - View officers info (rank, certifications)
28 | - Change officers ranks
29 | - Grant Revoke certifications
30 | - View officers by specific rank
31 |
32 |
33 |
34 |
35 | Evidence Lockers
36 |
37 | - Create / Open evidence lockers at specific locations, or use a command nearby to open evidence lockers
38 | - PD "trash can" that will regularly wipe (set in config)
39 |
40 |
41 |
42 |
43 | Armory
44 |
45 | - Restock option. Set certain items and max amounts for a "quick refill"
46 | - Rank / Cert checks for each item
47 | - Categorized items. Weapons, ammo, medical items, & tools
48 |
49 |
50 |
51 |
52 | Police Garages
53 |
54 | - Rank & Cert checks for each vehicle
55 |
56 |
57 |
58 |
59 | Police Garages / Boats / Helis
60 |
61 | - Rank & Cert checks for each vehicle
62 |
63 |
64 |
65 |
66 | Commands
67 |
68 | - View your own certs
69 | - Send players a fine
70 |
71 |
72 |
73 |
74 | Duty Blips
75 |
76 | - Blip sprite changes based on vehicle
77 | - Different blip colors for each LEO job
78 |
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
--------------------------------------------------------------------------------