From d3f4114333c31defbe4521e88edb0b8063a5bac6 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sun, 20 Jul 2025 04:05:16 +0200 Subject: [PATCH] Update main.lua --- .../nordi_containerheist/client/main.lua | 415 +++++++----------- 1 file changed, 165 insertions(+), 250 deletions(-) diff --git a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua index 05ec0b6fa..138445a4a 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua @@ -1,8 +1,7 @@ local QBCore = exports['qb-core']:GetCoreObject() -local isRobbing = false -local currentPoint = nil -local containerBlip = nil -local nearbyPoint = nil +local pointCooldowns = {} +local playerCooldowns = {} +local globalCooldowns = {} -- Debug function local function Debug(msg) @@ -11,286 +10,202 @@ local function Debug(msg) end end --- Helper function to draw 3D text -function DrawText3D(x, y, z, text) - local onScreen, _x, _y = World3dToScreen2d(x, y, z) - local px, py, pz = table.unpack(GetGameplayCamCoords()) +-- 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 - 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, 41, 11, 41, 68) -end + -- Check for required flex tool + local hasItem = exports['tgiann-inventory']:HasItem(src, Config.RequiredItems.flex.name, Config.RequiredItems.flex.amount) + return hasItem +end) --- Function to find the nearest container point -local function GetNearestContainerPoint() - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) - local closestPoint = nil - local minDistance = 3.0 -- Maximum interaction distance +-- Check cooldowns +lib.callback.register('container_heist:server:checkCooldown', function(source, pointId) + 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 point cooldown + if pointCooldowns[pointId] and (currentTime - pointCooldowns[pointId]) < (Config.CooldownTime * 60) then + return {success = false, message = Config.Notifications.alreadyRobbed} + end + + -- Find point type + local pointType = nil for _, point in pairs(Config.ContainerPoints) do - local distance = #(playerCoords - point.coords) - if distance < minDistance then - minDistance = distance - closestPoint = point + if point.id == pointId then + pointType = point.type + break end end - return closestPoint, minDistance -end + if not pointType then + return {success = false, message = "Invalid point!"} + end + + -- Check global cooldown for container type + if globalCooldowns[pointType] and (currentTime - globalCooldowns[pointType]) < (Config.GlobalCooldown * 60) then + local timeLeft = math.ceil(((globalCooldowns[pointType] + (Config.GlobalCooldown * 60)) - currentTime) / 60) + return {success = false, message = Config.Notifications.globalCooldown .. " (" .. timeLeft .. " minutes left)"} + end + + return {success = true} +end) --- Function to create a blip at the robbery location -local function CreateRobberyBlip(coords) - if containerBlip then - RemoveBlip(containerBlip) - end +-- Get police count +lib.callback.register('container_heist:server:getPoliceCount', function() + local policeCount = 0 + local players = QBCore.Functions.GetPlayers() - 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 + 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) -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 + return policeCount +end) --- Function to start container robbery -local function StartContainerRobbery(point) - if isRobbing then return end +-- Alert police +RegisterNetEvent('container_heist:server:alertPolice', function(coords, streetName, containerLabel) + local src = source + local players = QBCore.Functions.GetPlayers() - isRobbing = true - currentPoint = point + 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, containerLabel) + 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(pointId, containerTypeName) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return end - -- Get container type from point - local containerType = Config.ContainerTypes[point.type] + local citizenId = Player.PlayerData.citizenid + local currentTime = os.time() + + -- Set cooldowns + playerCooldowns[citizenId] = currentTime + pointCooldowns[pointId] = currentTime + globalCooldowns[containerTypeName] = currentTime + + -- Get container type + local containerType = Config.ContainerTypes[containerTypeName] if not containerType then - QBCore.Functions.Notify("Invalid container type!", "error") - isRobbing = false - currentPoint = nil + Debug("Container type not found: " .. containerTypeName) return end - -- Check if player has required tools - local hasTools = lib.callback.await('container_heist:server:checkRequiredItems', false) - if not hasTools then - QBCore.Functions.Notify(Config.Notifications.noTools, "error") - isRobbing = false - currentPoint = nil - return + -- 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 - -- Check cooldowns - local cooldownCheck = lib.callback.await('container_heist:server:checkCooldown', false, point.id) - if not cooldownCheck.success then - QBCore.Functions.Notify(cooldownCheck.message, "error") - isRobbing = false - currentPoint = nil - return + -- Give rewards based on chances + local rewardsGiven = 0 + for _, reward in pairs(containerType.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 - -- Check police count - local policeCount = lib.callback.await('container_heist:server:getPoliceCount', false) - if policeCount < Config.PoliceRequired then - QBCore.Functions.Notify(Config.Notifications.notEnoughPolice, "error") - isRobbing = false - currentPoint = nil - return + if rewardsGiven == 0 then + TriggerClientEvent('QBCore:Notify', src, "The container was empty!", "error") end - - -- Position player for animation - SetEntityCoords(PlayerPedId(), point.coords.x, point.coords.y, point.coords.z) - SetEntityHeading(PlayerPedId(), point.heading) - - -- Alert police if configured - if containerType.policeAlert then - local streetName = GetStreetNameFromHashKey(GetStreetNameAtCoord(point.coords.x, point.coords.y, point.coords.z)) - TriggerServerEvent('container_heist:server:alertPolice', point.coords, streetName, containerType.label) - end - - -- Start robbery progress bar - PlayRobberyAnimation(containerType) - - QBCore.Functions.Progressbar("container_robbery", 'Breaking into ' .. containerType.label, containerType.animation.duration, false, true, { - disableMovement = true, - disableCarMovement = true, - disableMouse = false, - disableCombat = true, - }, {}, {}, {}, function() -- Done - -- Success - TriggerServerEvent('container_heist:server:finishRobbery', point.id, point.type) - QBCore.Functions.Notify(Config.Notifications.success, "success") - isRobbing = false - currentPoint = nil - end, function() -- Cancel - -- Cancelled - QBCore.Functions.Notify(Config.Notifications.failed, "error") - isRobbing = false - currentPoint = nil - end) -end +end) --- Command to start container robbery -RegisterCommand('robcontainer', function() - local point, distance = GetNearestContainerPoint() - - if point then - StartContainerRobbery(point) - else - QBCore.Functions.Notify("No container nearby!", "error") - end -end, false) - --- Command to toggle debug mode -RegisterCommand('containerdebug', function() - Config.Debug = not Config.Debug - - if Config.Debug then - QBCore.Functions.Notify("Container Debug mode enabled", "primary") - else - QBCore.Functions.Notify("Container Debug mode disabled", "primary") - end -end, false) - --- Main thread for checking nearby container points +-- Clean up cooldowns periodically CreateThread(function() while true do - local playerPed = PlayerPedId() - local playerCoords = GetEntityCoords(playerPed) - local wait = 1000 - local point, distance = GetNearestContainerPoint() + Wait(60000) -- Check every minute + local currentTime = os.time() - if point and distance < 3.0 then - wait = 0 - nearbyPoint = point - - -- Draw marker - DrawMarker(1, point.coords.x, point.coords.y, point.coords.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.8, 0.8, 0, 255, 0, 100, false, true, 2, false, nil, nil, false) - - -- Draw text - DrawText3D(point.coords.x, point.coords.y, point.coords.z, point.label .. " [E]") - - -- Check for interaction - if IsControlJustReleased(0, 38) and distance < 1.5 then -- E key - StartContainerRobbery(point) + -- Clean up player cooldowns + for citizenId, cooldownTime in pairs(playerCooldowns) do + if (currentTime - cooldownTime) > (Config.CooldownTime * 60) then + playerCooldowns[citizenId] = nil end - else - nearbyPoint = nil end - Wait(wait) - end -end) - --- Debug thread for showing all container points -CreateThread(function() - while true do - Wait(0) - - if Config.Debug then - for _, point in pairs(Config.ContainerPoints) do - DrawMarker(1, point.coords.x, point.coords.y, point.coords.z - 1.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) - DrawText3D(point.coords.x, point.coords.y, point.coords.z + 0.5, point.id .. " (" .. point.type .. ")") + -- Clean up point cooldowns + for pointId, cooldownTime in pairs(pointCooldowns) do + if (currentTime - cooldownTime) > (Config.CooldownTime * 60) then + pointCooldowns[pointId] = nil end - else - Wait(1000) - end - end -end) - --- Event to show police alert -RegisterNetEvent('container_heist:client:policeAlert', function(coords, streetName, containerLabel) - local playerJob = QBCore.Functions.GetPlayerData().job.name - local alertTitle = Config.Notifications.policeTitle - - -- Customize alert based on job - if playerJob == "marshal" then - alertTitle = "MARSHAL SERVICE ALERT" - elseif playerJob == "sheriff" then - alertTitle = "SHERIFF DEPARTMENT ALERT" - end - - -- Create alert for police officers - QBCore.Functions.Notify(alertTitle .. ": " .. string.format(Config.Notifications.policeMessage, streetName), "police", 10000) - - -- 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 then - StopAnimTask(PlayerPedId(), "amb@world_human_welding@male@base", "base", 1.0) + -- 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)