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