├── .gitattributes
├── LICENSE
├── README.md
├── client
└── main.lua
├── config
├── client.lua
└── server.lua
├── fxmanifest.lua
├── locales
├── de.json
└── en.json
├── modules
├── bridge
│ ├── esx
│ │ └── server.lua
│ ├── nd
│ │ └── server.lua
│ ├── ox
│ │ └── server.lua
│ ├── qb
│ │ └── server.lua
│ └── qbx
│ │ └── server.lua
└── utils
│ ├── client.lua
│ └── server.lua
├── server
└── main.lua
└── shared
└── main.lua
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 KostaZ
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Peak Scripts Advanced Warehouse Robbery V2
2 |
3 | ## Features
4 | - Compatible with all major frameworks (QBX, QB, OX, ESX, ND)
5 | - Supports ox_target, interact, and sleepless_interact interaction systems
6 | - Extremely detailed configuration file
7 | - Protected against cheaters
8 | - Support for ox_lib logger
9 | - Rewards are based on a chance
10 | - Utilizes ox_lib for micro-optimizations and to replace natives
11 | - Guards protecting the warehouse
12 | - Laptop minigame to unlock the exits
13 | - Optimized for performance (0.00 - 0.01)
14 |
15 | ## Dependencies
16 | - [qbx_core](https://github.com/Qbox-project/qbx_core/releases), [qb-core](), [ox_core](https://github.com/overextended/ox_core), [es_extended](https://github.com/esx-framework/esx_core/tree/main/%5Bcore%5D/es_extended) or [ND_Core](https://github.com/ND-Framework/ND_Core)
17 | - [ox_inventory](https://github.com/overextended/ox_inventory/releases)
18 | - [ox_lib](https://github.com/overextended/ox_lib/releases)
19 | - [ox_target](https://github.com/overextended/ox_target/releases), [interact](https://github.com/darktrovx/interact) or [sleepless_interact](https://github.com/Sleepless-Development/sleepless_interact/releases)
20 | - [bl_ui](https://github.com/Byte-Labs-Studio/bl_ui) (optional)
21 |
22 | ## [Preview](https://youtu.be/aYylFenbxaU)
23 |
--------------------------------------------------------------------------------
/client/main.lua:
--------------------------------------------------------------------------------
1 | local config = require 'config.client'
2 | local serverConfig = require 'config.server'
3 | local utils = require 'modules.utils.client'
4 | local spawnedEntities = {
5 | props = {}
6 | }
7 |
8 | CreateThread(function()
9 | if config.blip.enabled then
10 | local blip = AddBlipForCoord(config.blip.location.x, config.blip.location.y, config.blip.location.z)
11 |
12 | SetBlipSprite(blip, config.blip.sprite)
13 | SetBlipDisplay(blip, config.blip.display)
14 | SetBlipScale(blip, config.blip.scale)
15 | SetBlipColour(blip, config.blip.colour)
16 | BeginTextCommandSetBlipName("STRING")
17 | AddTextComponentString(config.blip.label)
18 | EndTextCommandSetBlipName(blip)
19 | SetBlipAsShortRange(blip, true)
20 | end
21 | end)
22 |
23 | AddStateBagChangeHandler('peak_warehouse:pedHandler', nil, function(bagName, _, value)
24 | if not value then return end
25 |
26 | local guard = GetEntityFromStateBagName(bagName)
27 | if not guard and not DoesEntityExist(guard) then return end
28 |
29 | local netId = NetworkGetNetworkIdFromEntity(guard)
30 | if not netId then return end
31 |
32 | SetPedCombatAttributes(guard, 46, true)
33 | SetPedCombatMovement(guard, config.guardConfig.aggresiveness)
34 | SetEntityHealth(guard, config.guardConfig.health)
35 | SetEntityMaxHealth(guard, config.guardConfig.health)
36 | SetPedFleeAttributes(guard, 0, false)
37 | SetPedCombatAbility(guard, 2)
38 | SetPedCanRagdollFromPlayerImpact(guard, false)
39 | SetPedAsEnemy(guard, true)
40 | SetPedDropsWeaponsWhenDead(guard, false)
41 | SetPedAlertness(guard, config.guardConfig.alertness)
42 | SetPedSeeingRange(guard, 150.0)
43 | SetPedHearingRange(guard, 150.0)
44 | SetPedAsCop(guard, true)
45 | SetPedCombatAbility(guard, 2)
46 | SetPedAccuracy(guard, config.guardConfig.accuracy)
47 | SetPedArmour(guard, config.guardConfig.armor)
48 | SetPedSuffersCriticalHits(guard, config.guardConfig.sufferHeadshots)
49 | TaskCombatPed(guard, cache.ped, 0, 16)
50 |
51 | Entity(guard).state:set('peak_warehouse:pedHandler', nil)
52 | end)
53 |
54 | lib.callback.register('peak_warehouse:client:startRobbery', function()
55 |
56 | local success = utils.thermiteMinigame()
57 | if not success then return end
58 |
59 | if lib.progressCircle({
60 | duration = 5000,
61 | label = locale('progress.placing_c4'),
62 | useWhileDead = false,
63 | position = 'bottom',
64 | canCancel = true,
65 | disable = {
66 | move = true,
67 | car = true,
68 | combat = true,
69 | mouse = false,
70 | },
71 | anim = {
72 | dict = 'anim@heists@ornate_bank@thermal_charge_heels',
73 | clip = 'thermal_charge',
74 | flag = 16,
75 | },
76 | prop = {
77 | model = `prop_c4_final_green`,
78 | pos = vec3(0.06, 0.0, 0.06),
79 | rot = vec3(90.0, 0.0, 0.0),
80 | }
81 | }) then
82 | return true
83 | else
84 | return false
85 | end
86 | end)
87 |
88 | lib.callback.register('peak_warehouse:client:createC4', function()
89 | local coords = serverConfig.interactLocations.electricalBox.coords
90 |
91 | local c4Prop = CreateObject(`prop_c4_final_green`, coords.x , coords.y, coords.z, false, false, true)
92 | SetEntityHeading(c4Prop, 0.0)
93 | FreezeEntityPosition(c4Prop, true)
94 |
95 | Wait(1000)
96 |
97 | if c4Prop and DoesEntityExist(c4Prop) then
98 | DeleteEntity(c4Prop)
99 | end
100 |
101 | AddExplosion(coords.x, coords.y - 0.5, coords.z, 5, 1.0, true, false, 1.0)
102 |
103 | return true
104 | end)
105 |
106 | RegisterNetEvent('peak_warehouse:client:enterWarehouse', function()
107 | if GetInvokingResource() then return end
108 |
109 | local coords = serverConfig.interactLocations.interior.coords
110 |
111 | DoScreenFadeOut(500)
112 | Wait(1000)
113 | SetEntityCoords(cache.ped, coords.x, coords.y, coords.z, false, false, false, false)
114 | SetEntityHeading(cache.ped, 266.59)
115 | DoScreenFadeIn(500)
116 | end)
117 |
118 | RegisterNetEvent('peak_warehouse:client:exitWarehouse', function(type)
119 | if GetInvokingResource() then return end
120 |
121 | local exitLocation = serverConfig.interactLocations[type == 'front' and 'entrance' or 'backEntrance']
122 |
123 | DoScreenFadeOut(500)
124 | Wait(1000)
125 | SetEntityCoords(cache.ped, exitLocation.coords.x, exitLocation.coords.y, exitLocation.coords.z, false, false, false, false)
126 | SetEntityHeading(cache.ped, 266.59)
127 | DoScreenFadeIn(500)
128 | end)
129 |
130 | lib.callback.register('peak_warehouse:client:hackLaptop', function()
131 |
132 | local success = utils.laptopMinigame()
133 | if not success then return false end
134 |
135 | return true
136 | end)
137 |
138 | lib.callback.register('peak_warehouse:client:searchBox', function()
139 | if lib.progressCircle({
140 | duration = 5000,
141 | label = locale('progress.searching_box'),
142 | position = 'bottom',
143 | useWhileDead = false,
144 | canCancel = true,
145 | disable = {
146 | move = true,
147 | combat = true,
148 | sprint = true,
149 | car = true,
150 | },
151 | anim = {
152 | dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
153 | clip = 'machinic_loop_mechandplayer'
154 | },
155 | }) then
156 | return true
157 | else
158 | return false
159 | end
160 | end)
161 |
162 | local function createObjects()
163 | for index = 1, #serverConfig.boxLocations do
164 | local coord = serverConfig.boxLocations[index].coords
165 | local model = config.warehouseObjects[math.random(1, #config.warehouseObjects)]
166 |
167 | lib.requestModel(model, 50000)
168 |
169 | local entity = CreateObject(model, coord.x, coord.y, coord.z, false, false, false)
170 | FreezeEntityPosition(entity, true)
171 |
172 | if config.interact == 'ox_target' then
173 | exports.ox_target:addLocalEntity(entity, {
174 | name = index,
175 | icon = 'fas fa-search',
176 | label = locale('interaction.search_box'),
177 | distance = 1,
178 | onSelect = function()
179 | TriggerServerEvent('peak_warehouse:server:searchBox', index)
180 | end
181 | })
182 | elseif config.interact == 'interact' then
183 | exports.interact:AddLocalEntityInteraction({
184 | entity = entity,
185 | id = index,
186 | distance = 3.0,
187 | interactDst = 1.5,
188 | options = {
189 | {
190 | label = locale('interaction.search_box'),
191 | action = function()
192 | TriggerServerEvent('peak_warehouse:server:searchBox', index)
193 | end,
194 | },
195 | }
196 | })
197 | elseif config.interact == 'sleepless_interact' then
198 | exports.sleepless_interact:addLocalEntity({
199 | id = index,
200 | entity = entity,
201 | options = {
202 | {
203 | label = locale('interaction.search_box'),
204 | icon = 'fas fa-search',
205 | onSelect = function()
206 | TriggerServerEvent('peak_warehouse:server:searchBox', index)
207 | end
208 | }
209 | },
210 | renderDistance = 3.0,
211 | activeDistance = 1.5,
212 | })
213 |
214 | spawnedEntities.props[#spawnedEntities.props + 1] = entity
215 | end
216 | end
217 | end
218 |
219 |
220 | local function removeObjects()
221 | for _, entity in ipairs(spawnedEntities.props) do
222 | if entity and DoesEntityExist(entity) then
223 | DeleteEntity(entity)
224 | end
225 | end
226 |
227 | spawnedEntities.props = {}
228 | end
229 |
230 | local function createZone()
231 | lib.points.new({
232 | coords = serverConfig.interactLocations.interior.coords,
233 | distance = 100,
234 | onEnter = createObjects,
235 | onExit = removeObjects
236 | })
237 | end
238 |
239 | RegisterNetEvent('peak_warehouse:client:setupInteractions', function()
240 | if GetInvokingResource() then return end
241 |
242 | createZone()
243 | end)
244 |
245 | CreateThread(function()
246 | if config.interact == 'ox_target' then
247 |
248 | local interactions = {
249 | {
250 | coords = serverConfig.interactLocations.entrance.coords,
251 | distance = 1,
252 | size = vec3(0.5, 1.2, 1.2),
253 | rotation = 85,
254 | debug = config.debugPoly,
255 | options = {
256 | {
257 | name = 'enter_warehouse',
258 | icon = 'fas fa-door-open',
259 | label = locale('interaction.enter_warehouse'),
260 | onSelect = function()
261 | TriggerServerEvent('peak_warehouse:server:enterWarehouse')
262 | end
263 | }
264 | }
265 | },
266 | {
267 | coords = serverConfig.interactLocations.exit.coords,
268 | distance = 1,
269 | size = vec3(1, 1.2, 1.2),
270 | rotation = 0,
271 | debug = config.debugPoly,
272 | options = {
273 | {
274 | name = 'exit_warehouse',
275 | icon = 'fas fa-door-open',
276 | label = locale('interaction.exit_warehouse'),
277 | onSelect = function()
278 | TriggerServerEvent('peak_warehouse:server:exitWarehouse', 'front')
279 | end
280 | }
281 | }
282 | },
283 | {
284 | coords = serverConfig.interactLocations.backExit.coords,
285 | distance = 1,
286 | size = vec3(1, 0.8, 0.8),
287 | rotation = 0,
288 | debug = config.debugPoly,
289 | options = {
290 | {
291 | name = 'exit_warehouse_back',
292 | icon = 'fas fa-door-open',
293 | label = locale('interaction.exit_warehouse'),
294 | onSelect = function()
295 | TriggerServerEvent('peak_warehouse:server:exitWarehouse', 'back')
296 | end
297 | }
298 | }
299 | },
300 | {
301 | coords = serverConfig.interactLocations.electricalBox.coords,
302 | size = vec3(0.4, 1, 1),
303 | rotation = 85,
304 | debug = config.debugPoly,
305 | options = {
306 | {
307 | name = 'place_c4',
308 | icon = 'fa-solid fa-bomb',
309 | label = locale('interaction.place_c4'),
310 | onSelect = function()
311 | TriggerServerEvent('peak_warehouse:server:startRobbery')
312 | end
313 | }
314 | }
315 | },
316 | {
317 | name = 'laptop',
318 | coords = serverConfig.interactLocations.laptop.coords,
319 | size = vec3(0.4, 0.4, 0.4),
320 | rotation = 85,
321 | debug = config.debugPoly,
322 | options = {
323 | {
324 | name = 'hack_laptop',
325 | icon = 'fa-solid fa-laptop',
326 | label = locale('interaction.hack_laptop'),
327 | onSelect = function()
328 | TriggerServerEvent('peak_warehouse:server:hackLaptop')
329 | end
330 | }
331 | }
332 | },
333 | {
334 | coords = serverConfig.interactLocations.policeReset.coords,
335 | size = vec3(0.4, 0.1, 0.4),
336 | rotation = 90,
337 | debug = config.debugPoly,
338 | options = {
339 | {
340 | groups = 'police',
341 | name = 'police_reset',
342 | icon = 'fa-solid fa-lock',
343 | label = locale('interaction.lock_gates'),
344 | onSelect = function()
345 | TriggerServerEvent('peak_warehouse:server:policeReset')
346 | end
347 | }
348 | }
349 | }
350 | }
351 |
352 | for _, interaction in ipairs(interactions) do
353 | exports.ox_target:addBoxZone(interaction)
354 | end
355 |
356 | elseif config.interact == 'interact' then
357 |
358 | local interactions = {
359 | {
360 | coords = serverConfig.interactLocations.entrance.coords,
361 | distance = 10,
362 | interactDst = 1.0,
363 | options = {
364 | {
365 | icon = 'fas fa-door-open',
366 | label = locale('interaction.enter_warehouse'),
367 | action = function()
368 | TriggerServerEvent('peak_warehouse:server:enterWarehouse')
369 | end
370 | }
371 | }
372 | },
373 | {
374 | coords = serverConfig.interactLocations.exit.coords,
375 | distance = 10,
376 | interactDst = 1.0,
377 | options = {
378 | {
379 | name = 'exit_warehouse',
380 | label = locale('interaction.exit_warehouse'),
381 | action = function()
382 | TriggerServerEvent('peak_warehouse:server:exitWarehouse', 'front')
383 | end
384 | }
385 | }
386 | },
387 | {
388 | coords = serverConfig.interactLocations.backExit.coords,
389 | distance = 10,
390 | interactDst = 1.0,
391 | options = {
392 | {
393 | name = 'exit_warehouse_back',
394 | label = locale('interaction.exit_warehouse'),
395 | action = function()
396 | TriggerServerEvent('peak_warehouse:server:exitWarehouse', 'back')
397 | end
398 | }
399 | }
400 | },
401 | {
402 | coords = serverConfig.interactLocations.electricalBox.coords,
403 | distance = 10,
404 | interactDst = 1.0,
405 | options = {
406 | {
407 | name = 'place_c4',
408 | label = locale('interaction.place_c4'),
409 | action = function()
410 | TriggerServerEvent('peak_warehouse:server:startRobbery')
411 | end
412 | }
413 | }
414 | },
415 | {
416 | coords = serverConfig.interactLocations.laptop.coords,
417 | id = 'laptop',
418 | distance = 10,
419 | interactDst = 1.0,
420 | options = {
421 | {
422 | name = 'hack_laptop',
423 | label = locale('interaction.hack_laptop'),
424 | action = function()
425 | TriggerServerEvent('peak_warehouse:server:hackLaptop')
426 | end
427 | }
428 | }
429 | },
430 | {
431 | coords = serverConfig.interactLocations.policeReset.coords,
432 | distance = 10,
433 | interactDst = 1.0,
434 | groups = {
435 | ['police'] = 0
436 | },
437 | options = {
438 | {
439 | label = locale('interaction.lock_gates'),
440 | action = function()
441 | TriggerServerEvent('peak_warehouse:server:policeReset')
442 | end
443 | }
444 | }
445 | }
446 | }
447 |
448 | for _, interaction in ipairs(interactions) do
449 | exports.interact:AddInteraction(interaction)
450 | end
451 |
452 | elseif config.interact == 'sleepless_interact' then
453 |
454 | exports.sleepless_interact:addCoords({
455 | id = 'warehouse_enter',
456 | coords = serverConfig.interactLocations.entrance.coords,
457 | options = {
458 | {
459 | icon = 'fas fa-door-open',
460 | label = locale('interaction.enter_warehouse'),
461 | onSelect = function()
462 | TriggerServerEvent('peak_warehouse:server:enterWarehouse')
463 | end,
464 | }
465 | },
466 | renderDistance = 10.0,
467 | activeDistance = 2.0,
468 | cooldown = 0
469 | })
470 |
471 | local interactions = {
472 | {
473 | id = 'warehouse_enter',
474 | coords = serverConfig.interactLocations.entrance.coords,
475 | options = {
476 | {
477 | icon = 'fas fa-door-open',
478 | label = locale('interaction.enter_warehouse'),
479 | onSelect = function()
480 | TriggerServerEvent('peak_warehouse:server:enterWarehouse')
481 | end
482 | }
483 | },
484 | renderDistance = 10.0,
485 | activeDistance = 2.0,
486 | cooldown = 0
487 | },
488 | {
489 | id = 'warehouse_exit',
490 | coords = serverConfig.interactLocations.exit.coords,
491 | options = {
492 | {
493 | name = 'exit_warehouse',
494 | label = locale('interaction.exit_warehouse'),
495 | onSelect = function()
496 | TriggerServerEvent('peak_warehouse:server:exitWarehouse', 'front')
497 | end
498 | }
499 | },
500 | renderDistance = 10.0,
501 | activeDistance = 2.0,
502 | cooldown = 0
503 | },
504 | {
505 | id = 'warehouse_back_exit',
506 | coords = serverConfig.interactLocations.backExit.coords,
507 | options = {
508 | {
509 | name = 'exit_warehouse_back',
510 | label = locale('interaction.exit_warehouse'),
511 | onSelect = function()
512 | TriggerServerEvent('peak_warehouse:server:exitWarehouse', 'back')
513 | end
514 | }
515 | },
516 | renderDistance = 10.0,
517 | activeDistance = 2.0,
518 | cooldown = 0
519 | },
520 | {
521 | id = 'electrical_box',
522 | coords = serverConfig.interactLocations.electricalBox.coords,
523 | options = {
524 | {
525 | name = 'place_c4',
526 | label = locale('interaction.place_c4'),
527 | onSelect = function()
528 | TriggerServerEvent('peak_warehouse:server:startRobbery')
529 | end
530 | }
531 | },
532 | renderDistance = 10.0,
533 | activeDistance = 2.0,
534 | cooldown = 0
535 | },
536 | {
537 | id = 'laptop',
538 | coords = serverConfig.interactLocations.laptop.coords,
539 | options = {
540 | {
541 | name = 'hack_laptop',
542 | label = locale('interaction.hack_laptop'),
543 | onSelect = function()
544 | TriggerServerEvent('peak_warehouse:server:hackLaptop')
545 | end
546 | }
547 | },
548 | renderDistance = 10.0,
549 | activeDistance = 2.0,
550 | cooldown = 0
551 | },
552 | {
553 | coords = serverConfig.interactLocations.policeReset.coords,
554 | options = {
555 | {
556 | groups = { ['police'] = 0 },
557 | label = locale('interaction.lock_gates'),
558 | onSelect = function()
559 | TriggerServerEvent('peak_warehouse:server:policeReset')
560 | end
561 | }
562 | },
563 | renderDistance = 10.0,
564 | activeDistance = 2.0,
565 | cooldown = 0
566 | }
567 | }
568 |
569 | for _, interaction in ipairs(interactions) do
570 | exports.sleepless_interact:addCoords(interaction)
571 | end
572 |
573 | end
574 | end)
575 |
576 |
577 |
578 |
579 |
580 |
--------------------------------------------------------------------------------
/config/client.lua:
--------------------------------------------------------------------------------
1 | return {
2 | interact = 'ox_target', -- ox_target, interact, sleepless_interact
3 |
4 | minCooldown = 30, -- In minutes
5 | maxCooldown = 60, -- In minutes
6 |
7 | debugPoly = false,
8 |
9 | blip = {
10 | enabled = true,
11 | location = vec3(839.0181, -1923.1303, 30.8329),
12 | sprite = 473,
13 | display = 4,
14 | scale = 0.6,
15 | colour = 18,
16 | label = 'Warehouse',
17 | },
18 |
19 | warehouseObjects = {
20 | `prop_boxpile_05a`,
21 | `prop_boxpile_04a`,
22 | `prop_boxpile_06b`,
23 | `prop_boxpile_02c`,
24 | `prop_boxpile_02b`,
25 | `prop_boxpile_01a`,
26 | `prop_boxpile_08a`,
27 | },
28 |
29 | guardConfig = {
30 | spawnGuards = true,
31 | accuracy = 100,
32 | armor = 200,
33 | health = 200,
34 | sufferHeadshots = false,
35 | alertness = 2,
36 | aggresiveness = 2,
37 |
38 | },
39 |
40 | guardList = {
41 | {
42 | model = `s_m_m_security_01`,
43 | coords = vec4(998.8849, -3111.4160, -38.9999, 90),
44 | weapon = `WEAPON_CARBINERIFLE`,
45 | },
46 | {
47 | model = `s_m_m_security_01`,
48 | coords = vec4(999.9012, -3099.2932, -38.9999, 270),
49 | weapon = `WEAPON_CARBINERIFLE`,
50 | },
51 | {
52 | model = `s_m_m_security_01`,
53 | coords = vec4(1001.6360, -3092.2385, -38.999, 90),
54 | weapon = `WEAPON_CARBINERIFLE`,
55 | },
56 | {
57 | model = `s_m_m_security_01`,
58 | coords = vec4(993.8922, -3099.9907, -38.9958, 90),
59 | weapon = `WEAPON_CARBINERIFLE`,
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/config/server.lua:
--------------------------------------------------------------------------------
1 | return {
2 | requiredItem = {
3 | name = 'thermite',
4 | amount = 1,
5 | remove = {
6 | onFail = true,
7 | onUse = true,
8 | }
9 | },
10 |
11 | lootMin = 1, -- Minimum amount of different items player can get
12 | lootMax = 6, -- Maximum amount of different items player can get
13 |
14 | items = {
15 | { name = 'metalscrap', chance = 20, min = 2, max = 6 },
16 | { name = 'plastic', chance = 50, min = 5, max = 10 },
17 | { name = 'copper', chance = 100, min = 5, max = 20 },
18 | { name = 'ammo-9', chance = 30, min = 20, max = 50 },
19 | { name = 'ammo-rifle', chance = 10, min = 10, max = 30 },
20 | { name = 'WEAPON_APPISTOL', chance = 15, min = 1, max = 1 },
21 | { name = 'WEAPON_SMG', chance = 10, min = 1, max = 1 },
22 | { name = 'WEAPON_ASSAULTRIFLE', chance = 3, min = 1, max = 1 },
23 | },
24 |
25 | requiredCops = 0,
26 |
27 | interactLocations = {
28 | interior = { coords = vec3(1027.5515, -3101.7664, -38.9999) },
29 | policeReset = { coords = vec3(850.1256, -1926.7499, 30.3147) },
30 | laptop = { coords = vec3(995.2347, -3100.0031, -39.1758), isBusy = false, isHacked = false },
31 | electricalBox = { coords = vec3(835.8817, -1923.2244, 30.7248), isBusy = false, isExploded = false },
32 | entrance = { coords = vec3(839.0181, -1923.5303, 30.8329), isOpen = false },
33 | backEntrance = { coords = vec3(853.9672, -1852.6887, 29.7877), isOpen = false, },
34 | exit = { coords = vec3(1028.0412, -3101.4567, -38.1793), isOpen = false },
35 | backExit = { coords = vec3(992.0262, -3097.8188, -38.8184), isOpen = false },
36 | },
37 |
38 | boxLocations = {
39 | [1] = { coords = vec3(1018.1041, -3108.5, -40), isBusy = false, isRobbed = false },
40 | [2] = { coords = vec3(1015.5176, -3108.5, -40), isBusy = false, isRobbed = false },
41 | [3] = { coords = vec3(1013.2429, -3108.5, -40), isBusy = false, isRobbed = false },
42 | [4] = { coords = vec3(1010.8519, -3108.5, -40), isBusy = false, isRobbed = false },
43 | [5] = { coords = vec3(1008.5016, -3108.5, -40), isBusy = false, isRobbed = false },
44 | [6] = { coords = vec3(1006.1173, -3108.5, -40), isBusy = false, isRobbed = false },
45 | [7] = { coords = vec3(1003.4443, -3108.5, -40), isBusy = false, isRobbed = false },
46 | [8] = { coords = vec3(1018.1041, -3102.7, -40), isBusy = false, isRobbed = false },
47 | [9] = { coords = vec3(1015.5176, -3102.7, -40), isBusy = false, isRobbed = false },
48 | [10] = { coords = vec3(1013.2429, -3102.7, -40), isBusy = false, isRobbed = false },
49 | [11] = { coords = vec3(1010.8519, -3102.7, -40), isBusy = false, isRobbed = false },
50 | [12] = { coords = vec3(1008.5016, -3102.7, -40), isBusy = false, isRobbed = false },
51 | [13] = { coords = vec3(1006.1173, -3102.7, -40), isBusy = false, isRobbed = false },
52 | [14] = { coords = vec3(1003.4443, -3102.7, -40), isBusy = false, isRobbed = false },
53 | [15] = { coords = vec3(1018.1041, -3097, -40), isBusy = false, isRobbed = false },
54 | [16] = { coords = vec3(1015.5176, -3097, -40), isBusy = false, isRobbed = false },
55 | [17] = { coords = vec3(1013.2429, -3097, -40), isBusy = false, isRobbed = false },
56 | [18] = { coords = vec3(1010.8519, -3097, -40), isBusy = false, isRobbed = false },
57 | [19] = { coords = vec3(1008.5016, -3097, -40), isBusy = false, isRobbed = false },
58 | [20] = { coords = vec3(1006.1173, -3097, -40), isBusy = false, isRobbed = false },
59 | [21] = { coords = vec3(1003.4443, -3097, -40), isBusy = false, isRobbed = false },
60 | },
61 |
62 | logging = {
63 | enabled = false,
64 | system = 'ox_lib', -- ox_lib logger or discord (not recommended)
65 |
66 | -- Only used for discord logging
67 | webhookURL = ''
68 | },
69 | }
70 |
--------------------------------------------------------------------------------
/fxmanifest.lua:
--------------------------------------------------------------------------------
1 | fx_version 'cerulean'
2 | game 'gta5'
3 |
4 | author 'Peak Scripts'
5 | description 'Advanced Warehouse Robbery for QBX, QB, OX, ESX, ND frameworks'
6 | version '2.0.0'
7 |
8 | lua54 'yes'
9 |
10 | ox_lib 'locale'
11 |
12 | shared_scripts {
13 | '@ox_lib/init.lua',
14 | 'shared/*.lua'
15 | }
16 |
17 | server_scripts {
18 | 'server/*.lua'
19 | }
20 |
21 | client_scripts {
22 | 'client/*.lua'
23 | }
24 |
25 | files {
26 | 'config/*.lua',
27 | 'locales/*.json',
28 | 'modules/**/*.lua'
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/locales/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "notify": {
3 | "cooldown": "Abklingzeit aktiv...",
4 | "not_enough_police": "Nicht genügend Polizisten online...",
5 | "already_blown": "Der Stromkasten ist bereits gesprengt...",
6 | "dont_have_item": "Dir fehlt ein Gegenstand...",
7 | "failed": "Du hast es nicht geschafft, das System zu hacken...",
8 | "item_removed": "Der Gegenstand wurde entfernt...",
9 | "c4_placed": "C4 wurde platziert, geh zurück!",
10 | "gate_unlocked": "Das Tor wurde erfolgreich entriegelt",
11 | "entrance_closed": "Der Eingang ist verriegelt...",
12 | "not_hacked": "Das System ist nicht gehackt, was machst du hier, Junge?",
13 | "successfully_searched": "Du hast die Kiste erfolgreich durchsucht!",
14 | "already_searched": "Diese Kiste wurde bereits durchsucht...",
15 | "exit_locked": "Der Ausgang ist verschlossen...",
16 | "successfully_locked": "Du hast den Eingang erfolgreich verriegelt",
17 | "successfully_hacked": "Du hast den Laptop erfolgreich gehackt",
18 | "failed_hack": "Du hast es nicht geschafft, den Laptop zu hacken...",
19 | "already_hacked": "Laptop wurde bereits gehackt..."
20 | },
21 | "progress": {
22 | "searching_box": "Durchsuche die Kiste...",
23 | "placing_c4": "Platziere das C4..."
24 | },
25 | "interaction": {
26 | "place_c4": "C4 platzieren",
27 | "search_box": "Kiste durchsuchen",
28 | "exit_warehouse": "Verlasse das Lagerhaus",
29 | "enter_warehouse": "Betrete das Lagerhaus",
30 | "hack_laptop": "Laptop hacken",
31 | "lock_gates": "Tore verriegeln"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "notify": {
3 | "cooldown": "Cooldown is active...",
4 | "not_enough_police": "Not enough police are online...",
5 | "already_blown": "Electric box is already blown up...",
6 | "dont_have_item": "You do not have the required item...",
7 | "failed": "You failed to hack the system...",
8 | "item_removed": "The item was removed...",
9 | "c4_placed": "C4 has been placed, back off!",
10 | "gate_unlocked": "The gate has been successfully unlocked",
11 | "entrance_closed": "The entrance is closed...",
12 | "not_hacked": "The system is not hacked, what are you doing here boy?",
13 | "successfully_searched": "You have successfully searched the box!",
14 | "already_searched": "This box has already been searched...",
15 | "exit_locked": "The exit is locked...",
16 | "successfully_locked": "You have successfully locked the entrance",
17 | "successfully_hacked": "You have successfully hacked the laptop",
18 | "failed_hack": "You have failed to hack the laptop...",
19 | "already_hacked": "Laptop has already been hacked...",
20 | "already_doing": "Somebody is already doing this..."
21 | },
22 | "progress": {
23 | "searching_box": "Searching the box...",
24 | "placing_c4": "Placing the C4..."
25 | },
26 | "interaction": {
27 | "place_c4": "Place the C4",
28 | "search_box": "Search the box",
29 | "exit_warehouse": "Exit the warehouse",
30 | "enter_warehouse": "Enter the warehouse",
31 | "hack_laptop": "Hack the laptop",
32 | "lock_gates": "Lock the gates"
33 | },
34 | "exploit": {
35 | "exploiting_server": "Exploiting the server (aka unauthorized use of Lua executors)"
36 | }
37 | }
--------------------------------------------------------------------------------
/modules/bridge/esx/server.lua:
--------------------------------------------------------------------------------
1 | local bridge = {}
2 |
3 | local ESX = exports['es_extended']:getSharedObject()
4 |
5 | --- @param source integer
6 | function bridge.getPlayer(source)
7 | return ESX.GetPlayerFromId(source)
8 | end
9 |
10 | function bridge.checkCopCount()
11 | local amount = 0
12 | local players = ESX.GetExtendedPlayers()
13 | for i = 1, #players do
14 | local xPlayer = players[i]
15 | if xPlayer.job.name == 'police' then
16 | amount += 1
17 | end
18 | end
19 | return amount
20 | end
21 |
22 | --- @param source integer
23 | function bridge.hasPoliceJob(source)
24 | local playerData = ESX.GetPlayerData(source)
25 | local playerJob = playerData.job.name
26 |
27 | if playerJob == 'police' then
28 | return true
29 | end
30 |
31 | return nil
32 | end
33 |
34 | RegisterNetEvent('esx:playerLoaded', function()
35 | OnPlayerLoaded()
36 | end)
37 |
38 | return bridge
--------------------------------------------------------------------------------
/modules/bridge/nd/server.lua:
--------------------------------------------------------------------------------
1 | local bridge = {}
2 |
3 | --- @param source integer
4 | function bridge.getPlayer(source)
5 | return exports.ND_Core:getPlayer(source)
6 | end
7 |
8 | function bridge.checkCopCount()
9 | local amount = 0
10 | local players = exports.NDCore:getPlayers()
11 | local policeDepartments = {'sahp', 'lspd', 'bcso'}
12 |
13 | for _, player in pairs(players) do
14 | for index = 1, #policeDepartments do
15 | if player.groups[policeDepartments[index]] then
16 | amount += 1
17 | end
18 | end
19 | end
20 |
21 | return amount
22 | end
23 |
24 | function bridge.hasPoliceJob(source)
25 | local player = exports.ND_Core:getPlayer(source)
26 | local playerJob = player.getData('job')
27 |
28 | if playerJob == 'police' then
29 | return true
30 | end
31 |
32 | return nil
33 | end
34 |
35 | AddEventHandler('ND:characterLoaded', function()
36 | OnPlayerUnload()
37 | end)
38 |
39 | return bridge
--------------------------------------------------------------------------------
/modules/bridge/ox/server.lua:
--------------------------------------------------------------------------------
1 | local Ox = require '@ox_core.lib.init'
2 | local bridge = {}
3 |
4 | --- @param source integer
5 | function bridge.getPlayer(source)
6 | return Ox.GetPlayer(source)
7 | end
8 |
9 | function bridge.checkCopCount()
10 | local amount = 0
11 | local players = Ox.GetPlayers({ groups = { ['police'] = 1 } })
12 | for _, player in pairs(players) do
13 | amount += 1
14 | end
15 |
16 | return amount
17 | end
18 |
19 | --- @param source integer
20 | function bridge.hasPoliceJob(source)
21 | local player = Ox.GetPlayer(source)
22 | local groups = player.getGroups()
23 |
24 | for _, group in pairs(groups) do
25 | if group == 'police' then
26 | return true
27 | end
28 | end
29 |
30 | return nil
31 | end
32 |
33 | AddEventHandler('ox:playerLoaded', function()
34 | OnPlayerLoaded()
35 | end)
36 |
37 | return bridge
--------------------------------------------------------------------------------
/modules/bridge/qb/server.lua:
--------------------------------------------------------------------------------
1 |
2 | local bridge = {}
3 | local QBCore = exports['qb-core']:GetCoreObject()
4 |
5 | --- @param source integer
6 | function bridge.getPlayer(source)
7 | return QBCore.Functions.GetPlayer(source)
8 | end
9 |
10 | function bridge.checkCopCount()
11 | local amount = 0
12 | local players = QBCore.Functions.GetQBPlayers()
13 |
14 | for _, player in pairs(players) do
15 | if player.PlayerData.job.type == 'leo' and player.PlayerData.job.onduty then
16 | amount += 1
17 | end
18 | end
19 | return amount
20 | end
21 |
22 | --- @param source integer
23 | function bridge.hasPoliceJob(source)
24 | local player = QBCore.Functions.GetPlayer(source)
25 |
26 | if player.PlayerData.job.name == 'police' then
27 | return true
28 | end
29 |
30 | return nil
31 | end
32 |
33 | RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
34 | OnPlayerLoaded()
35 | end)
36 |
37 | return bridge
--------------------------------------------------------------------------------
/modules/bridge/qbx/server.lua:
--------------------------------------------------------------------------------
1 | local bridge = {}
2 |
3 | --- @param source integer
4 | function bridge.getPlayer(source)
5 | return exports.qbx_core:GetPlayer(source)
6 | end
7 |
8 | function bridge.checkCopCount()
9 | return exports.qbx_core:GetDutyCountType('leo')
10 | end
11 |
12 | --- @param source integer
13 | function bridge.hasPoliceJob(source)
14 | local hasJob = exports.qbx_core:HasPrimaryGroup(source, 'police')
15 | if not hasJob then return false end
16 |
17 | return true
18 | end
19 |
20 | RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
21 | OnPlayerLoaded()
22 | end)
23 |
24 | return bridge
--------------------------------------------------------------------------------
/modules/utils/client.lua:
--------------------------------------------------------------------------------
1 | local utils = {}
2 |
3 | --- @param message string
4 | --- @param type string
5 | function utils.notify(message, type)
6 | lib.notify({
7 | description = message,
8 | type = type
9 | })
10 | end
11 |
12 | function utils.thermiteMinigame()
13 | local success = exports.bl_ui:MineSweeper(3, {
14 | grid = 4,
15 | duration = 10000,
16 | target = 3,
17 | previewDuration = 1000
18 | })
19 |
20 | return success
21 | end
22 |
23 | function utils.laptopMinigame()
24 | local success = exports.bl_ui:PathFind(1, {
25 | numberOfNodes = 10,
26 | duration = 10000,
27 | })
28 |
29 | return success
30 | end
31 |
32 |
33 | return utils
--------------------------------------------------------------------------------
/modules/utils/server.lua:
--------------------------------------------------------------------------------
1 | local utils = {}
2 | local serverConfig = require 'config.server'
3 |
4 | --- @param source integer
5 | --- @param message string
6 | --- @param type string
7 | function utils.notify(source, message, type)
8 | lib.notify(source, {
9 | description = message,
10 | type = type
11 | })
12 | end
13 |
14 | local function getAmount(item)
15 | return item.amount or (item.min and item.max and math.random(item.min, item.max)) or 1
16 | end
17 |
18 | --- @param items table
19 | --- @param minLoot number
20 | --- @param maxLoot number
21 | function utils.generateLoot(items, minLoot, maxLoot)
22 | if not items or #items == 0 then
23 | lib.print.error('Invalid loot configuration: no items provided')
24 | return {}
25 | end
26 |
27 | local loot = {}
28 | local lootAmount = 0
29 | local attempts = 0
30 | local maxAttempts = 100
31 |
32 | while lootAmount < minLoot and attempts < maxAttempts do
33 | for i = 1, #items do
34 | if lootAmount >= maxLoot then
35 | break
36 | end
37 |
38 | local item = items[i]
39 | local chance = math.random(100)
40 |
41 | if chance <= item.chance then
42 | local amount = getAmount(item)
43 |
44 | if amount and amount > 0 then
45 | if not loot[item.name] then
46 | lootAmount = lootAmount + 1
47 | loot[item.name] = {
48 | amount = amount,
49 | metadata = item.metadata or nil
50 | }
51 | else
52 | loot[item.name].amount = math.min(loot[item.name].amount + amount, item.max)
53 | end
54 |
55 | if lootAmount >= maxLoot then
56 | break
57 | end
58 | end
59 | end
60 | end
61 | attempts = attempts + 1
62 | end
63 |
64 | if lootAmount < minLoot then
65 | return utils.generateLoot(items, minLoot, maxLoot)
66 | end
67 |
68 | return loot
69 | end
70 |
71 | --- @param source integer
72 | --- @param location vector3
73 | --- @param maxDistance number
74 | function utils.checkDistance(source, location, maxDistance)
75 | if not (type(location) == 'vector3' or type(location) == 'vector4') then
76 | return false
77 | end
78 |
79 | local locationCoords = vec3(location.x, location.y, location.z)
80 | local ped = GetPlayerPed(source)
81 | local playerPos = GetEntityCoords(ped)
82 |
83 | local distance = #(playerPos - locationCoords)
84 | return distance < maxDistance
85 | end
86 |
87 | function utils.handleExploit(source)
88 | -- If this is triggered, 99% of the time the player is cheating or doing something weird. Take precautions and investigate the player.
89 | DropPlayer(source, locale('exploit.exploiting_server'))
90 | utils.logPlayer(source, { locale('exploit.exploiting_server') })
91 | end
92 |
93 | --- @param source integer
94 | --- @param data table
95 | function utils.logPlayer(source, data)
96 | if serverConfig.logging.system == 'ox_lib' then
97 | lib.logger(source, 'House Robbery', json.encode(data))
98 | elseif serverConfig.logging.system == 'discord' then
99 | local playerName = GetPlayerName(source)
100 | local identifiers = GetPlayerIdentifiers(source)
101 | local playerIdentifier = identifiers[1] or 'Unknown'
102 | local playerDiscordId, playerSteamId, playerLicense, playerLicense2 = 'Unknown', 'Unknown', 'Unknown', 'Unknown'
103 |
104 | for _, id in ipairs(identifiers) do
105 | if string.match(id, 'discord:') then
106 | playerDiscordId = string.gsub(id, 'discord:', '')
107 | elseif string.match(id, 'steam:') then
108 | playerSteamId = string.gsub(id, 'steam:', '')
109 | elseif string.match(id, 'license:') then
110 | playerLicense = string.gsub(id, 'license:', '')
111 | elseif string.match(id, 'license2:') then
112 | playerLicense2 = string.gsub(id, 'license2:', '')
113 | end
114 | end
115 |
116 | local logMessage = string.format(
117 | "%s\n\n**Player identifiers:**\n" ..
118 | "•**player:** %s\n" ..
119 | "•**identifier:** %s\n" ..
120 | "•**discord:** %s\n" ..
121 | "•**steam:** %s\n" ..
122 | "•**license:** %s\n" ..
123 | "•**license2:** %s",
124 | data.message or 'No message provided',
125 | playerName, playerIdentifier, playerDiscordId, playerSteamId, playerLicense, playerLicense2
126 | )
127 |
128 | local payload = {
129 | username = serverConfig.discordLogs.name,
130 | avatar_url = serverConfig.discordLogs.image,
131 | content = data.normalMessage or '',
132 | embeds = {}
133 | }
134 |
135 | if data.title then
136 | table.insert(payload.embeds, {
137 | color = data.color,
138 | title = "**" .. data.title .. "**",
139 | description = "**Message: **\n" .. logMessage,
140 | footer = {
141 | text = os.date("%a %b %d, %I:%M%p"),
142 | icon_url = serverConfig.discordLogs.footer
143 | }
144 | })
145 | end
146 |
147 | if #payload.embeds == 0 then
148 | payload.embeds = nil
149 | end
150 |
151 | PerformHttpRequest(data.link, function(err, text, headers) end, 'POST', json.encode(payload), { ['Content-Type'] = 'application/json' })
152 | end
153 | end
154 |
155 | return utils
--------------------------------------------------------------------------------
/server/main.lua:
--------------------------------------------------------------------------------
1 | lib.versionCheck('Peak-Scripts/peak_warehouse')
2 |
3 | local globalState = GlobalState
4 | local spawnedEntities = {
5 | guards = {},
6 | }
7 | local config = require 'config.client'
8 | local serverConfig = require 'config.server'
9 | local utils = require 'modules.utils.server'
10 | globalState.cooldown = false
11 | globalState.robberyStarted = false
12 |
13 | function OnPlayerLoaded()
14 | if not globalState.robberyStarted then return end
15 |
16 | TriggerClientEvent('peak_warehouse:client:setupInteractions', source)
17 | end
18 |
19 | local function resetAllStates()
20 | for key, location in pairs(serverConfig.interactLocations) do
21 | if location.isBusy ~= nil then
22 | location.isBusy = false
23 | end
24 | if location.isHacked ~= nil then
25 | location.isHacked = false
26 | end
27 | if location.isExploded ~= nil then
28 | location.isExploded = false
29 | end
30 | if location.isOpen ~= nil then
31 | location.isOpen = false
32 | end
33 | end
34 |
35 | for _, box in pairs(serverConfig.boxLocations) do
36 | if box.isBusy ~= nil then
37 | box.isBusy = false
38 | end
39 | if box.isRobbed ~= nil then
40 | box.isRobbed = false
41 | end
42 | end
43 | end
44 |
45 | local function cleanupEntities()
46 | for _, netId in ipairs(spawnedEntities.guards) do
47 | local ped = NetworkGetEntityFromNetworkId(netId)
48 | if DoesEntityExist(ped) then
49 | DeleteEntity(ped)
50 | end
51 | end
52 |
53 | spawnedEntities.guards = {}
54 | end
55 |
56 | local function setCooldown()
57 | globalState.cooldown = true
58 | local cooldown = math.random(config.minCooldown, config.maxCooldown) * 60000
59 |
60 | SetTimeout(cooldown, function()
61 | globalState.cooldown = false
62 | globalState.robberyStarted = false
63 | resetAllStates()
64 | cleanupEntities()
65 | end)
66 | end
67 |
68 |
69 | AddEventHandler('onResourceStop', function(resource)
70 | if resource ~= GetCurrentResourceName() then return end
71 | cleanupEntities()
72 | end)
73 |
74 | local function spawnGuards()
75 | for _, guard in pairs(config.guardList) do
76 |
77 | local ped = CreatePed(4, guard.model, guard.coords.x, guard.coords.y, guard.coords.z, guard.heading, true, false)
78 | GiveWeaponToPed(ped, guard.weapon, 255, false, true)
79 |
80 | while not DoesEntityExist(ped) do Wait(25) end
81 |
82 | local netId = NetworkGetNetworkIdFromEntity(ped)
83 | if not netId then return end
84 |
85 | Entity(ped).state:set('peak_warehouse:pedHandler', true, true)
86 | spawnedEntities.guards[#spawnedEntities.guards + 1] = netId
87 | end
88 | end
89 |
90 | RegisterNetEvent('peak_warehouse:server:startRobbery', function()
91 | local player = bridge.getPlayer(source)
92 | if not player then return end
93 |
94 | local distance = utils.checkDistance(source, serverConfig.interactLocations.electricalBox.coords, 5)
95 | if not distance then
96 | utils.handleExploit(source)
97 | return
98 | end
99 |
100 | if serverConfig.interactLocations.electricalBox.isExploded then
101 | utils.notify(source, locale('notify.already_blown'), 'error')
102 | return
103 | end
104 |
105 | if globalState.cooldown then
106 | utils.notify(source, locale('notify.cooldown'), 'error')
107 | return
108 | end
109 |
110 | local copCount = bridge.checkCopCount()
111 | if copCount < serverConfig.requiredCops then
112 | utils.notify(source, locale('notify.not_enough_police'), 'error')
113 | return
114 | end
115 |
116 | local itemCount = exports.ox_inventory:Search(source, 'count', serverConfig.requiredItem.name)
117 | if itemCount < serverConfig.requiredItem.amount then
118 | utils.notify(source, locale('notify.dont_have_item'), 'error')
119 | return
120 | end
121 |
122 | globalState.robberyStarted = true
123 |
124 | serverConfig.interactLocations.electricalBox.isBusy = true
125 |
126 | local success = lib.callback.await('peak_warehouse:client:startRobbery', source)
127 | if not success then
128 | serverConfig.interactLocations.electricalBox.isBusy = false
129 |
130 | utils.notify(source, locale('notify.failed'), 'error')
131 |
132 | if serverConfig.requiredItem.remove.onFail then
133 | exports.ox_inventory:RemoveItem(source, serverConfig.requiredItem.name, serverConfig.requiredItem.amount)
134 | end
135 |
136 | return
137 | end
138 |
139 | serverConfig.interactLocations.electricalBox.isBusy = false
140 | serverConfig.interactLocations.electricalBox.isExploded = true
141 |
142 | exports.ox_inventory:RemoveItem(source, serverConfig.requiredItem.name, serverConfig.requiredItem.amount)
143 |
144 | local exploded = lib.callback.await('peak_warehouse:client:createC4', source)
145 | if not exploded then return end
146 |
147 | serverConfig.interactLocations.entrance.isOpen = true
148 | utils.notify(source, locale('notify.gate_unlocked'), 'success')
149 |
150 | if config.guardConfig.spawnGuards then
151 | spawnGuards()
152 | end
153 |
154 | TriggerClientEvent('peak_warehouse:client:setupInteractions', -1)
155 | end)
156 |
157 | RegisterNetEvent('peak_warehouse:server:enterWarehouse', function()
158 |
159 | local distance = utils.checkDistance(source, serverConfig.interactLocations.entrance.coords, 5)
160 | if not distance then
161 | utils.handleExploit(source)
162 | return
163 | end
164 |
165 | if not globalState.robberyStarted then
166 | utils.notify(source, locale('notify.entrance_closed'), 'error')
167 | return
168 | end
169 |
170 | TriggerClientEvent('peak_warehouse:client:enterWarehouse', source)
171 | end)
172 |
173 | RegisterNetEvent('peak_warehouse:server:exitWarehouse', function(type)
174 | if not globalState.robberyStarted then
175 | utils.notify(source, locale('notify.entrance_closed'), 'error')
176 | return
177 | end
178 |
179 | local exitLocation = serverConfig.interactLocations[type == 'front' and 'exit' or 'backExit']
180 | local distance = utils.checkDistance(source, exitLocation.coords, 5)
181 |
182 | if not distance then
183 | utils.handleExploit(source)
184 | return
185 | end
186 |
187 | if not exitLocation.isOpen then
188 | utils.notify(source, locale('notify.exit_locked'), 'error')
189 | return
190 | end
191 |
192 | TriggerClientEvent('peak_warehouse:client:exitWarehouse', source, type)
193 | end)
194 |
195 | RegisterNetEvent('peak_warehouse:server:hackLaptop', function()
196 | local source = source
197 |
198 | if serverConfig.interactLocations.laptop.isHacked then
199 | utils.notify(source, locale('notify.already_hacked'), 'error')
200 | return
201 | end
202 |
203 | local distance = utils.checkDistance(source, serverConfig.interactLocations.laptop.coords, 5)
204 | if not distance then
205 | utils.handleExploit(source)
206 | return
207 | end
208 |
209 | serverConfig.interactLocations.laptop.isBusy = true
210 |
211 | local success = lib.callback.await('peak_warehouse:client:hackLaptop', source)
212 | if not success then
213 | serverConfig.interactLocations.laptop.isBusy = false
214 |
215 | utils.notify(source, locale('notify.failed_hack'), 'error')
216 | return
217 | end
218 |
219 | serverConfig.interactLocations.laptop.isBusy = false
220 |
221 | utils.notify(source, locale('notify.successfully_hacked'), 'success')
222 |
223 | serverConfig.interactLocations.laptop.isHacked = true
224 | serverConfig.interactLocations.exit.isOpen = true
225 | serverConfig.interactLocations.backExit.isOpen = true
226 | setCooldown()
227 | end)
228 |
229 | RegisterNetEvent('peak_warehouse:server:policeReset', function()
230 | local distance = utils.checkDistance(source, serverConfig.interactLocations.policeReset.coords, 5)
231 | if not distance then
232 | utils.handleExploit(source)
233 | return
234 | end
235 |
236 | local playerJob = bridge.hasPoliceJob(source)
237 | if not playerJob then
238 | utils.notify(source, locale('notify.must_be_police'), 'error')
239 | return
240 | end
241 |
242 | if not serverConfig.interactLocations.entrance.isOpen then
243 | utils.notify(source, locale('notify.exit_locked'), 'error')
244 | return
245 | end
246 |
247 | resetAllStates()
248 | utils.notify(source, locale('notify.successfully_locked'), 'success')
249 | end)
250 |
251 | RegisterNetEvent('peak_warehouse:server:searchBox', function(index)
252 | local boxConfig = serverConfig.boxLocations[index]
253 |
254 | local distance = utils.checkDistance(source, boxConfig.coords, 5)
255 | if not distance then
256 | utils.handleExploit(source)
257 | return
258 | end
259 |
260 | if boxConfig.isBusy then
261 | utils.notify(source, locale('notify.already_doing'), 'error')
262 | return
263 | end
264 |
265 | if boxConfig.isRobbed then
266 | utils.notify(source, locale('notify.already_searched'), 'error')
267 | return
268 | end
269 |
270 | if not globalState.robberyStarted then
271 | utils.handleExploit(source)
272 | return
273 | end
274 |
275 | boxConfig.isBusy = true
276 |
277 | local success = lib.callback.await('peak_warehouse:client:searchBox', source)
278 | if not success then
279 | boxConfig.isBusy = false
280 | return
281 | end
282 |
283 | boxConfig.isBusy = false
284 | boxConfig.isRobbed = true
285 |
286 | utils.notify(source, locale('notify.successfully_searched'), 'success')
287 |
288 | local items = utils.generateLoot(serverConfig.items, serverConfig.lootMin, serverConfig.lootMax)
289 |
290 | for item, itemData in pairs(items) do
291 | exports.ox_inventory:AddItem(source, item, itemData.amount, itemData.metadata)
292 | end
293 | end)
--------------------------------------------------------------------------------
/shared/main.lua:
--------------------------------------------------------------------------------
1 | local bridgeResources = {
2 | ['es_extended'] = 'esx',
3 | ['ND_Core'] = 'nd',
4 | ['ox_core'] = 'ox',
5 | ['qbx_core'] = 'qbx',
6 | ['qb-core'] = 'qb',
7 | }
8 |
9 | local function getBridge()
10 | for resource, framework in pairs(bridgeResources) do
11 | if GetResourceState(resource):find('start') then
12 | return ('modules.bridge.%s.%s'):format(framework, lib.context)
13 | end
14 | end
15 | end
16 |
17 | bridge = require(getBridge())
--------------------------------------------------------------------------------