895 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			895 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
-- You probably shouldn't touch these.
 | 
						|
IsInAnimation = false
 | 
						|
CurrentAnimationName = nil
 | 
						|
CurrentTextureVariation = nil
 | 
						|
InHandsup = false
 | 
						|
CONVERTED = false
 | 
						|
 | 
						|
local ChosenDict = ""
 | 
						|
local CurrentAnimOptions = false
 | 
						|
local PlayerGender = "male"
 | 
						|
local PlayerProps = {}
 | 
						|
local PreviewPedProps = {}
 | 
						|
local PtfxNotif = false
 | 
						|
local PtfxPrompt = false
 | 
						|
local AnimationThreadStatus = false
 | 
						|
local CheckStatus = false
 | 
						|
local CanCancel = true
 | 
						|
local InExitEmote = false
 | 
						|
local ExitAndPlay = false
 | 
						|
local EmoteCancelPlaying = false
 | 
						|
local currentEmote = {}
 | 
						|
local attachedProp
 | 
						|
local scenarioObjects = {
 | 
						|
    `p_amb_coffeecup_01`,
 | 
						|
    `p_amb_joint_01`,
 | 
						|
    `p_cs_ciggy_01`,
 | 
						|
    `p_cs_ciggy_01b_s`,
 | 
						|
    `p_cs_clipboard`,
 | 
						|
    `prop_curl_bar_01`,
 | 
						|
    `p_cs_joint_01`,
 | 
						|
    `p_cs_joint_02`,
 | 
						|
    `prop_acc_guitar_01`,
 | 
						|
    `prop_amb_ciggy_01`,
 | 
						|
    `prop_amb_phone`,
 | 
						|
    `prop_beggers_sign_01`,
 | 
						|
    `prop_beggers_sign_02`,
 | 
						|
    `prop_beggers_sign_03`,
 | 
						|
    `prop_beggers_sign_04`,
 | 
						|
    `prop_bongos_01`,
 | 
						|
    `prop_cigar_01`,
 | 
						|
    `prop_cigar_02`,
 | 
						|
    `prop_cigar_03`,
 | 
						|
    `prop_cs_beer_bot_40oz_02`,
 | 
						|
    `prop_cs_paper_cup`,
 | 
						|
    `prop_cs_trowel`,
 | 
						|
    `prop_fib_clipboard`,
 | 
						|
    `prop_fish_slice_01`,
 | 
						|
    `prop_fishing_rod_01`,
 | 
						|
    `prop_fishing_rod_02`,
 | 
						|
    `prop_notepad_02`,
 | 
						|
    `prop_parking_wand_01`,
 | 
						|
    `prop_rag_01`,
 | 
						|
    `prop_scn_police_torch`,
 | 
						|
    `prop_sh_cigar_01`,
 | 
						|
    `prop_sh_joint_01`,
 | 
						|
    `prop_tool_broom`,
 | 
						|
    `prop_tool_hammer`,
 | 
						|
    `prop_tool_jackham`,
 | 
						|
    `prop_tennis_rack_01`,
 | 
						|
    `prop_weld_torch`,
 | 
						|
    `w_me_gclub`,
 | 
						|
    `p_amb_clipboard_01`
 | 
						|
}
 | 
						|
 | 
						|
if not Config.AnimalEmotesEnabled then
 | 
						|
    RP.AnimalEmotes = {}
 | 
						|
end
 | 
						|
 | 
						|
CreateThread(function()
 | 
						|
    LocalPlayer.state:set('canEmote', true, true)
 | 
						|
end)
 | 
						|
 | 
						|
local function RunAnimationThread()
 | 
						|
    local pPed = PlayerPedId()
 | 
						|
    if AnimationThreadStatus then return end
 | 
						|
    AnimationThreadStatus = true
 | 
						|
    CreateThread(function()
 | 
						|
        local sleep
 | 
						|
        while AnimationThreadStatus and (IsInAnimation or PtfxPrompt) do
 | 
						|
            sleep = 500
 | 
						|
 | 
						|
            if IsInAnimation then
 | 
						|
                sleep = 0
 | 
						|
                if IsPlayerAiming(pPed) then
 | 
						|
                    EmoteCancel()
 | 
						|
                end
 | 
						|
                if not Config.AllowPunchingDuringEmote then
 | 
						|
                    DisableControlAction(2, 140, true)
 | 
						|
                    DisableControlAction(2, 141, true)
 | 
						|
                    DisableControlAction(2, 142, true)
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            if PtfxPrompt and CurrentAnimOptions then
 | 
						|
                sleep = 0
 | 
						|
                if not PtfxNotif then
 | 
						|
                    SimpleNotify(CurrentAnimOptions.PtfxInfo or Translate('ptfxinfo'))
 | 
						|
                    PtfxNotif = true
 | 
						|
                end
 | 
						|
                if IsControlPressed(0, 47) then
 | 
						|
                    PtfxStart()
 | 
						|
                    Wait(CurrentAnimOptions.PtfxWait)
 | 
						|
                    if CurrentAnimOptions.PtfxCanHold then
 | 
						|
                        while IsControlPressed(0, 47) and IsInAnimation and AnimationThreadStatus do
 | 
						|
                            Wait(5)
 | 
						|
                        end
 | 
						|
                    end
 | 
						|
                    PtfxStop()
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            Wait(sleep)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function CheckStatusThread(dict, anim)
 | 
						|
    CreateThread(function()
 | 
						|
        if CheckStatus then
 | 
						|
            CheckStatus = false
 | 
						|
            Wait(10)
 | 
						|
        end
 | 
						|
        CheckStatus = true
 | 
						|
        while not IsEntityPlayingAnim(PlayerPedId(), dict, anim, 3) do
 | 
						|
            Wait(5)
 | 
						|
        end
 | 
						|
        while CheckStatus and IsInAnimation do
 | 
						|
            if not IsEntityPlayingAnim(PlayerPedId(), dict, anim, 3) then
 | 
						|
                DebugPrint("Animation ended")
 | 
						|
                DestroyAllProps()
 | 
						|
                EmoteCancel()
 | 
						|
                break
 | 
						|
            end
 | 
						|
            Wait(0)
 | 
						|
        end
 | 
						|
    end)
 | 
						|
end
 | 
						|
 | 
						|
local function cleanScenarioObjects(isClone)
 | 
						|
    local ped = isClone and ClonedPed or PlayerPedId()
 | 
						|
    local playerCoords = GetEntityCoords(ped)
 | 
						|
 | 
						|
    for i = 1, #scenarioObjects do
 | 
						|
        local deleteScenarioObject = GetClosestObjectOfType(playerCoords.x, playerCoords.y, playerCoords.z, 1.0,
 | 
						|
            scenarioObjects[i], false, true, true)
 | 
						|
        if DoesEntityExist(deleteScenarioObject) then
 | 
						|
            SetEntityAsMissionEntity(deleteScenarioObject, false, false)
 | 
						|
            DeleteObject(deleteScenarioObject)
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
function EmoteCancel(force)
 | 
						|
    LocalPlayer.state:set('currentEmote', nil, true)
 | 
						|
    EmoteCancelPlaying = true
 | 
						|
 | 
						|
    if InExitEmote then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if not CanCancel and not force then return end
 | 
						|
 | 
						|
    if ChosenDict == "MaleScenario" and IsInAnimation then
 | 
						|
        ClearPedTasksImmediately(PlayerPedId())
 | 
						|
        IsInAnimation = false
 | 
						|
        DebugPrint("Forced scenario exit")
 | 
						|
    elseif ChosenDict == "Scenario" and IsInAnimation then
 | 
						|
        ClearPedTasksImmediately(PlayerPedId())
 | 
						|
        IsInAnimation = false
 | 
						|
        DebugPrint("Forced scenario exit")
 | 
						|
    end
 | 
						|
 | 
						|
    PtfxNotif = false
 | 
						|
    PtfxPrompt = false
 | 
						|
    Pointing = false
 | 
						|
 | 
						|
    if IsInAnimation then
 | 
						|
        local ped = PlayerPedId()
 | 
						|
        if LocalPlayer.state.ptfx then
 | 
						|
            PtfxStop()
 | 
						|
        end
 | 
						|
        DetachEntity(ped, true, false)
 | 
						|
        CancelSharedEmote()
 | 
						|
 | 
						|
        if CurrentAnimOptions and CurrentAnimOptions.ExitEmote then
 | 
						|
            local options = CurrentAnimOptions
 | 
						|
            local ExitEmoteType = options.ExitEmoteType or "Emotes"
 | 
						|
 | 
						|
            if not RP[options.ExitEmote] then
 | 
						|
                DebugPrint("Exit emote was invalid")
 | 
						|
                IsInAnimation = false
 | 
						|
                ClearPedTasks(ped)
 | 
						|
                return
 | 
						|
            end
 | 
						|
 | 
						|
            OnEmotePlay(options.ExitEmote)
 | 
						|
            DebugPrint("Playing exit animation")
 | 
						|
 | 
						|
            local animationOptions = RP[options.ExitEmote].AnimationOptions
 | 
						|
            if animationOptions and animationOptions.EmoteDuration then
 | 
						|
                InExitEmote = true
 | 
						|
                SetTimeout(animationOptions.EmoteDuration, function()
 | 
						|
                    InExitEmote = false
 | 
						|
                    DestroyAllProps()
 | 
						|
                    ClearPedTasks(ped)
 | 
						|
                    EmoteCancelPlaying = false
 | 
						|
                end)
 | 
						|
                return
 | 
						|
            end
 | 
						|
        else
 | 
						|
            IsInAnimation = false
 | 
						|
            ClearPedTasks(ped)
 | 
						|
            EmoteCancelPlaying = false
 | 
						|
        end
 | 
						|
        DestroyAllProps()
 | 
						|
    end
 | 
						|
    cleanScenarioObjects(false)
 | 
						|
    AnimationThreadStatus = false
 | 
						|
    CheckStatus = false
 | 
						|
end
 | 
						|
 | 
						|
function EmoteMenuStart(name, category, textureVariation)
 | 
						|
    local emote = RP[name]
 | 
						|
 | 
						|
    if not emote then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if emote.category ~= category then
 | 
						|
        DebugPrint("Emote category mismatch : " .. emote.category .. " vs " .. category)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if category == "Expressions" then
 | 
						|
        SetPlayerPedExpression(name, true)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if emote.category == "AnimalEmotes" then
 | 
						|
        CheckAnimalAndOnEmotePlay(name)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    OnEmotePlay(name, textureVariation)
 | 
						|
end
 | 
						|
 | 
						|
function EmoteMenuStartClone(name, category)
 | 
						|
    if not Config.PreviewPed then return end
 | 
						|
    if not DoesEntityExist(ClonedPed) then return end
 | 
						|
 | 
						|
    local emote = RP[name]
 | 
						|
 | 
						|
    if not emote then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if emote.category ~= category then
 | 
						|
        DebugPrint("Emote category mismatch : " .. emote.category .. " vs " .. category)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if category == "Expressions" then
 | 
						|
        SetFacialIdleAnimOverride(ClonedPed, emote[1], true)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    OnEmotePlayClone(name)
 | 
						|
end
 | 
						|
 | 
						|
function EmoteCommandStart(args)
 | 
						|
    if #args > 0 then
 | 
						|
        if IsEntityDead(PlayerPedId()) or IsPedRagdoll(PlayerPedId()) or IsPedGettingUp(PlayerPedId()) or IsPedInMeleeCombat(PlayerPedId()) then
 | 
						|
            TriggerEvent('chat:addMessage', {
 | 
						|
                color = { 255, 0, 0 },
 | 
						|
                multiline = true,
 | 
						|
                args = { "RPEmotes", Translate('dead') }
 | 
						|
            })
 | 
						|
            return
 | 
						|
        end
 | 
						|
        if (IsPedSwimming(PlayerPedId()) or IsPedSwimmingUnderWater(PlayerPedId())) and not Config.AllowInWater then
 | 
						|
            TriggerEvent('chat:addMessage', {
 | 
						|
                color = { 255, 0, 0 },
 | 
						|
                multiline = true,
 | 
						|
                args = { "RPEmotes", Translate('swimming') }
 | 
						|
            })
 | 
						|
            return
 | 
						|
        end
 | 
						|
        local name = string.lower(args[1])
 | 
						|
        if name == "c" then
 | 
						|
            if IsInAnimation then
 | 
						|
                EmoteCancel()
 | 
						|
            else
 | 
						|
                EmoteChatMessage(Translate('nocancel'))
 | 
						|
            end
 | 
						|
            return
 | 
						|
        end
 | 
						|
 | 
						|
        local emote = RP[name]
 | 
						|
        if emote then
 | 
						|
            if emote.category == "AnimalEmotes" then
 | 
						|
                if Config.AnimalEmotesEnabled then
 | 
						|
                    CheckAnimalAndOnEmotePlay(name)
 | 
						|
                else
 | 
						|
                    EmoteChatMessage(Translate('animaldisabled'))
 | 
						|
                end
 | 
						|
                return
 | 
						|
            end
 | 
						|
 | 
						|
            if emote.category == "PropEmotes" and emote.AnimationOptions.PropTextureVariations then
 | 
						|
                if #args > 1 then
 | 
						|
                    local textureVariation = tonumber(args[2])
 | 
						|
                    if emote.AnimationOptions.PropTextureVariations[textureVariation] then
 | 
						|
                        OnEmotePlay(name, textureVariation - 1)
 | 
						|
                        return
 | 
						|
                    else
 | 
						|
                        local str = ""
 | 
						|
                        for k, v in ipairs(emote.AnimationOptions.PropTextureVariations) do
 | 
						|
                            str = str .. string.format("\n(%s) - %s", k, v.Name)
 | 
						|
                        end
 | 
						|
 | 
						|
                        EmoteChatMessage(string.format(Translate('invalidvariation'), str), true)
 | 
						|
                        OnEmotePlay(name, 0)
 | 
						|
                        return
 | 
						|
                    end
 | 
						|
                end
 | 
						|
            end
 | 
						|
 | 
						|
            OnEmotePlay(name)
 | 
						|
        else
 | 
						|
            EmoteChatMessage("'" .. name .. "' " .. Translate('notvalidemote') .. "")
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
function CheckAnimalAndOnEmotePlay(name)
 | 
						|
    local playerPed = PlayerPedId()
 | 
						|
    local isValidPet = false
 | 
						|
 | 
						|
    if string.sub(name, 1, 4) == "bdog" then
 | 
						|
        for _, model in ipairs(BigDogs) do
 | 
						|
            if IsPedModel(playerPed, GetHashKey(model)) then
 | 
						|
                isValidPet = true
 | 
						|
                break
 | 
						|
            end
 | 
						|
        end
 | 
						|
    elseif string.sub(name, 1, 4) == "sdog" then
 | 
						|
        for _, model in ipairs(SmallDogs) do
 | 
						|
            if IsPedModel(playerPed, GetHashKey(model)) then
 | 
						|
                isValidPet = true
 | 
						|
                break
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if isValidPet then
 | 
						|
        OnEmotePlay(name)
 | 
						|
    else
 | 
						|
        EmoteChatMessage(Translate('notvalidpet'))
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
---@param isClone? boolean
 | 
						|
function DestroyAllProps(isClone)
 | 
						|
    if isClone then
 | 
						|
        for _, v in pairs(PreviewPedProps) do
 | 
						|
            DeleteEntity(v)
 | 
						|
        end
 | 
						|
        PreviewPedProps = {}
 | 
						|
    else
 | 
						|
        for _, v in pairs(PlayerProps) do
 | 
						|
            DeleteEntity(v)
 | 
						|
        end
 | 
						|
        PlayerProps = {}
 | 
						|
    end
 | 
						|
    DebugPrint("Destroyed Props for " .. (isClone and "clone" or "player"))
 | 
						|
end
 | 
						|
 | 
						|
---@param data table
 | 
						|
---@return boolean
 | 
						|
function AddProp(data)
 | 
						|
    assert(data.prop1, 'no prop1 passed')
 | 
						|
    assert(data.bone, 'no bone passed')
 | 
						|
    data.off1 = data.off1 or 0.0
 | 
						|
    data.off2 = data.off2 or 0.0
 | 
						|
    data.off3 = data.off3 or 0.0
 | 
						|
    data.rot1 = data.rot1 or 0.0
 | 
						|
    data.rot2 = data.rot2 or 0.0
 | 
						|
    data.rot3 = data.rot3 or 0.0
 | 
						|
    assert(data.noCollision == nil or type(data.noCollision) == "boolean", 'noCollision must be a boolean')
 | 
						|
 | 
						|
    local target = data.isClone and ClonedPed or PlayerPedId()
 | 
						|
    local x, y, z = table.unpack(GetEntityCoords(target))
 | 
						|
 | 
						|
    if not IsModelValid(data.prop1) then
 | 
						|
        DebugPrint(tostring(data.prop1) .. " is not a valid model!")
 | 
						|
        return false
 | 
						|
    end
 | 
						|
 | 
						|
    LoadPropDict(data.prop1)
 | 
						|
 | 
						|
    attachedProp = CreateObject(GetHashKey(data.prop1), x, y, z + 0.2, not data.isClone, true, true)
 | 
						|
 | 
						|
    if data.textureVariation ~= nil then
 | 
						|
        SetObjectTextureVariation(attachedProp, data.textureVariation)
 | 
						|
    end
 | 
						|
 | 
						|
    if data.noCollision then
 | 
						|
        SetEntityCollision(attachedProp, false, false)
 | 
						|
    end
 | 
						|
 | 
						|
    AttachEntityToEntity(attachedProp, target, GetPedBoneIndex(target, data.bone), data.off1, data.off2, data.off3, data.rot1, data.rot2, data.rot3,
 | 
						|
        true, true, false, true, 1, true)
 | 
						|
 | 
						|
    if data.isClone then
 | 
						|
        table.insert(PreviewPedProps, attachedProp)
 | 
						|
    else
 | 
						|
        table.insert(PlayerProps, attachedProp)
 | 
						|
    end
 | 
						|
 | 
						|
    SetModelAsNoLongerNeeded(data.prop1)
 | 
						|
    DebugPrint("Added prop to " .. (data.isClone and "clone" or "player"))
 | 
						|
    return true
 | 
						|
end
 | 
						|
 | 
						|
function CheckGender()
 | 
						|
    PlayerGender = "male"
 | 
						|
 | 
						|
    if GetEntityModel(PlayerPedId()) == GetHashKey("mp_f_freemode_01") then
 | 
						|
        PlayerGender = "female"
 | 
						|
    end
 | 
						|
 | 
						|
    DebugPrint("Set gender to " .. PlayerGender)
 | 
						|
end
 | 
						|
 | 
						|
RegisterNetEvent('animations:ToggleCanDoAnims', function(value)
 | 
						|
    LocalPlayer.state:set('canEmote', value, true)
 | 
						|
end)
 | 
						|
 | 
						|
function OnEmotePlay(name, textureVariation)
 | 
						|
    local emoteData = RP[name]
 | 
						|
    if not emoteData then
 | 
						|
        EmoteChatMessage("'" .. name .. "' " .. Translate('notvalidemote') .. "")
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if not LocalPlayer.state.canEmote then return end
 | 
						|
 | 
						|
    if not DoesEntityExist(PlayerPedId()) then
 | 
						|
        return false
 | 
						|
    end
 | 
						|
 | 
						|
    cleanScenarioObjects(false)
 | 
						|
 | 
						|
    InVehicle = IsPedInAnyVehicle(PlayerPedId(), true)
 | 
						|
    Pointing = false
 | 
						|
 | 
						|
    if not Config.AllowEmoteInVehicle and InVehicle then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.AdultEmotesDisabled and emoteData.AdultAnimation then
 | 
						|
        return EmoteChatMessage(Translate('adultemotedisabled'))
 | 
						|
    end
 | 
						|
 | 
						|
    if InExitEmote then
 | 
						|
        return false
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.CancelPreviousEmote and IsInAnimation and not ExitAndPlay and not EmoteCancelPlaying then
 | 
						|
        ExitAndPlay = true
 | 
						|
        DebugPrint("Canceling previous emote and playing next emote")
 | 
						|
        PlayExitAndEnterEmote(name, textureVariation)
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
 | 
						|
    local animOption = emoteData.AnimationOptions
 | 
						|
    if InVehicle then
 | 
						|
        if animOption and animOption.NotInVehicle then
 | 
						|
            return EmoteChatMessage(Translate('not_in_a_vehicle'))
 | 
						|
        end
 | 
						|
    elseif animOption and animOption.onlyInVehicle then
 | 
						|
        return EmoteChatMessage(Translate('in_a_vehicle'))
 | 
						|
    end
 | 
						|
 | 
						|
    if CurrentAnimOptions and CurrentAnimOptions.ExitEmote and animOption and animOption.ExitEmote then
 | 
						|
        if not (animOption and CurrentAnimOptions.ExitEmote == animOption.ExitEmote) and RP[CurrentAnimOptions.ExitEmote][2] ~= emoteData[2] then
 | 
						|
            return
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if IsInActionWithErrorMessage() then
 | 
						|
        return false
 | 
						|
    end
 | 
						|
 | 
						|
    ChosenDict = emoteData[1]
 | 
						|
    local anim = emoteData[2]
 | 
						|
    CurrentAnimationName = name
 | 
						|
    LocalPlayer.state:set('currentEmote', name, true)
 | 
						|
    CurrentTextureVariation = textureVariation
 | 
						|
    CurrentAnimOptions = animOption
 | 
						|
 | 
						|
    if Config.DisarmPlayerOnEmote then
 | 
						|
        if IsPedArmed(PlayerPedId(), 7) then
 | 
						|
            SetCurrentPedWeapon(PlayerPedId(), GetHashKey('WEAPON_UNARMED'), true)
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if animOption and animOption.Prop then
 | 
						|
        DestroyAllProps()
 | 
						|
    end
 | 
						|
 | 
						|
    if ChosenDict == "MaleScenario" or ChosenDict == "Scenario" or ChosenDict == "ScenarioObject" then
 | 
						|
        if InVehicle then return end
 | 
						|
        CheckGender()
 | 
						|
        ClearPedTasks(PlayerPedId())
 | 
						|
        DestroyAllProps()
 | 
						|
        if ChosenDict == "MaleScenario" then
 | 
						|
            if PlayerGender == "male" then
 | 
						|
                TaskStartScenarioInPlace(PlayerPedId(), anim, 0, true)
 | 
						|
                DebugPrint("Playing scenario = (" .. anim .. ")")
 | 
						|
            else
 | 
						|
                EmoteCancel()
 | 
						|
                EmoteChatMessage(Translate('maleonly'))
 | 
						|
                return
 | 
						|
            end
 | 
						|
        elseif ChosenDict == "ScenarioObject" then
 | 
						|
            local BehindPlayer = GetOffsetFromEntityInWorldCoords(PlayerPedId(), 0.0, -0.5, -0.5)
 | 
						|
            TaskStartScenarioAtPosition(PlayerPedId(), anim, BehindPlayer.x, BehindPlayer.y, BehindPlayer.z, GetEntityHeading(PlayerPedId()), 0, true, false)
 | 
						|
            DebugPrint("Playing scenario = (" .. anim .. ")")
 | 
						|
        else
 | 
						|
            TaskStartScenarioInPlace(PlayerPedId(), anim, 0, true)
 | 
						|
            DebugPrint("Playing scenario = (" .. anim .. ")")
 | 
						|
        end
 | 
						|
        IsInAnimation = true
 | 
						|
        RunAnimationThread()
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    -- Small delay at the start
 | 
						|
    if animOption and animOption.StartDelay then
 | 
						|
        Wait(animOption.StartDelay)
 | 
						|
    end
 | 
						|
 | 
						|
    if not LoadAnim(ChosenDict) then
 | 
						|
        EmoteChatMessage("'" .. name .. "' " .. Translate('notvalidemote') .. "")
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local movementType = 0
 | 
						|
 | 
						|
    if InVehicle then
 | 
						|
        if animOption and animOption.FullBody then
 | 
						|
            movementType = 35
 | 
						|
        else
 | 
						|
            movementType = 51
 | 
						|
        end
 | 
						|
    elseif animOption then
 | 
						|
        if animOption.EmoteMoving then
 | 
						|
            movementType = 51
 | 
						|
        elseif animOption.EmoteLoop then
 | 
						|
            movementType = 1
 | 
						|
        elseif animOption.EmoteStuck then
 | 
						|
            movementType = 50
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    DebugPrint("Animation flag = (" .. movementType .. ")")
 | 
						|
 | 
						|
    if animOption then
 | 
						|
        if animOption.PtfxAsset then
 | 
						|
            Ptfx1, Ptfx2, Ptfx3, Ptfx4, Ptfx5, Ptfx6, PtfxScale = table.unpack(animOption.PtfxPlacement)
 | 
						|
            PtfxNotif = false
 | 
						|
            PtfxPrompt = true
 | 
						|
            RunAnimationThread()
 | 
						|
            TriggerServerEvent("rpemotes:ptfx:sync", animOption.PtfxAsset, animOption.PtfxName, vector3(Ptfx1, Ptfx2, Ptfx3),
 | 
						|
                vector3(Ptfx4, Ptfx5, Ptfx6), animOption.PtfxBone, PtfxScale, animOption.PtfxColor)
 | 
						|
        else
 | 
						|
            PtfxPrompt = false
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if IsPedUsingAnyScenario(PlayerPedId()) or IsPedActiveInScenario(PlayerPedId()) then
 | 
						|
        ClearPedTasksImmediately(PlayerPedId())
 | 
						|
    end
 | 
						|
 | 
						|
    TaskPlayAnim(PlayerPedId(), ChosenDict, anim, animOption?.BlendInSpeed or 5.0, animOption?.BlendOutSpeed or 5.0, animOption?.EmoteDuration or -1, animOption?.Flag or movementType, 0, false, false,
 | 
						|
        false)
 | 
						|
    RemoveAnimDict(ChosenDict)
 | 
						|
 | 
						|
    IsInAnimation = true
 | 
						|
    RunAnimationThread()
 | 
						|
 | 
						|
    if not (animOption and animOption.Prop) then
 | 
						|
        CheckStatusThread(ChosenDict, anim)
 | 
						|
    end
 | 
						|
 | 
						|
    local currentEmoteTable = emoteData
 | 
						|
    for _, tabledata in pairs(RP) do
 | 
						|
        for command, emotedata in pairs(tabledata) do
 | 
						|
            if emotedata == emoteData then
 | 
						|
                table.insert(currentEmoteTable, command)
 | 
						|
                break
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
    currentEmote = currentEmoteTable
 | 
						|
 | 
						|
    if animOption and animOption.Prop then
 | 
						|
        PropPl1, PropPl2, PropPl3, PropPl4, PropPl5, PropPl6 = table.unpack(animOption.PropPlacement)
 | 
						|
 | 
						|
        Wait(animOption and animOption.EmoteDuration or 0)
 | 
						|
 | 
						|
        if not AddProp({
 | 
						|
            prop1 = animOption.Prop,
 | 
						|
            bone = animOption.PropBone,
 | 
						|
            off1 = PropPl1, off2 = PropPl2, off3 = PropPl3,
 | 
						|
            rot1 = PropPl4, rot2 = PropPl5, rot3 = PropPl6,
 | 
						|
            textureVariation = textureVariation,
 | 
						|
            isClone = false,
 | 
						|
            noCollision = animOption.PropNoCollision
 | 
						|
        }) then return end
 | 
						|
 | 
						|
        if animOption.SecondProp then
 | 
						|
            SecondPropPl1, SecondPropPl2, SecondPropPl3, SecondPropPl4, SecondPropPl5, SecondPropPl6 = table.unpack(animOption.SecondPropPlacement)
 | 
						|
            if not AddProp({
 | 
						|
                prop1 = animOption.SecondProp,
 | 
						|
                bone = animOption.SecondPropBone,
 | 
						|
                off1 = SecondPropPl1, off2 = SecondPropPl2, off3 = SecondPropPl3,
 | 
						|
                rot1 = SecondPropPl4, rot2 = SecondPropPl5, rot3 = SecondPropPl6,
 | 
						|
                textureVariation = textureVariation,
 | 
						|
                isClone = false,
 | 
						|
                noCollision = animOption.SecondPropNoCollision
 | 
						|
            }) then
 | 
						|
                DestroyAllProps()
 | 
						|
                return
 | 
						|
            end
 | 
						|
        end
 | 
						|
 | 
						|
        -- Ptfx is on the prop, then we need to sync it
 | 
						|
        if not animOption then return end
 | 
						|
        if animOption.PtfxAsset and not animOption.PtfxNoProp then
 | 
						|
            TriggerServerEvent("rpemotes:ptfx:syncProp", ObjToNet(attachedProp))
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
function OnEmotePlayClone(name)
 | 
						|
    if not Config.PreviewPed then return end
 | 
						|
 | 
						|
    cleanScenarioObjects(true)
 | 
						|
 | 
						|
    if not DoesEntityExist(ClonedPed) then
 | 
						|
        return false
 | 
						|
    end
 | 
						|
 | 
						|
    if InExitEmote then
 | 
						|
        return false
 | 
						|
    end
 | 
						|
 | 
						|
    if Config.CancelPreviousEmote and not ExitAndPlay and not EmoteCancelPlaying then
 | 
						|
        ExitAndPlay = true
 | 
						|
        DebugPrint("Canceling previous emote and playing next emote")
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local emoteData = RP[name]
 | 
						|
    local animOption = emoteData.AnimationOptions
 | 
						|
 | 
						|
    local dict, anim = table.unpack(emoteData)
 | 
						|
 | 
						|
    if animOption and animOption.Prop then
 | 
						|
        DestroyAllProps(true)
 | 
						|
    end
 | 
						|
 | 
						|
    if dict == "MaleScenario" or dict == "Scenario" or dict == "ScenarioObject" then
 | 
						|
        CheckGender()
 | 
						|
        ClearPedTasks(ClonedPed)
 | 
						|
        DestroyAllProps(true)
 | 
						|
        if dict == "MaleScenario" then
 | 
						|
            if PlayerGender == "male" then
 | 
						|
                TaskStartScenarioInPlace(ClonedPed, anim, 0, true)
 | 
						|
            end
 | 
						|
        elseif dict == "ScenarioObject" then
 | 
						|
            local BehindPlayer = GetOffsetFromEntityInWorldCoords(ClonedPed, 0.0, -0.5, -0.5)
 | 
						|
            TaskStartScenarioAtPosition(ClonedPed, anim, BehindPlayer.x, BehindPlayer.y, BehindPlayer.z, GetEntityHeading(ClonedPed), 0, true, false)
 | 
						|
        elseif dict == "Scenario" then
 | 
						|
            TaskStartScenarioInPlace(ClonedPed, anim, 0, true)
 | 
						|
        end
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if not LoadAnim(dict) then
 | 
						|
        EmoteChatMessage("'" .. name .. "' " .. Translate('notvalidemote') .. "")
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    local movementType = 0
 | 
						|
 | 
						|
    if animOption then
 | 
						|
        if animOption.EmoteMoving then
 | 
						|
            movementType = 51
 | 
						|
        elseif animOption.EmoteLoop then
 | 
						|
            movementType = 1
 | 
						|
        elseif animOption.EmoteStuck then
 | 
						|
            movementType = 50
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    if IsPedUsingAnyScenario(ClonedPed) or IsPedActiveInScenario(ClonedPed) then
 | 
						|
        ClearPedTasksImmediately(ClonedPed)
 | 
						|
    end
 | 
						|
 | 
						|
    TaskPlayAnim(ClonedPed, dict, anim, 5.0, 5.0, animOption and animOption.EmoteDuration or -1, animOption?.Flag or movementType, 0, false, false, false)
 | 
						|
    RemoveAnimDict(dict)
 | 
						|
 | 
						|
    if animOption and animOption.Prop then
 | 
						|
        local PropPl1, PropPl2, PropPl3, PropPl4, PropPl5, PropPl6 = table.unpack(animOption.PropPlacement)
 | 
						|
 | 
						|
        Wait(animOption and animOption.EmoteDuration or 0)
 | 
						|
 | 
						|
        if not AddProp({
 | 
						|
            prop1 = animOption.Prop,
 | 
						|
            bone = animOption.PropBone,
 | 
						|
            off1 = PropPl1, off2 = PropPl2, off3 = PropPl3,
 | 
						|
            rot1 = PropPl4, rot2 = PropPl5, rot3 = PropPl6,
 | 
						|
            isClone = true,
 | 
						|
            noCollision = animOption.PropNoCollision
 | 
						|
        }) then return end
 | 
						|
 | 
						|
        if animOption.SecondProp then
 | 
						|
            local SecondPropPl1, SecondPropPl2, SecondPropPl3, SecondPropPl4, SecondPropPl5, SecondPropPl6 = table.unpack(animOption.SecondPropPlacement)
 | 
						|
 | 
						|
            if not AddProp({
 | 
						|
                prop1 = animOption.SecondProp,
 | 
						|
                bone = animOption.SecondPropBone,
 | 
						|
                off1 = SecondPropPl1, off2 = SecondPropPl2, off3 = SecondPropPl3,
 | 
						|
                rot1 = SecondPropPl4, rot2 = SecondPropPl5, rot3 = SecondPropPl6,
 | 
						|
                isClone = true,
 | 
						|
                noCollision = animOption.SecondPropNoCollision
 | 
						|
            }) then
 | 
						|
                DestroyAllProps(true)
 | 
						|
                return
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
function PlayExitAndEnterEmote(name, textureVariation)
 | 
						|
    local ped = PlayerPedId()
 | 
						|
    if not CanCancel then return end
 | 
						|
    if ChosenDict == "MaleScenario" and IsInAnimation then
 | 
						|
        ClearPedTasksImmediately(ped)
 | 
						|
        IsInAnimation = false
 | 
						|
        DebugPrint("Forced scenario exit")
 | 
						|
    elseif ChosenDict == "Scenario" and IsInAnimation then
 | 
						|
        ClearPedTasksImmediately(ped)
 | 
						|
        IsInAnimation = false
 | 
						|
        DebugPrint("Forced scenario exit")
 | 
						|
    end
 | 
						|
 | 
						|
    PtfxNotif = false
 | 
						|
    PtfxPrompt = false
 | 
						|
    Pointing = false
 | 
						|
 | 
						|
    if LocalPlayer.state.ptfx then
 | 
						|
        PtfxStop()
 | 
						|
    end
 | 
						|
    DetachEntity(ped, true, false)
 | 
						|
    CancelSharedEmote()
 | 
						|
 | 
						|
    if CurrentAnimOptions?.ExitEmote then
 | 
						|
        local options = CurrentAnimOptions or {}
 | 
						|
 | 
						|
        if not RP[options.ExitEmote] then
 | 
						|
            DebugPrint("Exit emote was invalid")
 | 
						|
            ClearPedTasks(ped)
 | 
						|
            IsInAnimation = false
 | 
						|
            return
 | 
						|
        end
 | 
						|
        OnEmotePlay(options.ExitEmote)
 | 
						|
        DebugPrint("Playing exit animation")
 | 
						|
 | 
						|
        local animationOptions = RP[options.ExitEmote].AnimationOptions
 | 
						|
        if animationOptions and animationOptions.EmoteDuration then
 | 
						|
            InExitEmote = true
 | 
						|
            SetTimeout(animationOptions.EmoteDuration, function()
 | 
						|
                InExitEmote = false
 | 
						|
                DestroyAllProps(true)
 | 
						|
                ClearPedTasks(ped)
 | 
						|
                OnEmotePlay(name, textureVariation)
 | 
						|
                ExitAndPlay = false
 | 
						|
            end)
 | 
						|
            return
 | 
						|
        end
 | 
						|
    else
 | 
						|
        ClearPedTasks(ped)
 | 
						|
        IsInAnimation = false
 | 
						|
        ExitAndPlay = false
 | 
						|
        DestroyAllProps(true)
 | 
						|
        OnEmotePlay(name, CurrentTextureVariation)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
RegisterNetEvent('animations:client:EmoteCommandStart', function(args)
 | 
						|
    EmoteCommandStart(args)
 | 
						|
end)
 | 
						|
 | 
						|
CreateExport("EmoteCommandStart", function(emoteName, textureVariation)
 | 
						|
    EmoteCommandStart({ emoteName, textureVariation })
 | 
						|
end)
 | 
						|
CreateExport("EmoteCancel", EmoteCancel)
 | 
						|
CreateExport("CanCancelEmote", function(State)
 | 
						|
    CanCancel = State == true
 | 
						|
end)
 | 
						|
CreateExport('IsPlayerInAnim', function()
 | 
						|
    return LocalPlayer.state.currentEmote
 | 
						|
end)
 | 
						|
CreateExport('getCurrentEmote', function()
 | 
						|
    return currentEmote
 | 
						|
end)
 | 
						|
 | 
						|
-- Door stuff
 | 
						|
local openingDoor = false
 | 
						|
AddEventHandler('CEventOpenDoor', function(unk1)
 | 
						|
    if unk1[1] ~= PlayerPedId() then return end
 | 
						|
    if ShowPed then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if not IsInAnimation then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if openingDoor then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    openingDoor = true
 | 
						|
 | 
						|
    while IsPedOpeningADoor(PlayerPedId()) do
 | 
						|
        Wait(100)
 | 
						|
    end
 | 
						|
 | 
						|
    openingDoor = false
 | 
						|
 | 
						|
    Wait(200)
 | 
						|
 | 
						|
    ClearPedTasks(PlayerPedId())
 | 
						|
    DestroyAllProps()
 | 
						|
    OnEmotePlay(CurrentAnimationName, CurrentTextureVariation)
 | 
						|
end)
 | 
						|
 | 
						|
local isBumpingPed = false
 | 
						|
local timeout = 500
 | 
						|
 | 
						|
AddEventHandler("CEventPlayerCollisionWithPed", function(unk1)
 | 
						|
    if unk1[1] ~= PlayerPedId() then return end
 | 
						|
    if not IsInAnimation then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    if isBumpingPed then
 | 
						|
        timeout = 500
 | 
						|
        return
 | 
						|
    end
 | 
						|
    isBumpingPed = true
 | 
						|
    timeout = 500
 | 
						|
    -- We wait a bit to avoid collision with the ped resetting the animation again
 | 
						|
 | 
						|
    while timeout > 0 do
 | 
						|
        Wait(100)
 | 
						|
        timeout = timeout - 100
 | 
						|
    end
 | 
						|
 | 
						|
    if not IsInAnimation then
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    isBumpingPed = false
 | 
						|
    ClearPedTasks(PlayerPedId())
 | 
						|
    Wait(125)
 | 
						|
    DestroyAllProps()
 | 
						|
    OnEmotePlay(CurrentAnimationName, CurrentTextureVariation)
 | 
						|
end)
 | 
						|
 | 
						|
AddEventHandler('onResourceStop', function(resource)
 | 
						|
    if resource ~= GetCurrentResourceName() then return end
 | 
						|
    local ped = PlayerPedId()
 | 
						|
    ClosePedMenu()
 | 
						|
    DestroyAllProps()
 | 
						|
    ClearPedTasksImmediately(ped)
 | 
						|
    DetachEntity(ped, true, false)
 | 
						|
    ResetPedMovementClipset(ped, 0.8)
 | 
						|
end)
 |