373 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			373 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --[[ 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)
 | 
