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)
 |