From 5cb18574a7701fb28e35008b6623494f54e46fe6 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sun, 20 Jul 2025 04:10:11 +0200 Subject: [PATCH] ed --- .../nordi_containerheist/client/main.lua | 425 +++++++++++------- .../nordi_containerheist/server/main.lua | 24 +- 2 files changed, 268 insertions(+), 181 deletions(-) diff --git a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua index 138445a4a..c2032605b 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua @@ -1,7 +1,7 @@ local QBCore = exports['qb-core']:GetCoreObject() -local pointCooldowns = {} -local playerCooldowns = {} -local globalCooldowns = {} +local isRobbing = false +local currentPoint = nil +local containerBlip = nil -- Debug function local function Debug(msg) @@ -10,202 +10,289 @@ local function Debug(msg) end end --- 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) +-- Completely rewritten DrawText3D function +function DrawText3D(x, y, z, text) + -- Set the text properties + SetTextScale(0.35, 0.35) + SetTextFont(4) + SetTextProportional(1) + SetTextColour(255, 255, 255, 215) + SetTextEntry("STRING") + SetTextCentre(1) + AddTextComponentString(text) + SetDrawOrigin(x, y, z, 0) + DrawText(0.0, 0.0) + local factor = (string.len(text)) / 370 + DrawRect(0.0, 0.0125, 0.017 + factor, 0.03, 0, 0, 0, 75) + ClearDrawOrigin() +end --- 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 +-- 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 - 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 - if point.id == pointId then - pointType = point.type - break + local distance = #(playerCoords - point.coords) + if distance < minDistance then + minDistance = distance + closestPoint = point end 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) + return closestPoint, minDistance +end --- Get police count -lib.callback.register('container_heist:server:getPoliceCount', function() - local policeCount = 0 - local players = QBCore.Functions.GetPlayers() +-- Function to create a blip at the robbery location +local function CreateRobberyBlip(coords) + if containerBlip then + RemoveBlip(containerBlip) + end - 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 + 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 - return policeCount -end) - --- Alert police -RegisterNetEvent('container_heist:server:alertPolice', function(coords, streetName, containerLabel) - local src = source - local players = QBCore.Functions.GetPlayers() + TaskPlayAnim(playerPed, animDict, animName, 8.0, -8.0, containerType.animation.duration, containerType.animation.flag, 0, false, false, false) - 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 + -- 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 -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 --- 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 +-- Function to start container robbery +local function StartContainerRobbery(point) + if isRobbing then return end - local citizenId = Player.PlayerData.citizenid - local currentTime = os.time() + isRobbing = true + currentPoint = point - -- Set cooldowns - playerCooldowns[citizenId] = currentTime - pointCooldowns[pointId] = currentTime - globalCooldowns[containerTypeName] = currentTime - - -- Get container type - local containerType = Config.ContainerTypes[containerTypeName] + -- Get container type from point + local containerType = Config.ContainerTypes[point.type] if not containerType then - Debug("Container type not found: " .. containerTypeName) + QBCore.Functions.Notify("Invalid container type!", "error") + isRobbing = false + currentPoint = nil return end - -- 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 + -- Check if player has required tools + QBCore.Functions.TriggerCallback('container_heist:server:checkRequiredItems', function(hasTools) + if not hasTools then + QBCore.Functions.Notify(Config.Notifications.noTools, "error") + isRobbing = false + currentPoint = nil + return 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 - for _, reward in pairs(containerType.rewards) do - if math.random(1, 100) <= reward.chance then - local amount = math.random(reward.min, reward.max) + + -- Check cooldowns + QBCore.Functions.TriggerCallback('container_heist:server:checkCooldown', function(cooldownCheck) + if not cooldownCheck.success then + QBCore.Functions.Notify(cooldownCheck.message, "error") + isRobbing = false + currentPoint = nil + return + end - 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) + -- Check police count + QBCore.Functions.TriggerCallback('container_heist:server:getPoliceCount', function(policeCount) + if policeCount < Config.PoliceRequired then + QBCore.Functions.Notify(Config.Notifications.notEnoughPolice, "error") + isRobbing = false + currentPoint = nil + return end - TriggerClientEvent('QBCore:Notify', src, "Found " .. amount .. "x " .. reward.label, "success") - end - - rewardsGiven = rewardsGiven + 1 - end - 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, point.id) + end) +end + +-- Command to start container robbery +RegisterCommand('robcontainer', function() + local point, distance = GetNearestContainerPoint() - if rewardsGiven == 0 then - TriggerClientEvent('QBCore:Notify', src, "The container was empty!", "error") + 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 and drawing text +CreateThread(function() + while true do + local sleep = 1000 + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + + for _, point in pairs(Config.ContainerPoints) do + local distance = #(playerCoords - point.coords) + + if distance < 10.0 then + sleep = 0 + + -- Draw marker at the container point + 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, 0.2, 0, 255, 0, 100, false, true, 2, false, nil, nil, false) + + -- Draw 3D text above the marker when close enough + if distance < 3.0 then + DrawText3D(point.coords.x, point.coords.y, point.coords.z + 0.5, point.label .. " [E]") + + -- Check for interaction + if IsControlJustReleased(0, 38) and distance < 1.5 then -- E key + StartContainerRobbery(point) + end + end + end + end + + -- Debug mode - show all container points + 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, 0.2, 255, 0, 0, 100, false, true, 2, false, nil, nil, false) + DrawText3D(point.coords.x, point.coords.y, point.coords.z + 1.0, + point.id .. " (" .. point.type .. ")") + end + sleep = 0 + end + + Wait(sleep) 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 +-- 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 - -- Clean up point cooldowns - for pointId, cooldownTime in pairs(pointCooldowns) do - if (currentTime - cooldownTime) > (Config.CooldownTime * 60) then - pointCooldowns[pointId] = 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 + if isRobbing then + StopAnimTask(PlayerPedId(), "amb@world_human_welding@male@base", "base", 1.0) end end end) diff --git a/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua index 138445a4a..6fae7d1a8 100644 --- a/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua +++ b/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua @@ -11,21 +11,21 @@ local function Debug(msg) end -- Check if player has required items -lib.callback.register('container_heist:server:checkRequiredItems', function(source) +QBCore.Functions.CreateCallback('container_heist:server:checkRequiredItems', function(source, cb) local src = source local Player = QBCore.Functions.GetPlayer(src) - if not Player then return false end + if not Player then return cb(false) end -- Check for required flex tool local hasItem = exports['tgiann-inventory']:HasItem(src, Config.RequiredItems.flex.name, Config.RequiredItems.flex.amount) - return hasItem + cb(hasItem) end) -- Check cooldowns -lib.callback.register('container_heist:server:checkCooldown', function(source, pointId) +QBCore.Functions.CreateCallback('container_heist:server:checkCooldown', function(source, cb, pointId) local src = source local Player = QBCore.Functions.GetPlayer(src) - if not Player then return {success = false, message = "Player not found"} end + if not Player then return cb({success = false, message = "Player not found"}) end local citizenId = Player.PlayerData.citizenid local currentTime = os.time() @@ -33,12 +33,12 @@ lib.callback.register('container_heist:server:checkCooldown', function(source, p -- 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!"} + return cb({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} + return cb({success = false, message = Config.Notifications.alreadyRobbed}) end -- Find point type @@ -51,20 +51,20 @@ lib.callback.register('container_heist:server:checkCooldown', function(source, p end if not pointType then - return {success = false, message = "Invalid point!"} + return cb({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)"} + return cb({success = false, message = Config.Notifications.globalCooldown .. " (" .. timeLeft .. " minutes left)"}) end - return {success = true} + cb({success = true}) end) -- Get police count -lib.callback.register('container_heist:server:getPoliceCount', function() +QBCore.Functions.CreateCallback('container_heist:server:getPoliceCount', function(source, cb) local policeCount = 0 local players = QBCore.Functions.GetPlayers() @@ -81,7 +81,7 @@ lib.callback.register('container_heist:server:getPoliceCount', function() end end - return policeCount + cb(policeCount) end) -- Alert police