diff --git a/resources/[jobs]/[crime]/nordi_hostage/client.lua b/resources/[jobs]/[crime]/nordi_hostage/client.lua new file mode 100644 index 000000000..b6dd26bdb --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_hostage/client.lua @@ -0,0 +1,272 @@ +local hasHostage = false +local currentHostage = nil +local isAimingAtPed = false +local lastAimedPed = nil + +-- Check if player has cable ties +local function HasCableTies() + local hasTies = QBCore.Functions.HasItem('cabletie') + return hasTies +end + +-- Check if player is aiming at an NPC +Citizen.CreateThread(function() + while true do + Citizen.Wait(500) + local playerPed = PlayerPedId() + local _, entity = GetEntityPlayerIsFreeAimingAt(PlayerId()) + + if entity and IsEntityAPed(entity) and not IsPedAPlayer(entity) then + isAimingAtPed = true + lastAimedPed = entity + else + isAimingAtPed = false + lastAimedPed = nil + end + end +end) + +-- Add targeting option when aiming at NPC with weapon +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if isAimingAtPed and lastAimedPed and not hasHostage then + exports['qb-target']:AddTargetEntity(lastAimedPed, { + options = { + { + type = "client", + action = function() + if HasCableTies() then + TriggerEvent('qb-hostage:client:takeHostageMenu', lastAimedPed) + else + QBCore.Functions.Notify("You need cable ties to take a hostage", "error") + end + end, + icon = "fas fa-user-ninja", + label = "Take Hostage", + canInteract = function() + return not hasHostage and HasCableTies() + end + } + }, + distance = 2.0 + }) + elseif not isAimingAtPed and lastAimedPed and not hasHostage then + exports['qb-target']:RemoveTargetEntity(lastAimedPed, 'Take Hostage') + end + end +end) + +-- Take hostage menu +RegisterNetEvent('qb-hostage:client:takeHostageMenu', function(ped) + local options = {} + + if not hasHostage then + options[#options+1] = { + title = "Take Hostage", + description = "Take this person as your hostage", + event = "qb-hostage:client:takeHostage", + args = { ped = ped } + } + else + options[#options+1] = { + title = "Release Hostage", + description = "Let your current hostage go", + event = "qb-hostage:client:releaseHostage" + } + + options[#options+1] = { + title = "Put in Vehicle", + description = "Put hostage in nearby vehicle", + event = "qb-hostage:client:putInVehicleMenu" + } + + options[#options+1] = { + title = "Remove from Vehicle", + description = "Take hostage out of vehicle", + event = "qb-hostage:client:removeFromVehicle" + } + end + + lib.registerContext({ + id = 'hostage_menu', + title = 'Hostage Options', + options = options + }) + + lib.showContext('hostage_menu') +end) + +-- Take hostage function +RegisterNetEvent('qb-hostage:client:takeHostage', function(data) + local ped = data.ped + if hasHostage then return end + + local playerPed = PlayerPedId() + + -- Play animation and notify + RequestAnimDict("anim@gangops@hostage@") + while not HasAnimDictLoaded("anim@gangops@hostage@") do + Citizen.Wait(100) + end + + TaskPlayAnim(playerPed, "anim@gangops@hostage@", "perp_idle", 8.0, 1.0, -1, 49, 0, false, false, false) + TaskPlayAnim(ped, "anim@gangops@hostage@", "victim_idle", 8.0, 1.0, -1, 9, 0, false, false, false) + + AttachEntityToEntity(ped, playerPed, GetPedBoneIndex(playerPed, 57005), 0.15, 0.0, -0.05, 340.0, 0.0, 80.0, false, false, false, false, 2, true) + + SetBlockingOfNonTemporaryEvents(ped, true) + SetEntityInvincible(ped, true) + SetPedFleeAttributes(ped, 0, false) + SetPedCombatAttributes(ped, 17, true) + SetPedSeeingRange(ped, 0.0) + SetPedHearingRange(ped, 0.0) + SetPedAlertness(ped, 0) + SetPedKeepTask(ped, true) + + currentHostage = ped + hasHostage = true + + QBCore.Functions.Notify("You've taken a hostage", "success") + + -- Control loop for hostage + Citizen.CreateThread(function() + while hasHostage do + Citizen.Wait(0) + if not IsEntityPlayingAnim(playerPed, "anim@gangops@hostage@", "perp_idle", 3) then + TaskPlayAnim(playerPed, "anim@gangops@hostage@", "perp_idle", 8.0, 1.0, -1, 49, 0, false, false, false) + end + + if IsPedDeadOrDying(currentHostage, true) then + TriggerEvent('qb-hostage:client:releaseHostage') + QBCore.Functions.Notify("Hostage died", "error") + end + + if IsControlJustReleased(0, 73) then -- X button to release + TriggerEvent('qb-hostage:client:releaseHostage') + end + end + end) +end) + +-- Release hostage +RegisterNetEvent('qb-hostage:client:releaseHostage', function() + if not hasHostage then return end + + DetachEntity(currentHostage, true, false) + ClearPedTasks(currentHostage) + ClearPedTasks(PlayerPedId()) + + SetBlockingOfNonTemporaryEvents(currentHostage, false) + SetEntityInvincible(currentHostage, false) + TaskReactAndFleePed(currentHostage, PlayerPedId()) + + RemoveAnimDict("anim@gangops@hostage@") + + currentHostage = nil + hasHostage = false + + QBCore.Functions.Notify("You released the hostage", "success") +end) + +-- Put hostage in vehicle menu +RegisterNetEvent('qb-hostage:client:putInVehicleMenu', function() + local playerPed = PlayerPedId() + local coords = GetEntityCoords(playerPed) + local vehicles = QBCore.Functions.GetVehicles() + local options = {} + + for _, vehicle in ipairs(vehicles) do + local vehCoords = GetEntityCoords(vehicle) + local distance = #(coords - vehCoords) + + if distance < 10.0 then + local plate = QBCore.Functions.GetPlate(vehicle) + local seats = GetVehicleMaxNumberOfPassengers(vehicle) + + for i = -1, seats - 1 do + if IsVehicleSeatFree(vehicle, i) then + local seatName = i == -1 and "Driver" or ("Passenger " .. (i + 1)) + options[#options+1] = { + title = ("%s (%s)"):format(plate, seatName), + description = "Put hostage in this seat", + event = "qb-hostage:client:putInVehicle", + args = { vehicle = vehicle, seat = i } + } + end + end + end + end + + if #options == 0 then + QBCore.Functions.Notify("No suitable vehicles nearby", "error") + return + end + + lib.registerContext({ + id = 'hostage_vehicle_menu', + title = 'Select Vehicle Seat', + options = options + }) + + lib.showContext('hostage_vehicle_menu') +end) + +-- Put hostage in vehicle +RegisterNetEvent('qb-hostage:client:putInVehicle', function(data) + if not hasHostage then return end + + local vehicle = data.vehicle + local seat = data.seat + + DetachEntity(currentHostage, true, false) + ClearPedTasks(currentHostage) + ClearPedTasks(PlayerPedId()) + + TaskWarpPedIntoVehicle(currentHostage, vehicle, seat) + + SetBlockingOfNonTemporaryEvents(currentHostage, true) + SetPedFleeAttributes(currentHostage, 0, false) + + hasHostage = false + + QBCore.Functions.Notify("You put the hostage in the vehicle", "success") + RemoveAnimDict("anim@gangops@hostage@") +end) + +-- Remove hostage from vehicle +RegisterNetEvent('qb-hostage:client:removeFromVehicle', function() + if IsPedInAnyVehicle(currentHostage, false) then + TaskLeaveVehicle(currentHostage, GetVehiclePedIsIn(currentHostage, false), 16) + + Citizen.Wait(1000) + + -- Take hostage again after removing from vehicle + local playerPed = PlayerPedId() + AttachEntityToEntity(currentHostage, playerPed, GetPedBoneIndex(playerPed, 57005), 0.15, 0.0, -0.05, 340.0, 0.0, 80.0, false, false, false, false, 2, true) + + TaskPlayAnim(playerPed, "anim@gangops@hostage@", "perp_idle", 8.0, 1.0, -1, 49, 0, false, false, false) + TaskPlayAnim(currentHostage, "anim@gangops@hostage@", "victim_idle", 8.0, 1.0, -1, 9, 0, false, false, false) + + hasHostage = true + + QBCore.Functions.Notify("You took the hostage out of the vehicle", "success") + else + QBCore.Functions.Notify("Hostage is not in a vehicle", "error") + end +end) + +-- Cleanup on resource stop +AddEventHandler('onResourceStop', function(resource) + if resource == GetCurrentResourceName() then + if hasHostage and currentHostage then + DetachEntity(currentHostage, true, false) + ClearPedTasks(currentHostage) + SetBlockingOfNonTemporaryEvents(currentHostage, false) + SetEntityInvincible(currentHostage, false) + TaskReactAndFleePed(currentHostage, PlayerPedId()) + end + ClearPedTasks(PlayerPedId()) + RemoveAnimDict("anim@gangops@hostage@") + end +end) diff --git a/resources/[jobs]/[crime]/nordi_hostage/fxmanifest.lua b/resources/[jobs]/[crime]/nordi_hostage/fxmanifest.lua new file mode 100644 index 000000000..f153a0ce3 --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_hostage/fxmanifest.lua @@ -0,0 +1,24 @@ +fx_version 'cerulean' +game 'gta5' + +author 'Your Name' +description 'QBcore Hostage Script' +version '1.0.0' + +client_scripts { + '@qb-core/import.lua', + '@qb-target/client.lua', + '@ox_lib/init.lua', + 'client.lua' +} + +server_scripts { + '@qb-core/import.lua', + 'server.lua' +} + +dependencies { + 'qb-core', + 'qb-target', + 'ox_lib' +} diff --git a/resources/[jobs]/[crime]/nordi_hostage/server.lua b/resources/[jobs]/[crime]/nordi_hostage/server.lua new file mode 100644 index 000000000..1884b51cf --- /dev/null +++ b/resources/[jobs]/[crime]/nordi_hostage/server.lua @@ -0,0 +1,14 @@ +QBCore = exports['qb-core']:GetCoreObject() + +-- You can add server-side events here if needed +-- For example, to log when players take hostages + +RegisterNetEvent('qb-hostage:server:logHostageTaken', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + + -- Add logging or other server-side functionality here + -- For example, you could add a cooldown or notify police +end) + +-- You might want to add a command for police to check for active hostage situations