├── .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()) --------------------------------------------------------------------------------