--[[ require ]] local Utils = require 'modules.utils.client' local Target = require 'modules.target.client' --[[ state ]] Rope = {} local playerRopes = {} local ownedHookObject = nil local ownedFakeAtmObject = nil local ownedAnchorThread = false --[[ helper functions ]] function deletePlayerRope(owner) if not playerRopes[owner] then return end if DoesPlayerRopeExist(owner) then DeleteRope(playerRopes[owner].id) end cleanup_rope_textures() playerRopes[owner] = nil end function deletePlayerRopes() for _, rope in pairs(playerRopes) do if DoesRopeExist(rope.id) then DeleteRope(rope.id) end end playerRopes = {} end function deleteHookObject() if not ownedHookObject or not DoesEntityExist(ownedHookObject) then return end SetEntityAsMissionEntity(ownedHookObject, true, true) DeleteEntity(ownedHookObject) ownedHookObject = nil end function deleteFakeAtmEntity() if not ownedFakeAtmObject or not DoesEntityExist(ownedFakeAtmObject) then return end SetEntityAsMissionEntity(ownedFakeAtmObject, true, true) DeleteEntity(ownedFakeAtmObject) ownedFakeAtmObject = nil end function giveHookToPedHand() deleteHookObject() local playerPedId = cache.ped local hookModel = 'prop_rope_hook_01' lib.requestModel(hookModel) local hookCoords = GetEntityCoords(playerPedId) ownedHookObject = Utils.CreateObject(hookModel, hookCoords, nil, true, true, false) local boneIndex = GetPedBoneIndex(playerPedId, 57005) AttachEntityToEntity(ownedHookObject, playerPedId, boneIndex, 0.15, 0.0, -0.05, 120.0, 0.0, 15.0, true, false, false, true, 2, true) end function cleanup_rope_textures() if #GetAllRopes() == 0 then RopeUnloadTextures() end end function addRopeToPlayer(owner, attach) while not RopeAreTexturesLoaded() do RopeLoadTextures() Citizen.Wait(0) end local coords = GetEntityCoords(cache.ped) local rope = AddRope( coords.x, coords.y, coords.z, 0.0, 0.0, 0.0, 1.0, 4, 7.0, 1.0, 0, false, false, false, 0, false, 0 ) if not DoesRopeExist(rope) then cleanup_rope_textures() return false end playerRopes[owner] = { id = rope, attach = attach } return rope end function attachRopeToObject(rope, ent1, ent2, offset1, offset2) AttachEntitiesToRope(rope, ent1, ent2, offset1.x, offset1.y, offset1.z, offset2.x, offset2.y, offset2.z, 7.0, 0, 0, 'rope_attach_a', 'rope_attach_b') end function DoesPlayerRopeExist(owner, attach) local playerRope = playerRopes[owner] local state = playerRope and DoesRopeExist(playerRope.id) if not state then return false end if attach and playerRope.attach ~= attach then state = false end return state end function onOwnedAnchorCreated() if ownedAnchorThread then return end Citizen.CreateThread(function() local textUI = false local attachedVehicle = nil ownedAnchorThread = true while ownedAnchorThread do local wait = 500 local entityCoords = GetEntityCoords(cache.ped) local closestVehicle = lib.getClosestVehicle(entityCoords, 5.0, false) local distanceFromFakeAtm = #(entityCoords - GetEntityCoords(ownedFakeAtmObject)) if distanceFromFakeAtm < 2.0 then wait = 5 if not textUI then textUI = true Utils.ShowTextUI('[E] ' .. locale('remove_rope')) end if IsControlJustPressed(0, 38) then Utils.HideTextUI() deleteHookObject() deleteFakeAtmEntity() TriggerServerEvent(_e('server:rope:DeletePlayerRope')) client.setBusy(false, 'rope.removeatmrope') ownedAnchorThread = false return end elseif closestVehicle then local offsetCoords = GetOffsetFromEntityInWorldCoords(closestVehicle, 0, -2.3, 0.0) if #(entityCoords - offsetCoords) < 1.5 then wait = 5 if not textUI then textUI = true Utils.ShowTextUI('[E] ' .. locale('attach_rope')) end if IsControlJustPressed(0, 38) then Utils.HideTextUI() attachedVehicle = closestVehicle break end elseif textUI then Utils.HideTextUI() textUI = false end elseif textUI then Utils.HideTextUI() textUI = false end Citizen.Wait(wait) end ownedAnchorThread = false if not attachedVehicle then return end TriggerServerEvent(_e('server:rope:attachRopeWithVehicle'), ObjToNet(ownedFakeAtmObject), VehToNet(attachedVehicle)) deleteHookObject() Citizen.SetTimeout(10000, function() if not ownedFakeAtmObject or not DoesEntityExist(ownedFakeAtmObject) then return end local coords = GetEntityCoords(ownedFakeAtmObject) local model = GetEntityModel(ownedFakeAtmObject) TriggerServerEvent(_e('server:rope:onAtmRipped'), coords, model) SetEntityVisible(ownedFakeAtmObject, true) FreezeEntityPosition(ownedFakeAtmObject, false) SetObjectPhysicsParams(ownedFakeAtmObject, 170.0, -1.0, 30.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0) local forceDirection = vector3(2.0, 2.0, 2.0) ApplyForceToEntity(ownedAtm, 1, forceDirection.x, forceDirection.y, forceDirection.z, 0, 0, 0, true, true, true, true, false, true) end) end) end function addRobTargetToAtm() Target.AddLocalEntity(ownedFakeAtmObject, { { label = locale('collect'), icon = 'fa-solid fa-money-bill-1-wave', distance = 2.0, canInteract = function() return Entity(ownedFakeAtmObject) and Entity(ownedFakeAtmObject)?.state?.robbable end, onSelect = function() client.setBusy(false, 'rope.onAtmRobbed') Target.RemoveLocalEntity(ownedFakeAtmObject) Entity(ownedFakeAtmObject).state.robbable = false Entity(ownedFakeAtmObject).state.robbed = true TriggerServerEvent(_e('server:rope:DeletePlayerRope')) SetEntityAsNoLongerNeeded(ownedFakeAtmObject) ownedFakeAtmObject = nil lib.playAnim(cache.ped, 'amb@medic@standing@tendtodead@idle_a', 'idle_a') lib.progressBar({ duration = 3500, position = 'bottom', label = locale('collect'), useWhileDead = false, canCancel = false, disable = { car = true, move = true, combat = true, sprint = true }, }) ClearPedTasks(ped) TriggerServerEvent(_e('server:GiveItem'), Config.Rope.reward.name, math.random(Config.Rope.reward.min, Config.Rope.reward.max)) end } }) end --[[ functions ]] function Rope.CreateAtmRope(_, model) Utils.debug('Rope.CreateAtmRope | Selected ATM Model: ', model) local pedCoords = GetEntityCoords(cache.ped) local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, 2.0, model, false, false, false) if not DoesEntityExist(entity) then return end local atmCoords = GetEntityCoords(entity) local atmRotation = GetEntityRotation(entity) local atmModel = GetEntityModel(entity) if Entity(entity).state.robbed then return Utils.Notify(locale('atm_can_not_be_rob'), 'error') end if lib.callback.await(_e('server:IsAtmHacked'), false, atmModel, atmCoords) then return Utils.Notify(locale('atm_can_not_be_hack'), 'error') end if not lib.callback.await(_e('server:HasItem'), false, Config.Rope.requiredItem.name) then return Utils.Notify(locale('need_item', Config.Rope.requiredItem.label), 'error') end Utils.TriggerPoliceAlert(atmCoords) client.setHeadingToObject(atmModel) client.setBusy(true, 'rope.createatmrope') lib.playAnim(cache.ped, 'missmechanic', 'work2_base', nil, nil, nil, 1) Citizen.Wait(2500) ClearPedTasks(cache.ped) giveHookToPedHand() ownedFakeAtmObject = Utils.CreateObject(atmModel, atmCoords, atmRotation, true, true, false) Entity(ownedFakeAtmObject).state.isFakeAtm = true SetEntityVisible(ownedFakeAtmObject, false) local netPedId = PedToNet(cache.ped) local netFakeAtmEntity = ObjToNet(ownedFakeAtmObject) TriggerServerEvent(_e('server:rope:AttachAtmRopeToPed'), netPedId, netFakeAtmEntity) TriggerServerEvent(_e('server:RemoveItem'), Config.Rope.requiredItem.name) onOwnedAnchorCreated() addRobTargetToAtm() end function Rope.Clear() if ownedFakeAtmObject then TriggerServerEvent(_e('server:rope:DeletePlayerRope')) end deleteHookObject() deleteFakeAtmEntity() deletePlayerRopes() ownedAnchorThread = nil end --[[ events ]] RegisterNetEvent(_e('client:rope:AttachAtmRopeToPed'), function(netPedId, netFakeAtmEntity, owner) if not Utils.WaitForEntityWithNetworkId(netPedId) then return end if not Utils.WaitForEntityWithNetworkId(netFakeAtmEntity) then return end local targetPedId = NetToPed(netPedId) local targetAtmEntity = NetToObj(netFakeAtmEntity) if not DoesEntityExist(targetPedId) then return end if not DoesEntityExist(targetAtmEntity) then return end ClearPedTasksImmediately(cache.ped) Citizen.Wait(100) local rope = addRopeToPlayer(owner, 'ped') local targetAtmOffset = GetOffsetFromEntityInWorldCoords(targetAtmEntity, 0.0, 0.0, 1.0) local pedBoneCoords = GetPedBoneCoords(targetPedId, 6286, 0.0, 0.0, 0.0) attachRopeToObject(rope, targetAtmEntity, targetPedId, targetAtmOffset, pedBoneCoords) Citizen.CreateThread(function() while DoesPlayerRopeExist(owner, 'ped') and DoesEntityExist(targetAtmEntity) and DoesEntityExist(targetPedId) do targetAtmOffset = GetOffsetFromEntityInWorldCoords(targetAtmEntity, 0.0, 0.0, 1.0) pedBoneCoords = GetPedBoneCoords(targetPedId, 6286, 0.0, 0.0, 0.0) DetachEntity(targetAtmEntity) DetachEntity(targetPedId) attachRopeToObject(rope, targetAtmEntity, targetPedId, targetAtmOffset, pedBoneCoords) Citizen.Wait(64) end end) end) RegisterNetEvent(_e('client:rope:attachRopeWithVehicle'), function(netFakeAtmEntity, netAttachedVehicle, owner) deletePlayerRope(owner) if not Utils.WaitForEntityWithNetworkId(netFakeAtmEntity) then return end if not Utils.WaitForEntityWithNetworkId(netAttachedVehicle) then return end local targetAtmEntity = NetToObj(netFakeAtmEntity) local targetVehicleEntity = NetToVeh(netAttachedVehicle) if not DoesEntityExist(targetAtmEntity) then return end if not DoesEntityExist(targetVehicleEntity) then return end local targetAtmOffset = GetOffsetFromEntityInWorldCoords(targetAtmEntity, 0, 0.0, 1.0) local targetVehicleOffset = GetOffsetFromEntityInWorldCoords(targetVehicleEntity, 0, -2.1, -.2) local rope = addRopeToPlayer(owner, 'vehicle') attachRopeToObject(rope, targetAtmEntity, targetVehicleEntity, targetAtmOffset, targetVehicleOffset) end) RegisterNetEvent(_e('client:rope:onAtmRipped'), function(coords, model, owner) local nearByObjects = lib.getNearbyObjects(coords, 0.3) for _, v in ipairs(nearByObjects) do if GetEntityModel(v.object) == model and not Entity(v.object).state.isFakeAtm then SetEntityAsMissionEntity(v.object, true, true) DeleteEntity(v.object) break end end if owner == cache.serverId then Utils.Notify(locale('sec_active', Config.Rope.breakTime), 'inform', 5000) Citizen.SetTimeout(Config.Rope.breakTime * 1000, function() Utils.Notify(locale('sec_deactive'), 'success', 5000) Entity(ownedFakeAtmObject).state.robbable = true end) end end) RegisterNetEvent(_e('client:rope:DeletePlayerRope'), function(owner) deletePlayerRope(owner) end)