2025-07-20 02:41:44 +02:00
|
|
|
local QBCore = exports['qb-core']:GetCoreObject()
|
2025-07-20 17:07:46 +02:00
|
|
|
local zoneCooldowns = {}
|
2025-07-20 02:41:44 +02:00
|
|
|
local playerCooldowns = {}
|
|
|
|
local globalCooldowns = {}
|
|
|
|
|
|
|
|
-- Debug function
|
|
|
|
local function Debug(msg)
|
|
|
|
if Config.Debug then
|
|
|
|
print("[Container Heist] " .. msg)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-07-20 17:07:46 +02:00
|
|
|
-- Register usable item
|
|
|
|
QBCore.Functions.CreateUseableItem(Config.RequiredItems.flex.name, function(source)
|
|
|
|
TriggerClientEvent('container_heist:client:useFlexItem', source)
|
|
|
|
end)
|
|
|
|
|
2025-07-20 02:41:44 +02:00
|
|
|
-- Check if player has required items
|
2025-07-20 04:10:11 +02:00
|
|
|
QBCore.Functions.CreateCallback('container_heist:server:checkRequiredItems', function(source, cb)
|
2025-07-20 02:41:44 +02:00
|
|
|
local src = source
|
|
|
|
local Player = QBCore.Functions.GetPlayer(src)
|
2025-07-20 04:10:11 +02:00
|
|
|
if not Player then return cb(false) end
|
2025-07-20 02:41:44 +02:00
|
|
|
|
|
|
|
-- Check for required flex tool
|
|
|
|
local hasItem = exports['tgiann-inventory']:HasItem(src, Config.RequiredItems.flex.name, Config.RequiredItems.flex.amount)
|
2025-07-20 04:10:11 +02:00
|
|
|
cb(hasItem)
|
2025-07-20 02:41:44 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
-- Check cooldowns
|
2025-07-20 17:07:46 +02:00
|
|
|
QBCore.Functions.CreateCallback('container_heist:server:checkCooldown', function(source, cb, zoneId)
|
2025-07-20 02:41:44 +02:00
|
|
|
local src = source
|
|
|
|
local Player = QBCore.Functions.GetPlayer(src)
|
2025-07-20 04:10:11 +02:00
|
|
|
if not Player then return cb({success = false, message = "Player not found"}) end
|
2025-07-20 02:41:44 +02:00
|
|
|
|
|
|
|
local citizenId = Player.PlayerData.citizenid
|
|
|
|
local currentTime = os.time()
|
|
|
|
|
|
|
|
-- Check player cooldown
|
|
|
|
if playerCooldowns[citizenId] and (currentTime - playerCooldowns[citizenId]) < (Config.CooldownTime * 60) then
|
|
|
|
local timeLeft = math.ceil(((playerCooldowns[citizenId] + (Config.CooldownTime * 60)) - currentTime) / 60)
|
2025-07-20 04:10:11 +02:00
|
|
|
return cb({success = false, message = "You need to wait " .. timeLeft .. " more minutes before attempting another heist!"})
|
2025-07-20 02:41:44 +02:00
|
|
|
end
|
|
|
|
|
2025-07-20 17:07:46 +02:00
|
|
|
-- Check zone cooldown
|
|
|
|
if zoneCooldowns[zoneId] and (currentTime - zoneCooldowns[zoneId]) < (Config.CooldownTime * 60) then
|
2025-07-20 04:10:11 +02:00
|
|
|
return cb({success = false, message = Config.Notifications.alreadyRobbed})
|
2025-07-20 02:41:44 +02:00
|
|
|
end
|
|
|
|
|
2025-07-20 17:07:46 +02:00
|
|
|
-- Find zone type
|
|
|
|
local zoneType = nil
|
|
|
|
for _, zone in pairs(Config.ContainerZones) do
|
|
|
|
if zone.id == zoneId then
|
|
|
|
zoneType = zone.type
|
2025-07-20 03:54:06 +02:00
|
|
|
break
|
2025-07-20 02:41:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-07-20 17:07:46 +02:00
|
|
|
if not zoneType then
|
|
|
|
return cb({success = false, message = "Invalid zone!"})
|
2025-07-20 03:54:06 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Check global cooldown for container type
|
2025-07-20 17:07:46 +02:00
|
|
|
if globalCooldowns[zoneType] and (currentTime - globalCooldowns[zoneType]) < (Config.GlobalCooldown * 60) then
|
|
|
|
local timeLeft = math.ceil(((globalCooldowns[zoneType] + (Config.GlobalCooldown * 60)) - currentTime) / 60)
|
2025-07-20 04:10:11 +02:00
|
|
|
return cb({success = false, message = Config.Notifications.globalCooldown .. " (" .. timeLeft .. " minutes left)"})
|
2025-07-20 03:54:06 +02:00
|
|
|
end
|
|
|
|
|
2025-07-20 04:10:11 +02:00
|
|
|
cb({success = true})
|
2025-07-20 02:41:44 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
-- Get police count
|
2025-07-20 04:10:11 +02:00
|
|
|
QBCore.Functions.CreateCallback('container_heist:server:getPoliceCount', function(source, cb)
|
2025-07-20 02:41:44 +02:00
|
|
|
local policeCount = 0
|
|
|
|
local players = QBCore.Functions.GetPlayers()
|
|
|
|
|
|
|
|
for _, playerId in ipairs(players) do
|
|
|
|
local Player = QBCore.Functions.GetPlayer(playerId)
|
2025-07-20 03:18:58 +02:00
|
|
|
if Player then
|
|
|
|
-- Check if player's job is in the list of police jobs
|
|
|
|
for _, jobName in ipairs(Config.PoliceJobs) do
|
|
|
|
if Player.PlayerData.job.name == jobName and Player.PlayerData.job.onduty then
|
|
|
|
policeCount = policeCount + 1
|
|
|
|
break -- No need to check other job names for this player
|
|
|
|
end
|
|
|
|
end
|
2025-07-20 02:41:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-07-20 04:10:11 +02:00
|
|
|
cb(policeCount)
|
2025-07-20 02:41:44 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
-- Alert police
|
2025-07-20 03:54:06 +02:00
|
|
|
RegisterNetEvent('container_heist:server:alertPolice', function(coords, streetName, containerLabel)
|
2025-07-20 02:41:44 +02:00
|
|
|
local src = source
|
|
|
|
local players = QBCore.Functions.GetPlayers()
|
|
|
|
|
|
|
|
for _, playerId in ipairs(players) do
|
|
|
|
local Player = QBCore.Functions.GetPlayer(playerId)
|
2025-07-20 03:18:58 +02:00
|
|
|
if Player then
|
|
|
|
-- Check if player's job is in the list of police jobs
|
|
|
|
for _, jobName in ipairs(Config.PoliceJobs) do
|
|
|
|
if Player.PlayerData.job.name == jobName and Player.PlayerData.job.onduty then
|
2025-07-20 03:54:06 +02:00
|
|
|
TriggerClientEvent('container_heist:client:policeAlert', playerId, coords, streetName, containerLabel)
|
2025-07-20 03:18:58 +02:00
|
|
|
break -- No need to send multiple alerts to the same player
|
|
|
|
end
|
|
|
|
end
|
2025-07-20 02:41:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Finish robbery and give rewards
|
2025-07-20 17:07:46 +02:00
|
|
|
RegisterNetEvent('container_heist:server:finishRobbery', function(zoneId, containerTypeName)
|
2025-07-20 02:41:44 +02:00
|
|
|
local src = source
|
|
|
|
local Player = QBCore.Functions.GetPlayer(src)
|
|
|
|
if not Player then return end
|
|
|
|
|
|
|
|
local citizenId = Player.PlayerData.citizenid
|
|
|
|
local currentTime = os.time()
|
|
|
|
|
|
|
|
-- Set cooldowns
|
|
|
|
playerCooldowns[citizenId] = currentTime
|
2025-07-20 17:07:46 +02:00
|
|
|
zoneCooldowns[zoneId] = currentTime
|
2025-07-20 03:54:06 +02:00
|
|
|
globalCooldowns[containerTypeName] = currentTime
|
|
|
|
|
|
|
|
-- Get container type
|
|
|
|
local containerType = Config.ContainerTypes[containerTypeName]
|
|
|
|
if not containerType then
|
|
|
|
Debug("Container type not found: " .. containerTypeName)
|
|
|
|
return
|
|
|
|
end
|
2025-07-20 02:41:44 +02:00
|
|
|
|
|
|
|
-- Decrease durability of flex tool if configured
|
|
|
|
if Config.RequiredItems.flex.durability then
|
|
|
|
local itemData = exports['tgiann-inventory']:GetItemByName(src, Config.RequiredItems.flex.name)
|
2025-07-20 03:18:58 +02:00
|
|
|
if itemData and itemData.info then
|
|
|
|
local newDurability = math.max(0, (itemData.info.durabilityPercent or 100) - Config.RequiredItems.flex.durabilityDecrease)
|
2025-07-20 02:41:44 +02:00
|
|
|
exports['tgiann-inventory']:UpdateItemMetadata(src, Config.RequiredItems.flex.name, itemData.slot, {
|
|
|
|
durabilityPercent = newDurability,
|
2025-07-20 03:18:58 +02:00
|
|
|
serie = itemData.info.serie or "TOOL-" .. math.random(100000, 999999),
|
2025-07-20 02:41:44 +02:00
|
|
|
usedTotalAmmo = itemData.info.usedTotalAmmo or 0,
|
|
|
|
ammo = itemData.info.ammo or 0
|
|
|
|
})
|
|
|
|
|
|
|
|
-- Remove item if durability reaches 0
|
|
|
|
if newDurability <= 0 and Config.RequiredItems.flex.remove then
|
|
|
|
exports['tgiann-inventory']:RemoveItem(src, Config.RequiredItems.flex.name, 1)
|
|
|
|
TriggerClientEvent('QBCore:Notify', src, "Your " .. Config.RequiredItems.flex.label .. " broke!", "error")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif Config.RequiredItems.flex.remove then
|
|
|
|
-- Remove item if configured
|
|
|
|
exports['tgiann-inventory']:RemoveItem(src, Config.RequiredItems.flex.name, Config.RequiredItems.flex.amount)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Give rewards based on chances
|
|
|
|
local rewardsGiven = 0
|
2025-07-20 03:54:06 +02:00
|
|
|
for _, reward in pairs(containerType.rewards) do
|
2025-07-20 02:41:44 +02:00
|
|
|
if math.random(1, 100) <= reward.chance then
|
|
|
|
local amount = math.random(reward.min, reward.max)
|
|
|
|
|
|
|
|
if reward.item == "cash" then
|
|
|
|
Player.Functions.AddMoney("cash", amount)
|
|
|
|
TriggerClientEvent('QBCore:Notify', src, "Found $" .. amount, "success")
|
|
|
|
else
|
|
|
|
-- Add item with proper metadata for weapons
|
|
|
|
if string.match(reward.item, "weapon_") then
|
|
|
|
exports['tgiann-inventory']:AddItem(src, reward.item, amount, nil, {
|
|
|
|
serie = "HEIST-" .. math.random(100000, 999999),
|
|
|
|
durabilityPercent = 100,
|
|
|
|
usedTotalAmmo = 0,
|
|
|
|
ammo = 0
|
|
|
|
})
|
|
|
|
else
|
|
|
|
exports['tgiann-inventory']:AddItem(src, reward.item, amount)
|
|
|
|
end
|
|
|
|
|
|
|
|
TriggerClientEvent('QBCore:Notify', src, "Found " .. amount .. "x " .. reward.label, "success")
|
|
|
|
end
|
|
|
|
|
|
|
|
rewardsGiven = rewardsGiven + 1
|
|
|
|
end
|
2025-07-20 03:42:24 +02:00
|
|
|
end
|
2025-07-20 02:41:44 +02:00
|
|
|
|
|
|
|
if rewardsGiven == 0 then
|
|
|
|
TriggerClientEvent('QBCore:Notify', src, "The container was empty!", "error")
|
|
|
|
end
|
2025-07-20 03:26:36 +02:00
|
|
|
end)
|
|
|
|
|
2025-07-20 02:41:44 +02:00
|
|
|
-- Clean up cooldowns periodically
|
|
|
|
CreateThread(function()
|
|
|
|
while true do
|
|
|
|
Wait(60000) -- Check every minute
|
|
|
|
local currentTime = os.time()
|
|
|
|
|
|
|
|
-- Clean up player cooldowns
|
|
|
|
for citizenId, cooldownTime in pairs(playerCooldowns) do
|
|
|
|
if (currentTime - cooldownTime) > (Config.CooldownTime * 60) then
|
|
|
|
playerCooldowns[citizenId] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-07-20 17:07:46 +02:00
|
|
|
-- Clean up zone cooldowns
|
|
|
|
for zoneId, cooldownTime in pairs(zoneCooldowns) do
|
2025-07-20 02:41:44 +02:00
|
|
|
if (currentTime - cooldownTime) > (Config.CooldownTime * 60) then
|
2025-07-20 17:07:46 +02:00
|
|
|
zoneCooldowns[zoneId] = nil
|
2025-07-20 02:41:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Clean up global cooldowns
|
|
|
|
for containerType, cooldownTime in pairs(globalCooldowns) do
|
|
|
|
if (currentTime - cooldownTime) > (Config.GlobalCooldown * 60) then
|
|
|
|
globalCooldowns[containerType] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|