This commit is contained in:
EVO 2025-06-24 23:13:17 +02:00
parent d58e87474f
commit a474caf290
17 changed files with 2643 additions and 0 deletions

View file

@ -0,0 +1,245 @@
-- Initialize config(s)
local sh_config = require 'config.shared'

-- Display a notification
--- @param message string
--- @param type string
function ShowNotification(message, type)
if sh_config.setup.notify == 'ox_lib' then
lib.notify({ description = message, type = type, position = 'top', icon = 'fas fa-store' })
elseif sh_config.setup.notify == 'esx' then
ESX.ShowNotification(message)
elseif sh_config.setup.notify == 'qb' then
QBCore.Functions.Notify(message, type)
elseif sh_config.setup.notify == 'okok' then
exports['okokNotify']:Alert('Convenience Store', message, 5000, type, false)
elseif sh_config.setup.notify == 'sd-notify' then
exports['sd-notify']:Notify('Convenience Store', message, type)
elseif sh_config.setup.notify == 'wasabi_notify' then
exports.wasabi_notify:notify('Convenience Store', message, 5000, type, false, 'fas fa-store')
elseif sh_config.setup.notify == 'custom' then
-- Add custom notification export/event here
end
end

-- Display a notification from server
--- @param message string
--- @param type string
RegisterNetEvent('lation_247robbery:Notify', function(message, type)
ShowNotification(message, type)
end)

-- Display a minigame
--- @param data table
function Minigame(data)
if lib.skillCheck(data.difficulty, data.inputs) then
return true
end
return false
end

-- Display a progress bar
--- @param data table
function ProgressBar(data)
if sh_config.setup.progress == 'ox_lib' then
-- Want to use ox_lib's progress circle instead of bar?
-- Change "progressBar" to "progressCircle" below & done!
if lib.progressBar({
label = data.label,
duration = data.duration,
position = data.position or 'bottom',
useWhileDead = data.useWhileDead,
canCancel = data.canCancel,
disable = data.disable,
anim = {
dict = data.anim.dict or nil,
clip = data.anim.clip or nil,
flag = data.anim.flag or nil
},
prop = {
model = data.prop.model or nil,
bone = data.prop.bone or nil,
pos = data.prop.pos or nil,
rot = data.prop.rot or nil
}
}) then
return true
end
return false
elseif sh_config.setup.progress == 'qbcore' then
local p = promise.new()
QBCore.Functions.Progressbar(data.label, data.label, data.duration, data.useWhileDead, data.canCancel, {
disableMovement = data.disable.move,
disableCarMovement = data.disable.car,
disableMouse = false,
disableCombat = data.disable.combat
}, {
animDict = data.anim.dict or nil,
anim = data.anim.clip or nil,
flags = data.anim.flag or nil
}, {
model = data.prop.model or nil,
bone = data.prop.bone or nil,
coords = data.prop.pos or nil,
rotation = data.prop.rot or nil
}, {},
function()
p:resolve(true)
end,
function()
p:resolve(false)
end)
return Citizen.Await(p)
else
-- Add 'custom' progress bar here
end
end

-- Send police dispatch message
--- @param data table data.coords, data.street
function PoliceDispatch(data)
if not data then print('^1[ERROR]: Failed to retrieve dispatch data, cannot proceed^0') return end
if sh_config.police.dispatch == 'cd_dispatch' then
local playerData = exports['cd_dispatch']:GetPlayerInfo()
if not playerData then
print('^1[ERROR]: cd_dispatch failed to return playerData, cannot proceed^0')
return
end
TriggerServerEvent('cd_dispatch:AddNotification', {
job_table = sh_config.police.jobs,
coords = playerData.coords,
title = '10-88 - Store Robbery',
message = 'An alarm has been triggered at 24/7 on ' ..playerData.street,
flash = 0,
unique_id = playerData.unique_id,
sound = 1,
blip = {
sprite = 52,
scale = 1.0,
colour = 1,
flashes = false,
text = '10-88 - Store Robbery',
time = 5,
radius = 0,
}
})
elseif sh_config.police.dispatch == 'ps-dispatch' then
local alert = {
coords = data.coords,
message = 'An alarm has been triggered at 24/7 on ' ..data.street,
dispatchCode = '10-88',
description = 'Store Robbery',
radius = 0,
sprite = 52,
color = 1,
scale = 1.0,
length = 3
}
exports["ps-dispatch"]:CustomAlert(alert)
elseif sh_config.police.dispatch == 'qs-dispatch' then
local playerData = exports['qs-dispatch']:GetPlayerInfo()
if not playerData then
print('^1[ERROR]: qs-dispatch failed to return playerData, cannot proceed^0')
return
end
exports['qs-dispatch']:getSSURL(function(image)
TriggerServerEvent('qs-dispatch:server:CreateDispatchCall', {
job = sh_config.police.jobs,
callLocation = playerData.coords,
callCode = { code = '10-88', snippet = 'Store Robbery' },
message = 'An alarm has been triggered at 24/7 on ' ..playerData.street_1,
flashes = false,
image = image or nil,
blip = {
sprite = 52,
scale = 1.0,
colour = 1,
flashes = false,
text = '10-88 - Store Robbery',
time = (30 * 1000),
}
})
end)
elseif sh_config.police.dispatch == 'core_dispatch' then
local gender = IsPedMale(cache.ped) and 'male' or 'female'
TriggerServerEvent('core_dispatch:addCall', '10-88', 'Potential Store Robbery',
{{icon = 'fa-venus-mars', info = gender}},
{data.coords.x, data.coords.y, data.coords.z},
'police', 30000, 52, 1, false)
elseif sh_config.police.dispatch == 'rcore_dispatch' then
local playerData = exports['rcore_dispatch']:GetPlayerData()
if not playerData then
print('^1[ERROR]: rcore_dispatch failed to return playerData, cannot proceed^0')
return
end
local alert = {
code = '10-88 - Store Robbery',
default_priority = 'low',
coords = playerData.coords,
job = sh_config.police.jobs,
text = 'An alarm has been triggered at 24/7 on ' ..playerData.street_1,
type = 'alerts',
blip_time = 30,
blip = {
sprite = 52,
colour = 1,
scale = 1.0,
text = '10-88 - Store Robbery',
flashes = false,
radius = 0,
}
}
TriggerServerEvent('rcore_dispatch:server:sendAlert', alert)
elseif sh_config.police.dispatch == 'aty_dispatch' then
TriggerEvent('aty_dispatch:SendDispatch', 'Potential Store Robbery', '10-88', 52, sh_config.police.jobs)
elseif sh_config.police.dispatch == 'op-dispatch' then
local job = 'police'
local text = 'An alarm has been triggered at 24/7 on ' ..data.street
local coords = data.coords
local id = cache.serverId
local title = '10-88 - Store Robbery'
local panic = false
TriggerServerEvent('Opto_dispatch:Server:SendAlert', job, title, text, coords, panic, id)
elseif sh_config.police.dispatch == 'origen_police' then
local alert = {
coords = data.coords,
title = '10-88 - Store Robbery',
type = 'GENERAL',
message = 'An alarm has been triggered at 24/7 on ' ..data.street,
job = 'police',
}
TriggerServerEvent("SendAlert:police", alert)
elseif sh_config.police.dispatch == 'emergencydispatch' then
TriggerServerEvent('emergencydispatch:emergencycall:new', 'police', '10-88 | Potential Store Robbery', data.coords, true)
elseif sh_config.police.dispatch == 'custom' then
-- Add your custom dispatch system here
else
print('^1[ERROR]: No dispatch system was detected - please visit config/shared.lua "police" section^0')
end
end

-- Add circle target zones
--- @param data table
function AddCircleZone(data)
if sh_config.setup.interact == 'ox_target' then
exports.ox_target:addSphereZone(data)
elseif sh_config.setup.interact == 'qb-target' then
exports['qb-target']:AddCircleZone(data.name, data.coords, data.radius, {
name = data.name,
debugPoly = sh_config.setup.debug}, {
options = data.options,
distance = 2,
})
elseif sh_config.setup.interact == 'interact' then
exports.interact:AddInteraction({
coords = data.coords,
interactDst = 2.0,
id = data.name,
options = data.options
})
elseif sh_config.setup.interact == 'custom' then
-- Add support for a custom target system here
else
print('^1[ERROR]: No interaction system was detected - please visit config/shared "setup" section^0')
end
end

View file

@ -0,0 +1,290 @@
-- Initialize config(s)
local sh_config = require 'config.shared'
local cl_config = require 'config.client'
local icons = require 'config.icons'

-- Initialize variables to track active locations
local activeRegister, activeComputer, activeSafe = false, false, false

-- Initialize variables to store code & pin
local safePin

-- Initialize variables to track failed attempts
local wrongPIN, failedHack = 0, 0

-- Initialize table to build questionnaire if applicable
local questionData = {}

-- Build the input dialog for questionnaire if applicable
if sh_config.computers.questionnaire then
for _, question in ipairs(sh_config.questionnaire.questions) do
questionData[#questionData + 1] = {
type = question.type,
label = question.label,
description = question.description,
icon = question.icon,
required = question.required
}
if question.type == 'select' then
questionData[#questionData].options = question.options
end
end
end

-- Used to check if the answers submitted are correct
--- @param answers table
local function AreAnswersCorrect(answers)
for question, answer in ipairs(sh_config.questionnaire.answers) do
local submitted_answer = answers[question]
if sh_config.questionnaire.questions[question].type == 'select' then
if tonumber(submitted_answer) ~= answer then
return false
end
else
if string.lower(submitted_answer) ~= string.lower(answer) then
return false
end
end
end
return true
end

-- Function to start the initial robbery process
local function InitiateRegisterRobbery()
local canStart = lib.callback.await('lation_247robbery:StartRobbery', false)
if not canStart then activeRegister = false return end
local dict, anim = cl_config.anims.lockpick.dict, cl_config.anims.lockpick.clip
lib.requestAnimDict(dict)
while not HasAnimDictLoaded(dict) do Wait(0) end
TaskPlayAnim(cache.ped, dict, anim, 8.0, 8.0, -1, 51, 1.0, false, false, false)
local skillcheck = Minigame(sh_config.registers.minigame)
ClearPedTasks(cache.ped)
if not skillcheck then
TriggerServerEvent('lation_247robbery:DoesLockpickBreak')
activeRegister = false
return
end
local coords = GetEntityCoords(cache.ped)
local data = {
coords = coords,
street = GetStreetNameFromHashKey(GetStreetNameAtCoord(coords.x, coords.y, coords.z))
}
PoliceDispatch(data)
if ProgressBar(cl_config.anims.register) then
local codeChance = math.random(100)
if codeChance <= sh_config.registers.noteChance then
local generatedCode = math.random(1111, 9999)
if safePin then safePin = nil end
safePin = generatedCode
local note = lib.alertDialog({
header = locale('alerts.note.header'),
content = locale('alerts.note.content', safePin),
centered = true,
cancel = false,
})
if note == 'confirm' then
activeSafe = true
TriggerServerEvent('lation_247robbery:CompleteRegisterRobbery')
end
else
activeComputer = true
TriggerServerEvent('lation_247robbery:CompleteRegisterRobbery')
end
else
activeRegister = false
ShowNotification(locale('notify.cancel-rob'), 'error')
end
end

-- Function to handle hacking the computer if required
local function InitiateComputerHack()
activeComputer = false -- Deactive target
if failedHack >= sh_config.computers.maxAttempts then
activeRegister = false
activeComputer = false
failedHack = 0
ShowNotification(locale('notify.failed-limit'), 'error')
TriggerServerEvent('lation_247robbery:FailedRobbery')
return
end
local dict, anim = cl_config.anims.hackPC.dict, cl_config.anims.hackPC.clip
lib.requestAnimDict(dict)
while not HasAnimDictLoaded(dict) do Wait(0) end
TaskPlayAnim(cache.ped, dict, anim, 8.0, 8.0, -1, 1, 1, false, false, false)
if sh_config.computers.questionnaire then
local questions = lib.inputDialog(locale('inputs.questions.header'), questionData)
if not questions then
activeComputer = true
ClearPedTasks(cache.ped)
return
end
if AreAnswersCorrect(questions) then
failedHack = 0
ClearPedTasks(cache.ped)
local generatedCode = math.random(1111, 9999)
if safePin then safePin = nil end
safePin = generatedCode
lib.alertDialog({
header = locale('alerts.hack.header'),
content = locale('alerts.hack.content', safePin),
centered = true,
cancel = false
})
activeSafe = true
else
ClearPedTasks(cache.ped)
activeComputer = true
failedHack = failedHack + 1
ShowNotification(locale('notify.failed-hack'), 'error')
end
else
local skillcheck = Minigame(sh_config.computers.minigame)
if not skillcheck then
ClearPedTasks(cache.ped)
activeComputer = true
failedHack = failedHack + 1
ShowNotification(locale('notify.failed-hack'), 'error')
return
end
failedHack = 0
ClearPedTasks(cache.ped)
local generatedCode = math.random(1111, 9999)
if safePin then safePin = nil end
safePin = generatedCode
lib.alertDialog({
header = locale('alerts.hack.header'),
content = locale('alerts.hack.content', safePin),
centered = true,
cancel = false
})
activeSafe = true
end
end

-- Function to handle the safe robbery
local function InitiateSafeRobbery()
activeSafe = false
if wrongPIN >= sh_config.safes.maxAttempts then
activeRegister = false
activeSafe = false
wrongPIN = 0
ShowNotification(locale('notify.failed-limit'), 'error')
TriggerServerEvent('lation_247robbery:FailedRobbery')
return
end
local inputCode = lib.inputDialog(locale('inputs.safe.header'), {
{
type = 'input',
label = locale('inputs.safe.label'),
description = locale('inputs.safe.desc'),
placeholder = locale('inputs.safe.placeholder'),
icon = icons.safe_pin,
required = true
}
})
if not inputCode then activeSafe = true return end
local convertedCode = tonumber(inputCode[1])
if convertedCode ~= safePin then
wrongPIN = wrongPIN + 1
activeSafe = true
ShowNotification(locale('notify.wrong-pin'), 'error')
elseif convertedCode == safePin then
activeSafe = false
wrongPIN = 0
if ProgressBar(cl_config.anims.safe) then
activeRegister = false
TriggerServerEvent('lation_247robbery:CompleteSafeRobbery')
safePin = nil
else
activeSafe = true
end
end
end

-- Create interactions
AddEventHandler('lation_247robbery:onPlayerLoaded', function()
local gabz = GetResourceState('cfx-gabz-247') == 'started'
local fm = GetResourceState('cfx-fm-supermarkets') == 'started'
local coords = gabz and require 'data.gabz' or fm and require 'data.fmshop' or require 'data.default'
for key, coord in pairs(coords.registers) do
local data = {
name = 'cash_register' ..key,
coords = coord,
radius = 0.35,
debug = sh_config.setup.debug,
options = {
{
label = locale('target.register'),
icon = icons.register,
iconColor = icons.register_color,
canInteract = function()
if not activeRegister then
return true
end
return false
end,
action = function()
activeRegister = true
InitiateRegisterRobbery()
end,
onSelect = function()
activeRegister = true
InitiateRegisterRobbery()
end,
distance = 2
}
}
}
AddCircleZone(data)
end
for key, coord in pairs(coords.computers) do
local data = {
name = 'computer' ..key,
coords = coord,
radius = 0.35,
debug = sh_config.setup.debug,
options = {
{
label = locale('target.computer'),
icon = icons.computer,
iconColor = icons.computer_color,
canInteract = function()
if activeComputer then
return true
end
return false
end,
action = InitiateComputerHack,
onSelect = InitiateComputerHack,
distance = 2
}
}
}
AddCircleZone(data)
end
for key, coord in pairs(coords.safes) do
local data = {
name = 'safe' ..key,
coords = coord,
radius = 0.45,
debug = sh_config.setup.debug,
options = {
{
label = locale('target.safe'),
icon = icons.safe,
iconColor = icons.safe_color,
canInteract = function()
if activeSafe then
return true
end
return false
end,
action = InitiateSafeRobbery,
onSelect = InitiateSafeRobbery,
distance = 2
}
}
}
AddCircleZone(data)
end
end)