From 515271cff01aad9eca9267b6db88f0ae130849cb Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sun, 20 Jul 2025 02:41:44 +0200 Subject: [PATCH] ed --- .../nordi_containerheist/client/main.lua | 308 ++++++++++++++++++ .../[crime]/nordi_containerheist/config.lua | 155 +++++++++ .../nordi_containerheist/fxmanifest.lua | 26 ++ .../nordi_containerheist/server/main.lua | 195 +++++++++++ 4 files changed, 684 insertions(+) create mode 100644 resources/[jobs]/[crime]/nordi_containerheist/client/main.lua create mode 100644 resources/[jobs]/[crime]/nordi_containerheist/config.lua create mode 100644 resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua create mode 100644 resources/[jobs]/[crime]/nordi_containerheist/server/main.lua diff --git a/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua new file mode 100644 index 000000000..7015aac1b --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_containerheist/client/main.lua @@ -0,0 +1,308 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local containerCooldowns = {} +local isRobbing = false +local currentContainer = nil +local containerBlip = nil + +-- Debug function +local function Debug(msg) + if Config.Debug then + print("[Container Heist] " .. msg) + end +end + +-- Function to check if player is near a valid container +local function IsNearValidContainer() + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + + -- Check for containers in the area + 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) + + if distance <= 5.0 then + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + return object, containerType + end + end + end + end + end + + -- Check for trailers in the area + 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 then + for _, containerType in pairs(Config.ContainerTypes) do + if model == GetHashKey(containerType.model) then + return vehicle, containerType + end + end + end + end + end + + return nil, nil +end + +-- Function to create a blip at the robbery location +local function CreateRobberyBlip(coords) + if containerBlip then + RemoveBlip(containerBlip) + end + + containerBlip = AddBlipForCoord(coords) + SetBlipSprite(containerBlip, Config.Blip.sprite) + SetBlipColour(containerBlip, Config.Blip.color) + 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 + +-- 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) + +-- 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) + +-- 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) + -- Create alert for police officers + lib.notify({ + title = Config.Notifications.policeTitle, + description = string.format(Config.Notifications.policeMessage, streetName), + type = 'inform', + position = 'top', + icon = 'fas fa-exclamation-triangle', + 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 new file mode 100644 index 000000000..522e2d429 --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_containerheist/config.lua @@ -0,0 +1,155 @@ +Config = {} + +-- General Settings +Config.Debug = false -- Set to true for debug prints +Config.CooldownTime = 30 -- Minutes between heists (per player) +Config.GlobalCooldown = 15 -- Minutes between heists (server-wide) +Config.PoliceRequired = 1 -- Minimum police required +Config.PoliceJobName = "police" -- Police job name + +-- Required Items +Config.RequiredItems = { + flex = { + name = "angle_grinder", -- Item name in your inventory + label = "Angle Grinder", -- Display name + amount = 1, -- How many required + remove = false, -- Whether to remove the item after use + durability = false, -- Whether the item has durability + durabilityDecrease = 20, -- How much durability to decrease per use (%) + }, + -- You can add more required items here +} + +-- Notification Settings +Config.Notifications = { + title = "Container Heist", + policeTitle = "DISPATCH ALERT", + success = "You successfully broke into the container!", + failed = "You failed to break into the container!", + noTools = "You don't have the required tools!", + cooldown = "You need to wait before attempting another heist!", + globalCooldown = "This type of heist is currently on cooldown!", + notEnoughPolice = "Not enough police in the city!", + policeMessage = "Container robbery in progress at %s", + alreadyRobbed = "This container has already been robbed recently!", +} + +-- Blip Settings +Config.Blip = { + sprite = 67, + color = 1, + scale = 0.8, + label = "Container Robbery", + duration = 180, -- seconds + flash = true, +} + +-- Container Types +Config.ContainerTypes = { + -- Regular shipping containers + { + model = "prop_container_01a", -- Container model + type = "shipping", + label = "Shipping Container", + offset = vector3(0.0, -4.0, 0.0), -- Player position offset for interaction + heading = 180.0, -- Player heading for interaction + animation = { + dict = "amb@world_human_welding@male@base", + name = "base", + flag = 1, + duration = 30000, -- milliseconds + }, + 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, + }, + { + 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", + flag = 1, + duration = 30000, + }, + rewards = { + {item = "weapon_pistol", label = "Pistol", min = 1, max = 1, chance = 10}, + {item = "pistol_ammo", label = "Pistol Ammo", min = 10, max = 30, chance = 25}, + {item = "armor", label = "Body Armor", min = 1, max = 2, chance = 20}, + {item = "weapon_knife", label = "Knife", min = 1, max = 1, chance = 30}, + {item = "cash", label = "Cash", min = 2000, max = 7000, chance = 35}, + }, + policeAlert = true, + }, + -- Trailer containers + { + 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", + 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 = "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", + flag = 1, + duration = 40000, + }, + rewards = { + {item = "food", label = "Food", min = 5, max = 15, chance = 70}, + {item = "water", label = "Water", min = 5, max = 15, chance = 70}, + {item = "firstaid", label = "First Aid Kit", min = 1, max = 3, chance = 30}, + {item = "cash", label = "Cash", min = 500, max = 3000, chance = 20}, + }, + policeAlert = false, -- No police alert for food trailers + }, + -- Add more container types as needed +} + +-- Locations where containers can be found (optional, for target setup) +Config.ContainerLocations = { + { + 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 + }, + { + coords = vector3(980.0, -3000.0, 5.0), + heading = 0.0, + model = "prop_container_01b", + spawnContainer = true, + }, + -- Add more fixed locations as needed +} diff --git a/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua b/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua new file mode 100644 index 000000000..2a8e5aa50 --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_containerheist/fxmanifest.lua @@ -0,0 +1,26 @@ +fx_version 'cerulean' +game 'gta5' + +description 'Container Heist Script' +author 'Your Name' +version '1.0.0' + +shared_scripts { + '@ox_lib/init.lua', + 'config.lua' +} + +client_scripts { + 'client/main.lua' +} + +server_scripts { + 'server/main.lua' +} + +lua54 'yes' + +dependencies { + 'ox_lib', + 'tgiann-inventory' +} diff --git a/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua b/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua new file mode 100644 index 000000000..3701af14d --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_containerheist/server/main.lua @@ -0,0 +1,195 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local containerCooldowns = {} +local playerCooldowns = {} +local globalCooldowns = {} + +-- Debug function +local function Debug(msg) + if Config.Debug then + print("[Container Heist] " .. 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) + +-- 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 and Player.PlayerData.job.name == Config.PoliceJobName and Player.PlayerData.job.onduty then + policeCount = policeCount + 1 + 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 and Player.PlayerData.job.name == Config.PoliceJobName and Player.PlayerData.job.onduty then + TriggerClientEvent('container_heist:client:policeAlert', playerId, coords, streetName, containerType) + 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 + + local citizenId = Player.PlayerData.citizenid + local currentTime = os.time() + + -- 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 and itemData.info.durabilityPercent then + local newDurability = math.max(0, itemData.info.durabilityPercent - Config.RequiredItems.flex.durabilityDecrease) + exports['tgiann-inventory']:UpdateItemMetadata(src, Config.RequiredItems.flex.name, itemData.slot, { + durabilityPercent = newDurability, + serie = itemData.info.serie, + 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 + + 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)