From 4676cbfc6d5cfe0e257f4e5f649021d849bfc47e Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sun, 20 Jul 2025 17:41:10 +0200 Subject: [PATCH] ed --- .../nordi_containerheist/client/main.lua | 678 ++++++++++++---- .../[crime]/nordi_containerheist/config.lua | 742 ++++++++++++++++-- .../nordi_containerheist/fxmanifest.lua | 8 +- .../nordi_containerheist/server/main.lua | 208 ++++- 4 files changed, 1386 insertions(+), 250 deletions(-) diff --git a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua index 6ae1c6983..ad164ca1d 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua @@ -1,7 +1,8 @@ local QBCore = exports['qb-core']:GetCoreObject() local isRobbing = false -local inZone = false -local currentZoneId = nil +local currentContainer = nil +local containerBlip = nil +local addedEntities = {} -- Debug function local function Debug(msg) @@ -10,167 +11,544 @@ local function Debug(msg) end end --- Function to check if a point is inside a polygon -local function IsPointInPolygon(point, polygon) - local x, y = point.x, point.y - local inside = false - local j = #polygon - - for i = 1, #polygon do - if (polygon[i].y > y) ~= (polygon[j].y > y) and - x < (polygon[j].x - polygon[i].x) * (y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x then - inside = not inside +-- Function to get model name from hash +local function GetModelNameFromHash(hash) + for _, containerType in pairs(Config.ContainerTypes) do + if GetHashKey(containerType.model) == hash then + return containerType.model end - j = i end - - return inside + return "Unknown" end --- Function to check if player is in any container zone -local function GetCurrentZone() +-- Function to check if player is near a valid container +local function IsNearValidContainer() local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) + local foundEntity = nil + local foundType = nil + local closestDistance = 999.0 - for _, zone in pairs(Config.ContainerZones) do - if IsPointInPolygon(playerCoords, zone.points) and - playerCoords.z >= zone.minZ and playerCoords.z <= zone.maxZ then - return zone - end - end - - return nil -end - --- Simple command to test if the script is working -RegisterCommand('containertest', function() - local playerCoords = GetEntityCoords(PlayerPedId()) - print("Player coords: " .. playerCoords.x .. ", " .. playerCoords.y .. ", " .. playerCoords.z) - - local zone = GetCurrentZone() - if zone then - QBCore.Functions.Notify("You are in zone: " .. zone.id, "success") - print("In zone: " .. zone.id) - else - QBCore.Functions.Notify("You are not in any container zone", "error") - print("Not in any zone") - end -end, false) - --- Command to start robbery (for testing) -RegisterCommand('robcontainer', function() - local zone = GetCurrentZone() - - if zone then - QBCore.Functions.Notify("Starting robbery in zone: " .. zone.id, "success") - TriggerServerEvent('container_heist:server:testRobbery', zone.id, zone.type) - else - QBCore.Functions.Notify("You are not in any container zone", "error") - end -end, false) - --- Register usable item event handler -RegisterNetEvent('container_heist:client:useFlexItem', function() - print("useFlexItem event triggered") - local zone = GetCurrentZone() - - if zone then - print("Player is in zone: " .. zone.id) - QBCore.Functions.Notify("Starting robbery with flex tool in zone: " .. zone.id, "success") - TriggerServerEvent('container_heist:server:testRobbery', zone.id, zone.type) - else - QBCore.Functions.Notify(Config.Notifications.notInZone, "error") - end -end) - --- Main thread for checking if player is in a zone -CreateThread(function() - while true do - local sleep = 1000 - local zone = GetCurrentZone() - - if zone then - if not inZone or currentZoneId ~= zone.id then - inZone = true - currentZoneId = zone.id - QBCore.Functions.Notify("You entered " .. zone.label .. ". Use your flex tool to break in.", "primary") - print("Entered zone: " .. zone.id) - end - sleep = 500 - else - if inZone then - inZone = false - currentZoneId = nil - print("Left zone") - end - end - - Wait(sleep) - end -end) - --- Debug thread for visualizing zones -CreateThread(function() - while true do - if Config.Debug then - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) + -- Check for containers in the area (objects) + local objects = GetGamePool('CObject') + for _, object in ipairs(objects) do + if DoesEntityExist(object) and not IsEntityDead(object) then + local model = GetEntityModel(object) + local objectCoords = GetEntityCoords(object) + local distance = #(playerCoords - objectCoords) - for _, zone in pairs(Config.ContainerZones) do - -- Draw lines connecting the points to visualize the zone - for i = 1, #zone.points do - local j = i % #zone.points + 1 - local point1 = zone.points[i] - local point2 = zone.points[j] - - -- Draw line at ground level - DrawLine( - point1.x, point1.y, zone.minZ, - point2.x, point2.y, zone.minZ, - 255, 0, 0, 255 - ) + if distance <= 5.0 and distance < closestDistance then + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + foundEntity = object + foundType = containerType + closestDistance = distance + break + end end - - -- Calculate center of zone for label - local centerX, centerY = 0, 0 - for _, point in ipairs(zone.points) do - centerX = centerX + point.x - centerY = centerY + point.y - end - centerX = centerX / #zone.points - centerY = centerY / #zone.points - - -- Draw zone label - local centerZ = (zone.minZ + zone.maxZ) / 2 - DrawTextOnCoord(centerX, centerY, centerZ, zone.id .. " (" .. zone.type .. ")") end - Wait(0) - else - Wait(1000) + end + end + + -- Check for trailers in the area (vehicles) + local vehicles = GetGamePool('CVehicle') + for _, vehicle in ipairs(vehicles) do + if DoesEntityExist(vehicle) and not IsEntityDead(vehicle) then + local model = GetEntityModel(vehicle) + local vehicleCoords = GetEntityCoords(vehicle) + local distance = #(playerCoords - vehicleCoords) + + if distance <= 5.0 and distance < closestDistance then + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + foundEntity = vehicle + foundType = containerType + closestDistance = distance + break + end + end + end + end + end + + return foundEntity, foundType +end + +-- Function to create a blip at the robbery location +local function CreateRobberyBlip(coords) + if containerBlip then + RemoveBlip(containerBlip) + end + + containerBlip = AddBlipForCoord(coords) + + -- Set blip color based on job + local playerJob = QBCore.Functions.GetPlayerData().job.name + if playerJob == "marshal" then + SetBlipColour(containerBlip, 38) -- Purple for Marshal + elseif playerJob == "sheriff" then + SetBlipColour(containerBlip, 16) -- Orange for Sheriff + else + SetBlipColour(containerBlip, Config.Blip.color) -- Default color for regular police + end + + SetBlipSprite(containerBlip, Config.Blip.sprite) + SetBlipScale(containerBlip, Config.Blip.scale) + SetBlipAsShortRange(containerBlip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString(Config.Blip.label) + EndTextCommandSetBlipName(containerBlip) + + if Config.Blip.flash then + SetBlipFlashes(containerBlip, true) + end + + -- Remove blip after duration + SetTimeout(Config.Blip.duration * 1000, function() + if containerBlip then + RemoveBlip(containerBlip) + containerBlip = nil + end + end) +end + +-- Function to play container robbery animation +local function PlayRobberyAnimation(containerType) + local playerPed = PlayerPedId() + local animDict = containerType.animation.dict + local animName = containerType.animation.name + + RequestAnimDict(animDict) + while not HasAnimDictLoaded(animDict) do + Wait(10) + end + + TaskPlayAnim(playerPed, animDict, animName, 8.0, -8.0, containerType.animation.duration, containerType.animation.flag, 0, false, false, false) + + -- Add particle effects for welding + local boneIndex = GetPedBoneIndex(playerPed, 28422) + local particleDict = "core" + local particleName = "ent_amb_welding" + + RequestNamedPtfxAsset(particleDict) + while not HasNamedPtfxAssetLoaded(particleDict) do + Wait(10) + end + + UseParticleFxAssetNextCall(particleDict) + local particleHandle = StartParticleFxLoopedOnPedBone(particleName, playerPed, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, boneIndex, 0.5, false, false, false) + + -- Clean up after animation + SetTimeout(containerType.animation.duration, function() + StopParticleFxLooped(particleHandle, 0) + StopAnimTask(playerPed, animDict, animName, 1.0) + end) +end + +-- Function to start container robbery +local function StartContainerRobbery(container, containerType) + if isRobbing then return end + + isRobbing = true + currentContainer = container + + -- Check if player has required tools + local hasTools = lib.callback.await('container_heist:server:checkRequiredItems', false) + if not hasTools then + lib.notify({ + title = Config.Notifications.title, + description = Config.Notifications.noTools, + type = 'error' + }) + isRobbing = false + currentContainer = nil + return + end + + -- Check cooldowns + local cooldownCheck = lib.callback.await('container_heist:server:checkCooldown', false, NetworkGetNetworkIdFromEntity(container)) + if not cooldownCheck.success then + lib.notify({ + title = Config.Notifications.title, + description = cooldownCheck.message, + type = 'error' + }) + isRobbing = false + currentContainer = nil + return + end + + -- Check police count + local policeCount = lib.callback.await('container_heist:server:getPoliceCount', false) + if policeCount < Config.PoliceRequired then + lib.notify({ + title = Config.Notifications.title, + description = Config.Notifications.notEnoughPolice, + type = 'error' + }) + isRobbing = false + currentContainer = nil + return + end + + -- Position player for animation + local containerCoords = GetEntityCoords(container) + local containerHeading = GetEntityHeading(container) + local offsetCoords = GetOffsetFromEntityInWorldCoords(container, containerType.offset.x, containerType.offset.y, containerType.offset.z) + + -- Set player position and heading + SetEntityCoords(PlayerPedId(), offsetCoords.x, offsetCoords.y, offsetCoords.z) + SetEntityHeading(PlayerPedId(), containerHeading + containerType.heading) + + -- Alert police if configured + if containerType.policeAlert then + local streetName = GetStreetNameFromHashKey(GetStreetNameAtCoord(containerCoords.x, containerCoords.y, containerCoords.z)) + TriggerServerEvent('container_heist:server:alertPolice', containerCoords, streetName, containerType.label) + end + + -- Start robbery progress bar + PlayRobberyAnimation(containerType) + + if lib.progressBar({ + duration = containerType.animation.duration, + label = 'Breaking into ' .. containerType.label, + useWhileDead = false, + canCancel = true, + disable = { + car = true, + move = true, + combat = true, + }, + anim = { + dict = containerType.animation.dict, + clip = containerType.animation.name, + }, + }) then + -- Success + TriggerServerEvent('container_heist:server:finishRobbery', NetworkGetNetworkIdFromEntity(container), containerType.type) + lib.notify({ + title = Config.Notifications.title, + description = Config.Notifications.success, + type = 'success' + }) + else + -- Cancelled + lib.notify({ + title = Config.Notifications.title, + description = Config.Notifications.failed, + type = 'error' + }) + end + + isRobbing = false + currentContainer = nil +end + +-- Function to scan and add all nearby containers to target system +local function ScanAndAddContainersToTarget() + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + local count = 0 + + -- Check for containers in the area (objects) + local objects = GetGamePool('CObject') + for _, object in ipairs(objects) do + if DoesEntityExist(object) and not IsEntityDead(object) then + local objectCoords = GetEntityCoords(object) + local distance = #(playerCoords - objectCoords) + + if distance <= 50.0 then + local model = GetEntityModel(object) + + if not addedEntities[object] then + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + exports['qb-target']:AddTargetEntity(object, { + options = { + { + type = "client", + event = "container_heist:client:startRobbery", + icon = "fas fa-angle-double-right", + label = "Break into " .. containerType.label, + containerType = containerType, + } + }, + distance = 3.0 + }) + addedEntities[object] = true + count = count + 1 + break + end + end + end + end + end + end + + -- Check for trailers in the area (vehicles) + local vehicles = GetGamePool('CVehicle') + for _, vehicle in ipairs(vehicles) do + if DoesEntityExist(vehicle) and not IsEntityDead(vehicle) then + local vehicleCoords = GetEntityCoords(vehicle) + local distance = #(playerCoords - vehicleCoords) + + if distance <= 50.0 then + local model = GetEntityModel(vehicle) + + if not addedEntities[vehicle] then + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + exports['qb-target']:AddTargetEntity(vehicle, { + options = { + { + type = "client", + event = "container_heist:client:startRobbery", + icon = "fas fa-angle-double-right", + label = "Break into " .. containerType.label, + containerType = containerType, + } + }, + distance = 3.0 + }) + addedEntities[vehicle] = true + count = count + 1 + break + end + end + end + end + end + end + + return count +end + +-- Command to start container robbery +RegisterCommand('robcontainer', function() + local container, containerType = IsNearValidContainer() + if container and containerType then + StartContainerRobbery(container, containerType) + else + lib.notify({ + title = Config.Notifications.title, + description = "No valid container nearby!", + type = 'error' + }) + end +end, false) + +-- Command to scan and add all nearby containers to target system +RegisterCommand('scancontainers', function() + local count = ScanAndAddContainersToTarget() + lib.notify({ + title = "Container Scanner", + description = "Added " .. count .. " containers/trailers to target system", + type = 'success', + position = 'top', + duration = 3000 + }) +end, false) + +-- Debug command to show all nearby containers and trailers +RegisterCommand('containersdebug', function() + if not Config.Debug then return end + + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + local foundContainers = 0 + + -- Check for containers in the area (objects) + local objects = GetGamePool('CObject') + for _, object in ipairs(objects) do + if DoesEntityExist(object) and not IsEntityDead(object) then + local objectCoords = GetEntityCoords(object) + local distance = #(playerCoords - objectCoords) + + if distance <= 20.0 then + local model = GetEntityModel(object) + local modelName = "Unknown" + + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + modelName = containerType.model + break + end + end + + print("Found container object: " .. modelName .. " (Hash: " .. model .. ") at distance: " .. distance) + foundContainers = foundContainers + 1 + end + end + end + + -- Check for trailers in the area (vehicles) + local vehicles = GetGamePool('CVehicle') + for _, vehicle in ipairs(vehicles) do + if DoesEntityExist(vehicle) and not IsEntityDead(vehicle) then + local vehicleCoords = GetEntityCoords(vehicle) + local distance = #(playerCoords - vehicleCoords) + + if distance <= 20.0 then + local model = GetEntityModel(vehicle) + local modelName = "Unknown" + + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + modelName = containerType.model + break + end + end + + print("Found trailer vehicle: " .. modelName .. " (Hash: " .. model .. ") at distance: " .. distance) + foundContainers = foundContainers + 1 + end + end + end + + print("Total containers/trailers found: " .. foundContainers) +end, false) + +-- Command to identify the model of what you're looking at +RegisterCommand('identifycontainer', function() + local playerPed = PlayerPedId() + local success, entity = GetEntityPlayerIsFreeAimingAt(PlayerId()) + + if success and DoesEntityExist(entity) then + local model = GetEntityModel(entity) + local modelName = GetModelNameFromHash(model) + local entityType = GetEntityType(entity) + local entityTypeStr = "Unknown" + + if entityType == 1 then + entityTypeStr = "Ped" + elseif entityType == 2 then + entityTypeStr = "Vehicle" + elseif entityType == 3 then + entityTypeStr = "Object" + end + + print("Entity Type: " .. entityTypeStr) + print("Model Hash: " .. model) + print("Model Name: " .. modelName) + + -- Add visual indicator + local coords = GetEntityCoords(entity) + DrawMarker(0, coords.x, coords.y, coords.z + 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 255, 0, 0, 100, false, true, 2, false, nil, nil, false) + + -- Show notification + lib.notify({ + title = "Container Identification", + description = "Type: " .. entityTypeStr .. "\nModel: " .. modelName .. "\nHash: " .. model, + type = 'inform', + position = 'top', + duration = 5000 + }) + else + lib.notify({ + title = "Container Identification", + description = "No entity found. Aim at a container or trailer.", + type = 'error', + position = 'top', + duration = 3000 + }) + end +end, false) + +-- Setup target interactions for containers +CreateThread(function() + -- Wait for target system to be ready + Wait(1000) + + -- Add target for all container types + for _, containerType in pairs(Config.ContainerTypes) do + exports['qb-target']:AddTargetModel(containerType.model, { + options = { + { + type = "client", + event = "container_heist:client:startRobbery", + icon = "fas fa-angle-double-right", + label = "Break into " .. containerType.label, + containerType = containerType, + } + }, + distance = 3.0 + }) + end + + -- Spawn containers at fixed locations if configured + for _, location in pairs(Config.ContainerLocations) do + if location.spawnContainer then + local hash = GetHashKey(location.model) + RequestModel(hash) + while not HasModelLoaded(hash) do + Wait(10) + end + + local container = CreateObject(hash, location.coords.x, location.coords.y, location.coords.z, true, false, false) + SetEntityHeading(container, location.heading) + FreezeEntityPosition(container, true) + SetModelAsNoLongerNeeded(hash) end end end) --- Function to draw text in 3D space -function DrawTextOnCoord(x, y, z, text) - local onScreen, _x, _y = World3dToScreen2d(x, y, z) - if onScreen then - SetTextScale(0.35, 0.35) - SetTextFont(4) - SetTextProportional(1) - SetTextColour(255, 255, 255, 215) - SetTextEntry("STRING") - SetTextCentre(1) - AddTextComponentString(text) - DrawText(_x, _y) - local factor = (string.len(text)) / 370 - DrawRect(_x, _y + 0.0125, 0.015 + factor, 0.03, 0, 0, 0, 75) - end -end - --- Print message when resource starts -AddEventHandler('onClientResourceStart', function(resourceName) - if (GetCurrentResourceName() == resourceName) then - print("^2[Container Heist]^7: Client script started successfully") +-- Automatically scan for containers periodically +CreateThread(function() + while true do + ScanAndAddContainersToTarget() + Wait(30000) -- Scan every 30 seconds + end +end) + +-- Event handler for target interaction +RegisterNetEvent('container_heist:client:startRobbery', function(data) + local container, _ = IsNearValidContainer() + if container then + StartContainerRobbery(container, data.containerType) + end +end) + +-- Event to show police alert +RegisterNetEvent('container_heist:client:policeAlert', function(coords, streetName, containerType) + local playerJob = QBCore.Functions.GetPlayerData().job.name + local alertTitle = Config.Notifications.policeTitle + local alertIcon = 'fas fa-exclamation-triangle' + + -- Customize alert based on job + if playerJob == "marshal" then + alertTitle = "MARSHAL SERVICE ALERT" + alertIcon = 'fas fa-star' -- Marshal badge icon + elseif playerJob == "sheriff" then + alertTitle = "SHERIFF DEPARTMENT ALERT" + alertIcon = 'fas fa-shield-alt' -- Sheriff badge icon + end + + -- Create alert for police officers + lib.notify({ + title = alertTitle, + description = string.format(Config.Notifications.policeMessage, streetName), + type = 'inform', + position = 'top', + icon = alertIcon, + iconColor = '#ff0000' + }) + + -- Add blip to map + CreateRobberyBlip(coords) + + -- Play alert sound + PlaySound(-1, "Lose_1st", "GTAO_FM_Events_Soundset", 0, 0, 1) +end) + +-- Clean up on resource stop +AddEventHandler('onResourceStop', function(resourceName) + if resourceName == GetCurrentResourceName() then + if containerBlip then + RemoveBlip(containerBlip) + end + + if isRobbing and currentContainer then + StopAnimTask(PlayerPedId(), "amb@world_human_welding@male@base", "base", 1.0) + end end end) diff --git a/resources/[jobs]/[crime]/nordi_containerheist/config.lua b/resources/[jobs]/[crime]/nordi_containerheist/config.lua index 79899a50c..6a9a01c40 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/config.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/config.lua @@ -2,8 +2,8 @@ Config = {} -- General Settings Config.Debug = true -- Set to true for debug prints -Config.CooldownTime = 30 -- Minutes between heists (per player) -Config.GlobalCooldown = 15 -- Minutes between heists (server-wide) +Config.CooldownTime = 1 -- Minutes between heists (per player) +Config.GlobalCooldown = 1 -- Minutes between heists (server-wide) Config.PoliceRequired = 1 -- Minimum police required Config.PoliceJobs = { "police", -- Regular police @@ -35,7 +35,6 @@ Config.Notifications = { notEnoughPolice = "Not enough police in the city!", policeMessage = "Container robbery in progress at %s", alreadyRobbed = "This container has already been robbed recently!", - notInZone = "You need to be in a container area to use this tool!", } -- Blip Settings @@ -43,38 +42,42 @@ Config.Blip = { sprite = 67, color = 1, scale = 0.8, - label = "Container Robbery", + label = "Container Einbruch", duration = 180, -- seconds flash = true, } --- Container Types (for rewards and animations) + Config.ContainerTypes = { -- Regular shipping containers - shipping = { + { + model = "prop_container_01a", + type = "shipping", label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, animation = { dict = "amb@world_human_welding@male@base", name = "base", flag = 1, - duration = 30000, -- milliseconds + duration = 30000, }, rewards = { - -- Each reward has a chance (total should be <= 100) {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, - -- Cash reward {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, }, policeAlert = true, }, - - -- Weapons container - weapons = { - label = "Weapons Container", + { + model = "prop_container_01b", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, animation = { dict = "amb@world_human_welding@male@base", name = "base", @@ -90,10 +93,340 @@ Config.ContainerTypes = { }, policeAlert = true, }, + { + model = "prop_container_01c", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_01d", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_01e", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_01f", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_01g", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_01h", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_02a", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_03a", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_03b", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_03mb", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_04a", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_04mb", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_05mb", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_ld", + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -2.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "prop_container_door_mb", + type = "shipping", + label = "Container Door", + offset = vector3(0.0, -1.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 20000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 1, chance = 20}, + {item = "cash", label = "Cash", min = 500, max = 2000, chance = 30}, + }, + policeAlert = false, + }, - -- Cargo trailer - cargo = { + -- Trailer models + { + model = "trailers", + type = "trailer", label = "Cargo Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, animation = { dict = "amb@world_human_welding@male@base", name = "base", @@ -109,10 +442,12 @@ Config.ContainerTypes = { }, policeAlert = true, }, - - -- Food trailer - food = { - label = "Food Trailer", + { + model = "trailers2", + type = "trailer", + label = "Box Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, animation = { dict = "amb@world_human_welding@male@base", name = "base", @@ -127,69 +462,320 @@ Config.ContainerTypes = { }, policeAlert = false, }, + { + model = "trailers3", + type = "trailer", + label = "Transport Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "phone", label = "Phone", min = 1, max = 3, chance = 30}, + {item = "rolex", label = "Rolex Watch", min = 1, max = 2, chance = 20}, + {item = "goldchain", label = "Gold Chain", min = 1, max = 3, chance = 25}, + {item = "diamond", label = "Diamond", min = 1, max = 1, chance = 5}, + {item = "laptop", label = "Laptop", min = 1, max = 1, chance = 15}, + {item = "cash", label = "Cash", min = 1000, max = 5000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "trailers4", + type = "trailer", + label = "Car Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "carparts", label = "Car Parts", min = 3, max = 8, chance = 60}, + {item = "toolbox", label = "Toolbox", min = 1, max = 2, chance = 40}, + {item = "cash", label = "Cash", min = 1000, max = 3000, chance = 30}, + }, + policeAlert = true, + }, + { + model = "trailerlogs", + type = "trailer", + label = "Logging Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "wood", label = "Wood", min = 10, max = 20, chance = 80}, + {item = "cash", label = "Cash", min = 500, max = 2000, chance = 20}, + }, + policeAlert = false, + }, + { + model = "tanker", + type = "trailer", + label = "Fuel Tanker", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 50000, + }, + rewards = { + {item = "petrol", label = "Petrol", min = 10, max = 20, chance = 70}, + {item = "cash", label = "Cash", min = 2000, max = 5000, chance = 30}, + }, + policeAlert = true, + }, + { + model = "tanker2", + type = "trailer", + label = "Chemical Tanker", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 50000, + }, + rewards = { + {item = "chemicals", label = "Chemicals", min = 5, max = 15, chance = 60}, + {item = "cash", label = "Cash", min = 3000, max = 8000, chance = 40}, + }, + policeAlert = true, + }, + { + model = "docktrailer", + type = "trailer", + label = "Dock Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "electronics", label = "Electronics", min = 3, max = 8, chance = 40}, + {item = "plastic", label = "Plastic", min = 10, max = 20, chance = 60}, + {item = "aluminum", label = "Aluminum", min = 10, max = 20, chance = 50}, + {item = "copper", label = "Copper", min = 5, max = 15, chance = 30}, + {item = "cash", label = "Cash", min = 3000, max = 10000, chance = 25}, + }, + policeAlert = true, + }, + { + model = "tr2", + type = "trailer", + label = "Car Carrier Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "carparts", label = "Car Parts", min = 3, max = 8, chance = 60}, + {item = "toolbox", label = "Toolbox", min = 1, max = 2, chance = 40}, + {item = "cash", label = "Cash", min = 1000, max = 3000, chance = 30}, + }, + policeAlert = true, + }, + { + model = "tr3", + type = "trailer", + label = "Large Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "electronics", label = "Electronics", min = 3, max = 8, chance = 40}, + {item = "plastic", label = "Plastic", min = 10, max = 20, chance = 60}, + {item = "aluminum", label = "Aluminum", min = 10, max = 20, chance = 50}, + {item = "copper", label = "Copper", min = 5, max = 15, chance = 30}, + {item = "cash", label = "Cash", min = 3000, max = 10000, chance = 25}, + }, + policeAlert = true, + }, + { + model = "tr4", + type = "trailer", + label = "Training Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "electronics", label = "Electronics", min = 3, max = 8, chance = 40}, + {item = "plastic", label = "Plastic", min = 10, max = 20, chance = 60}, + {item = "aluminum", label = "Aluminum", min = 10, max = 20, chance = 50}, + {item = "copper", label = "Copper", min = 5, max = 15, chance = 30}, + {item = "cash", label = "Cash", min = 3000, max = 10000, chance = 25}, + }, + policeAlert = true, + }, + { + model = "tvtrailer", + type = "trailer", + label = "TV Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "electronics", label = "Electronics", min = 5, max = 10, chance = 70}, + {item = "wires", label = "Wires", min = 5, max = 15, chance = 60}, + {item = "cash", label = "Cash", min = 2000, max = 6000, chance = 30}, + }, + policeAlert = true, + }, + { + model = "armytanker", + type = "trailer", + label = "Military Tanker", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 50000, + }, + rewards = { + {item = "weapon_pistol", label = "Pistol", min = 1, max = 1, chance = 15}, + {item = "pistol_ammo", label = "Pistol Ammo", min = 10, max = 30, chance = 30}, + {item = "armor", label = "Body Armor", min = 1, max = 3, chance = 25}, + {item = "cash", label = "Cash", min = 5000, max = 15000, chance = 20}, + }, + policeAlert = true, + }, + { + model = "armytrailer", + type = "trailer", + label = "Military Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 50000, + }, + rewards = { + {item = "weapon_pistol", label = "Pistol", min = 1, max = 1, chance = 15}, + {item = "pistol_ammo", label = "Pistol Ammo", min = 10, max = 30, chance = 30}, + {item = "armor", label = "Body Armor", min = 1, max = 3, chance = 25}, + {item = "cash", label = "Cash", min = 5000, max = 15000, chance = 20}, + }, + policeAlert = true, + }, + { + model = "freighttrailer", + type = "trailer", + label = "Freight Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 45000, + }, + rewards = { + {item = "electronics", label = "Electronics", min = 3, max = 8, chance = 40}, + {item = "plastic", label = "Plastic", min = 10, max = 20, chance = 60}, + {item = "aluminum", label = "Aluminum", min = 10, max = 20, chance = 50}, + {item = "copper", label = "Copper", min = 5, max = 15, chance = 30}, + {item = "cash", label = "Cash", min = 3000, max = 10000, chance = 25}, + }, + policeAlert = true, + }, + { + model = "graintrailer", + type = "trailer", + label = "Grain Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "grain", label = "Grain", min = 10, max = 20, chance = 80}, + {item = "cash", label = "Cash", min = 500, max = 2000, chance = 20}, + }, + policeAlert = false, + }, + { + model = "proptrailer", + type = "trailer", + label = "Prop Trailer", + offset = vector3(0.0, -5.0, 0.0), + heading = 180.0, + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "electronics", label = "Electronics", min = 3, max = 8, chance = 40}, + {item = "plastic", label = "Plastic", min = 10, max = 20, chance = 60}, + {item = "aluminum", label = "Aluminum", min = 10, max = 20, chance = 50}, + {item = "copper", label = "Copper", min = 5, max = 15, chance = 30}, + {item = "cash", label = "Cash", min = 3000, max = 10000, chance = 25}, + }, + policeAlert = true, + }, + } --- Container Zones (defined by 4 points) -Config.ContainerZones = { - -- Port area shipping containers + + +-- Locations where containers can be found (optional, for target setup) +Config.ContainerLocations = { { - id = "port_containers", - type = "shipping", -- References the container type above for rewards - label = "Port Containers", - points = { - vector3(992.49, -3048.93, 5.9), -- Point 1 - vector3(991.28, -3018.97, 5.9), -- Point 2 - vector3(1063.13, -3016.99, 5.9), -- Point 3 - vector3(1061.97, -3050.63, 5.9) -- Point 4 - }, - minZ = 4.0, - maxZ = 15.0, + coords = vector3(1000.0, -3000.0, 5.0), + heading = 0.0, + model = "prop_container_01a", + spawnContainer = true, -- Whether to spawn a container at this location }, - - -- Weapons containers { - id = "weapons_containers", - type = "weapons", - label = "Weapons Containers", - points = { - vector3(905.45, -3200.67, 5.90), -- Point 1 - vector3(905.45, -3190.67, 5.90), -- Point 2 - vector3(895.45, -3190.67, 5.90), -- Point 3 - vector3(895.45, -3200.67, 5.90) -- Point 4 - }, - minZ = 4.0, - maxZ = 15.0, + coords = vector3(980.0, -3000.0, 5.0), + heading = 0.0, + model = "prop_container_01b", + spawnContainer = true, }, - - -- Cargo trailers - { - id = "cargo_trailers", - type = "cargo", - label = "Cargo Trailers", - points = { - vector3(825.34, -3125.45, 5.90), -- Point 1 - vector3(825.34, -3115.45, 5.90), -- Point 2 - vector3(815.34, -3115.45, 5.90), -- Point 3 - vector3(815.34, -3125.45, 5.90) -- Point 4 - }, - minZ = 4.0, - maxZ = 15.0, - }, - - -- Food trailers - { - id = "food_trailers", - type = "food", - label = "Food Trailers", - points = { - vector3(765.23, -3165.34, 5.90), -- Point 1 - vector3(765.23, -3155.34, 5.90), -- Point 2 - vector3(755.23, -3155.34, 5.90), -- Point 3 - vector3(755.23, -3165.34, 5.90) -- Point 4 - }, - minZ = 4.0, - maxZ = 15.0, - }, - - -- Add more zones as needed + -- Add more fixed locations as needed } diff --git a/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua b/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua index a1b01c8a8..2a8e5aa50 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua @@ -6,21 +6,21 @@ author 'Your Name' version '1.0.0' shared_scripts { + '@ox_lib/init.lua', 'config.lua' } client_scripts { - 'client.lua' + 'client/main.lua' } server_scripts { - 'server.lua' + 'server/main.lua' } lua54 'yes' dependencies { - 'qb-core', + 'ox_lib', 'tgiann-inventory' } - diff --git a/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua index d980b6d1a..a908ced54 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua @@ -1,4 +1,7 @@ local QBCore = exports['qb-core']:GetCoreObject() +local containerCooldowns = {} +local playerCooldowns = {} +local globalCooldowns = {} -- Debug function local function Debug(msg) @@ -7,30 +10,199 @@ local function Debug(msg) end end --- Register usable item -QBCore.Functions.CreateUseableItem(Config.RequiredItems.flex.name, function(source) - print("Player " .. source .. " used item: " .. Config.RequiredItems.flex.name) - TriggerClientEvent('container_heist:client:useFlexItem', source) +-- Check if player has required items +lib.callback.register('container_heist:server:checkRequiredItems', function(source) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return false end + + -- Check for required flex tool + local hasItem = exports['tgiann-inventory']:HasItem(src, Config.RequiredItems.flex.name, Config.RequiredItems.flex.amount) + return hasItem end) --- Test robbery event -RegisterNetEvent('container_heist:server:testRobbery', function(zoneId, zoneType) +-- Check cooldowns +lib.callback.register('container_heist:server:checkCooldown', function(source, containerId) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return {success = false, message = "Player not found"} end + + 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) + return {success = false, message = "You need to wait " .. timeLeft .. " more minutes before attempting another heist!"} + end + + -- Check container cooldown + if containerCooldowns[containerId] and (currentTime - containerCooldowns[containerId]) < (Config.CooldownTime * 60) then + return {success = false, message = Config.Notifications.alreadyRobbed} + end + + -- Check global cooldown for container type + for containerType, cooldownTime in pairs(globalCooldowns) do + if (currentTime - cooldownTime) < (Config.GlobalCooldown * 60) then + local timeLeft = math.ceil(((cooldownTime + (Config.GlobalCooldown * 60)) - currentTime) / 60) + return {success = false, message = Config.Notifications.globalCooldown .. " (" .. timeLeft .. " minutes left)"} + end + end + + return {success = true} +end) + +-- Get police count +lib.callback.register('container_heist:server:getPoliceCount', function() + local policeCount = 0 + local players = QBCore.Functions.GetPlayers() + + for _, playerId in ipairs(players) do + local Player = QBCore.Functions.GetPlayer(playerId) + 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 + end + end + + return policeCount +end) + +-- Alert police +RegisterNetEvent('container_heist:server:alertPolice', function(coords, streetName, containerType) + local src = source + local players = QBCore.Functions.GetPlayers() + + for _, playerId in ipairs(players) do + local Player = QBCore.Functions.GetPlayer(playerId) + 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 + TriggerClientEvent('container_heist:client:policeAlert', playerId, coords, streetName, containerType) + break -- No need to send multiple alerts to the same player + end + end + end + end +end) + +-- Finish robbery and give rewards +RegisterNetEvent('container_heist:server:finishRobbery', function(containerId, containerType) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - print("Player " .. src .. " started robbery in zone: " .. zoneId .. " of type: " .. zoneType) - TriggerClientEvent('QBCore:Notify', src, "Robbery started in zone: " .. zoneId, "success") + local citizenId = Player.PlayerData.citizenid + local currentTime = os.time() - -- Give a test reward - exports['tgiann-inventory']:AddItem(src, "cash", 1000) - TriggerClientEvent('QBCore:Notify', src, "You found $1000!", "success") -end) - --- Print message when resource starts -AddEventHandler('onResourceStart', function(resourceName) - if (GetCurrentResourceName() == resourceName) then - print("^2[Container Heist]^7: Server script started successfully") - print("^2[Container Heist]^7: Registered usable item: " .. Config.RequiredItems.flex.name) + -- Set cooldowns + playerCooldowns[citizenId] = currentTime + containerCooldowns[containerId] = currentTime + globalCooldowns[containerType] = currentTime + + -- Decrease durability of flex tool if configured + if Config.RequiredItems.flex.durability then + local itemData = exports['tgiann-inventory']:GetItemByName(src, Config.RequiredItems.flex.name) + if itemData and itemData.info then + local newDurability = math.max(0, (itemData.info.durabilityPercent or 100) - Config.RequiredItems.flex.durabilityDecrease) + exports['tgiann-inventory']:UpdateItemMetadata(src, Config.RequiredItems.flex.name, itemData.slot, { + durabilityPercent = newDurability, + serie = itemData.info.serie or "TOOL-" .. math.random(100000, 999999), + 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 + + -- Find the container type in config + local containerConfig = nil + for _, config in pairs(Config.ContainerTypes) do + if config.type == containerType then + containerConfig = config + break + end + end + + if not containerConfig then + Debug("Container type not found in config: " .. containerType) + return + end + + -- Give rewards based on chances + local rewardsGiven = 0 + for _, reward in pairs(containerConfig.rewards) do + 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 + end -- <-- This end statement was missing + + if rewardsGiven == 0 then + TriggerClientEvent('QBCore:Notify', src, "The container was empty!", "error") + end +end) + + +-- 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 + + -- Clean up container cooldowns + for containerId, cooldownTime in pairs(containerCooldowns) do + if (currentTime - cooldownTime) > (Config.CooldownTime * 60) then + containerCooldowns[containerId] = nil + 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)