1153 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1153 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- You probably shouldn't touch these.
 | |
| local ChosenDict = ""
 | |
| local ChosenAnimOptions = false
 | |
| local PlayerGender = "male"
 | |
| local PlayerProps = {}
 | |
| local PlayerParticles = {}
 | |
| 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
 | |
| IsInAnimation = false
 | |
| CurrentAnimationName = nil
 | |
| CurrentTextureVariation = nil
 | |
| InHandsup = false
 | |
| 
 | |
| -- Remove emotes if needed
 | |
| 
 | |
| local emoteTypes = {
 | |
|     Shared = '🤼 ',
 | |
|     Dances = '',
 | |
|     AnimalEmotes = '🐶 ',
 | |
|     Emotes = '',
 | |
|     PropEmotes = '📦 '
 | |
| }
 | |
| 
 | |
| for emoteType, prefix in pairs(emoteTypes) do
 | |
|     for emoteName, emoteData in pairs(RP[emoteType]) do
 | |
|         if prefix ~= '' then
 | |
|             emoteData[3] = prefix..emoteData[3]
 | |
|         end
 | |
| 
 | |
|         local shouldRemove = false
 | |
| 
 | |
|         if Config.AdultEmotesDisabled and emoteData.AdultAnimation then
 | |
|             shouldRemove = true
 | |
|         elseif emoteData[1] and not ((emoteData[1] == 'Scenario') or (emoteData[1] == 'ScenarioObject') or (emoteData[1] == 'MaleScenario')) and not DoesAnimDictExist(emoteData[1]) then
 | |
|             shouldRemove = true
 | |
|         end
 | |
| 
 | |
|         if shouldRemove then
 | |
|             RP[emoteType][emoteName] = nil
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| if not Config.AnimalEmotesEnabled then
 | |
|     RP.AnimalEmotes = {}
 | |
| end
 | |
| 
 | |
| local function RunAnimationThread()
 | |
|     local playerId = 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(playerId) then
 | |
|                     EmoteCancel()
 | |
|                 end
 | |
|                 if not Config.AllowPunching then
 | |
|                     DisableControlAction(2, 140, true)
 | |
|                     DisableControlAction(2, 141, true)
 | |
|                     DisableControlAction(2, 142, true)
 | |
|                 end
 | |
|             end
 | |
| 
 | |
|             if PtfxPrompt and ChosenAnimOptions then
 | |
|                 sleep = 0
 | |
|                 if not PtfxNotif then
 | |
|                     SimpleNotify(ChosenAnimOptions.PtfxInfo)
 | |
|                     PtfxNotif = true
 | |
|                 end
 | |
|                 if IsControlPressed(0, 47) then
 | |
|                     PtfxStart()
 | |
|                     Wait(ChosenAnimOptions.PtfxWait)
 | |
|                     if ChosenAnimOptions.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
 | |
| 
 | |
| if Config.EnableCancelKeybind then
 | |
|     RegisterKeyMapping("emotecancel", Translate('register_cancel_emote'), "keyboard", Config.CancelEmoteKey)
 | |
| end
 | |
| 
 | |
| -----------------------------------------------------------------------------------------------------
 | |
| -- Commands / Events --------------------------------------------------------------------------------
 | |
| -----------------------------------------------------------------------------------------------------
 | |
| 
 | |
| CreateThread(function()
 | |
|     TriggerEvent('chat:addSuggestion', '/e', Translate('play_emote'),
 | |
|         { { name = "emotename",      help = Translate('help_command') },
 | |
|             { name = "texturevariation", help = Translate('help_variation') } })
 | |
|     TriggerEvent('chat:addSuggestion', '/emote', Translate('play_emote'),
 | |
|         { { name = "emotename",      help = Translate('help_command') },
 | |
|             { name = "texturevariation", help = Translate('help_variation') } })
 | |
|     if Config.Keybinding then
 | |
|         TriggerEvent('chat:addSuggestion', '/emotebind', Translate('link_emote_keybind'),
 | |
|             { { name = "key",     help = "num4, num5, num6, num7. num8, num9. Numpad 4-9!" },
 | |
|                 { name = "emotename", help = Translate('help_command') } })
 | |
|         TriggerEvent('chat:addSuggestion', '/emotebinds', Translate('show_emote_keybind'))
 | |
|         TriggerEvent('chat:addSuggestion', '/emotedelete', Translate('remove_emote_keybind'),
 | |
|             { { name = "key", help = "num4, num5, num6, num7. num8, num9. Numpad 4-9!" } })
 | |
|     end
 | |
|     TriggerEvent('chat:addSuggestion', '/emotemenu', Translate('open_menu_emote'))
 | |
|     TriggerEvent('chat:addSuggestion', '/emotes', Translate('show_list_emote'))
 | |
|     TriggerEvent('chat:addSuggestion', '/emotecancel', Translate('cancel_emote'))
 | |
| end)
 | |
| 
 | |
| RegisterCommand('e', function(source, args, raw) EmoteCommandStart(source, args, raw) end, false)
 | |
| RegisterCommand('emote', function(source, args, raw) EmoteCommandStart(source, args, raw) end, false)
 | |
| if Config.Keybinding then
 | |
|     RegisterCommand('emotebind', function(source, args, raw) EmoteBindStart(source, args, raw) end, false)
 | |
|     RegisterCommand('emotebinds', function(source, args, raw) ListKeybinds() end, false)
 | |
|     RegisterCommand('emotedelete', function(source, args) DeleteEmote(args) end, false)
 | |
| end
 | |
| if Config.MenuKeybindEnabled then
 | |
|     RegisterCommand('emoteui', function() OpenEmoteMenu() end, false)
 | |
|     RegisterKeyMapping("emoteui", Translate('register_open_menu'), "keyboard", Config.MenuKeybind)
 | |
| else
 | |
|     RegisterCommand('emotemenu', function() OpenEmoteMenu() end, false)
 | |
| end
 | |
| RegisterCommand('emotes', function() EmotesOnCommand() end, false)
 | |
| RegisterCommand('emotecancel', function() EmoteCancel() end, false)
 | |
| 
 | |
| local disableHandsupControls = {
 | |
|     [36] = true, -- INPUT_DUCK
 | |
|     [44] = true, -- INPUT_COVER
 | |
|     [53] = true, -- INPUT_WEAPON_SPECIAL
 | |
|     [54] = true, --  INPUT_WEAPON_SPECIAL_TWO
 | |
|     [59] = true, -- INPUT_VEH_MOVE_LR
 | |
|     [60] = true, -- INPUT_VEH_MOVE_UD
 | |
|     [61] = true, -- INPUT_VEH_MOVE_UP_ONLY
 | |
|     [62] = true, -- INPUT_VEH_MOVE_DOWN_ONLY
 | |
|     [63] = true, -- INPUT_VEH_MOVE_LEFT_ONLY
 | |
|     [64] = true, -- INPUT_VEH_MOVE_RIGHT_ONLY
 | |
|     [65] = true, -- INPUT_VEH_SPECIAL
 | |
|     [66] = true, --  INPUT_VEH_GUN_LR
 | |
|     [67] = true, -- INPUT_VEH_GUN_UD
 | |
|     [69] = true, -- INPUT_VEH_ATTACK
 | |
|     [70] = true, -- INPUT_VEH_ATTACK2
 | |
|     [71] = true, -- INPUT_VEH_ACCELERATE
 | |
|     [72] = true, -- INPUT_VEH_BRAKE
 | |
|     [73] = true, -- INPUT_VEH_DUCK
 | |
|     [74] = true, -- INPUT_VEH_HEADLIGHT
 | |
|     [77] = true, -- INPUT_VEH_HOTWIRE_LEFT
 | |
|     [78] = true, -- INPUT_VEH_HOTWIRE_RIGHT
 | |
|     [80] = true, --  INPUT_VEH_CIN_CAM
 | |
|     [86] = true,  -- INPUT_VEH_HORN
 | |
|     [91] = true, -- INPUT_VEH_PASSENGER_AIM
 | |
|     [102] = true, -- INPUT_VEH_JUMP
 | |
|     [104] = true, -- INPUT_VEH_SHUFFLE
 | |
|     [105] = true, -- INPUT_VEH_DROP_PROJECTILE
 | |
|     [136] = true, -- INPUT_VEH_PUSHBIKE_PEDAL
 | |
|     [137] = true, -- INPUT_VEH_PUSHBIKE_SPRINT
 | |
|     [139] = true, -- INPUT_VEH_PUSHBIKE_REAR_BRAKE
 | |
|     [140] = true, -- INPUT_MELEE_ATTACK_LIGHT
 | |
|     [141] = true, --  INPUT_MELEE_ATTACK_HEAVY
 | |
|     [142] = true, -- INPUT_MELEE_ATTACK_ALTERNATE
 | |
|     [143] = true, -- INPUT_MELEE_BLOCK
 | |
|     [337] = true, -- INPUT_VEH_HYDRAULICS_CONTROL_TOGGLE
 | |
|     [338] = true, -- INPUT_VEH_HYDRAULICS_CONTROL_LEFT
 | |
|     [339] = true, -- INPUT_VEH_HYDRAULICS_CONTROL_RIGHT
 | |
|     [340] = true, -- INPUT_VEH_HYDRAULICS_CONTROL_UP
 | |
|     [341] = true, --  INPUT_VEH_HYDRAULICS_CONTROL_DOWN
 | |
|     [342] = true, --  INPUT_VEH_HYDRAULICS_CONTROL_UD
 | |
|     [343] = true, --  INPUT_VEH_HYDRAULICS_CONTROL_LR
 | |
|     [351] = true, --  INPUT_VEH_ROCKET_BOOST
 | |
|     [354] = true, -- INPUT_VEH_BIKE_WINGS
 | |
|     [357] = true, -- INPUT_VEH_TRANSFORM
 | |
|     [345] = true, -- INPUT_VEH_MELEE_HOLD
 | |
|     [346] = true, -- INPUT_VEH_MELEE_LEFT
 | |
|     [347] = true, -- INPUT_VEH_MELEE_RIGHT
 | |
| }
 | |
| 
 | |
| local playerId = PlayerId()
 | |
| 
 | |
| local function HandsUpLoop()
 | |
|     CreateThread(function()
 | |
|         while InHandsup do
 | |
|             if disableHandsupControls then
 | |
|                 for control, state in pairs(disableHandsupControls) do
 | |
|                     DisableControlAction(0, control, state)
 | |
|                 end
 | |
|             end
 | |
| 
 | |
|             if IsPlayerAiming(playerId) then
 | |
|                 ClearPedSecondaryTask(PlayerPedId())
 | |
|                 CreateThread(function()
 | |
|                     Wait(350)
 | |
|                     InHandsup = false
 | |
|                 end)
 | |
|             end
 | |
| 
 | |
|             Wait(0)
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| if Config.HandsupEnabled then
 | |
|     local function ToggleHandsUp(commandType)
 | |
|         RegisterCommand(commandType, function()
 | |
|             if IsPedInAnyVehicle(PlayerPedId(), false) and not Config.HandsupKeybindInCarEnabled and not InHandsup then
 | |
|                 return
 | |
|             end
 | |
|             Handsup()
 | |
|         end, false)
 | |
|     end
 | |
| 
 | |
|     if Config.HoldToHandsUp then
 | |
|         ToggleHandsUp('+handsup')
 | |
|         ToggleHandsUp('-handsup')
 | |
|     else
 | |
|         ToggleHandsUp('handsup')
 | |
|     end
 | |
| 
 | |
|     function Handsup()
 | |
|         local playerPed = PlayerPedId()
 | |
|         if not IsPedHuman(playerPed) then
 | |
|             return
 | |
|         end
 | |
|         if IsInActionWithErrorMessage() then
 | |
|             return
 | |
|         end
 | |
| 
 | |
|         InHandsup = not InHandsup
 | |
|         if InHandsup then
 | |
|             LocalPlayer.state:set('currentEmote', 'handsup', true)
 | |
|             DestroyAllProps()
 | |
|             local dict = "random@mugging3"
 | |
|             RequestAnimDict(dict)
 | |
|             while not HasAnimDictLoaded(dict) do
 | |
|                 Wait(0)
 | |
|             end
 | |
|             TaskPlayAnim(PlayerPedId(), dict, "handsup_standing_base", 3.0, 3.0, -1, 49, 0, false,
 | |
|                 IsThisModelABike(GetEntityModel(GetVehiclePedIsIn(PlayerPedId(), false))) and 4127 or false, false)
 | |
|             HandsUpLoop()
 | |
|         else
 | |
|             LocalPlayer.state:set('currentEmote', nil, true)
 | |
|             ClearPedSecondaryTask(PlayerPedId())
 | |
|             if Config.PersistentEmoteAfterHandsup and IsInAnimation then
 | |
|                 local emote = RP.Emotes[CurrentAnimationName] or RP.PropEmotes[CurrentAnimationName] or RP.Dances[CurrentAnimationName] or RP.AnimalEmotes[CurrentAnimationName]
 | |
|                 if not emote then
 | |
|                     return
 | |
|                 end
 | |
| 
 | |
|                 Wait(400)
 | |
|                 DestroyAllProps()
 | |
|                 OnEmotePlay(emote, CurrentAnimationName, CurrentTextureVariation)
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     TriggerEvent('chat:addSuggestion', '/handsup', Translate('handsup'))
 | |
| 
 | |
|     if Config.HandsupKeybindEnabled then
 | |
|         RegisterKeyMapping("handsup", Translate('register_handsup'), "keyboard", Config.HandsupKeybind)
 | |
|     end
 | |
| 
 | |
|     local function IsPlayerInHandsUp()
 | |
|         return InHandsup
 | |
|     end
 | |
| 
 | |
|     exports('IsPlayerInHandsUp', IsPlayerInHandsUp)
 | |
| end
 | |
| 
 | |
| AddEventHandler('onResourceStop', function(resource)
 | |
|     if resource == GetCurrentResourceName() then
 | |
|         local ped = PlayerPedId()
 | |
|         ClosePedMenu()
 | |
|         DestroyAllProps()
 | |
|         ClearPedTasksImmediately(ped)
 | |
|         DetachEntity(ped, true, false)
 | |
|         ResetPedMovementClipset(ped, 0.8)
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -----------------------------------------------------------------------------------------------------
 | |
| ------ Functions and stuff --------------------------------------------------------------------------
 | |
| -----------------------------------------------------------------------------------------------------
 | |
| 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`
 | |
| }
 | |
| 
 | |
| 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
 | |
|     -- Don't cancel if we are in an exit emote
 | |
|     if InExitEmote then
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local ped = PlayerPedId()
 | |
|     if not CanCancel and force ~= true 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 IsInAnimation then
 | |
|         if LocalPlayer.state.ptfx then
 | |
|             PtfxStop()
 | |
|         end
 | |
|         DetachEntity(ped, true, false)
 | |
|         CancelSharedEmote()
 | |
| 
 | |
|         if ChosenAnimOptions and ChosenAnimOptions.ExitEmote then
 | |
|             -- If the emote exit type is not specified, it defaults to Emotes
 | |
|             local options = ChosenAnimOptions
 | |
|             local ExitEmoteType = options.ExitEmoteType or "Emotes"
 | |
| 
 | |
|             -- Checks that the exit emote actually exists
 | |
|             if not RP[ExitEmoteType] or not RP[ExitEmoteType][options.ExitEmote] then
 | |
|                 DebugPrint("Exit emote was invalid")
 | |
|                 IsInAnimation = false
 | |
|                 ClearPedTasks(ped)
 | |
|                 return
 | |
|             end
 | |
|             OnEmotePlay(RP[ExitEmoteType][options.ExitEmote], ExitEmoteType)
 | |
|             DebugPrint("Playing exit animation")
 | |
| 
 | |
|             -- Check that the exit emote has a duration, and if so, set InExitEmote variable
 | |
|             local animationOptions = RP[ExitEmoteType][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 PtfxThis(asset)
 | |
|     while not HasNamedPtfxAssetLoaded(asset) do
 | |
|         RequestNamedPtfxAsset(asset)
 | |
|         Wait(10)
 | |
|     end
 | |
|     UseParticleFxAsset(asset)
 | |
| end
 | |
| 
 | |
| function PtfxStart()
 | |
|     LocalPlayer.state:set('ptfx', true, true)
 | |
| end
 | |
| 
 | |
| function PtfxStop()
 | |
|     LocalPlayer.state:set('ptfx', false, true)
 | |
| end
 | |
| 
 | |
| AddStateBagChangeHandler('ptfx', '', function(bagName, key, value, _unused, replicated)
 | |
|     local plyId = tonumber(bagName:gsub('player:', ''), 10)
 | |
| 
 | |
|     -- We stop here if we don't need to go further
 | |
|     -- We don't need to start or stop the ptfx twice
 | |
|     if (PlayerParticles[plyId] and value) or (not PlayerParticles[plyId] and not value) then return end
 | |
| 
 | |
|     -- Only allow ptfx change on players
 | |
|     local ply = GetPlayerFromServerId(plyId)
 | |
|     if ply == 0 then return end
 | |
| 
 | |
|     local plyPed = GetPlayerPed(ply)
 | |
|     if not DoesEntityExist(plyPed) then return end
 | |
| 
 | |
|     local stateBag = Player(plyId).state
 | |
| 
 | |
|     if value then
 | |
|         -- Start ptfx
 | |
| 
 | |
|         local asset = stateBag.ptfxAsset
 | |
|         local name = stateBag.ptfxName
 | |
|         local offset = stateBag.ptfxOffset
 | |
|         local rot = stateBag.ptfxRot
 | |
|         local boneIndex = stateBag.ptfxBone and GetPedBoneIndex(plyPed, stateBag.ptfxBone) or
 | |
|         GetEntityBoneIndexByName(name, "VFX")
 | |
|         local scale = stateBag.ptfxScale or 1
 | |
|         local color = stateBag.ptfxColor
 | |
|         local propNet = stateBag.ptfxPropNet
 | |
|         local entityTarget = plyPed
 | |
| 
 | |
|         if propNet then
 | |
|             local propObj = NetToObj(propNet)
 | |
|             if DoesEntityExist(propObj) then
 | |
|                 entityTarget = propObj
 | |
|             end
 | |
|         end
 | |
|         PtfxThis(asset)
 | |
|         PlayerParticles[plyId] = StartNetworkedParticleFxLoopedOnEntityBone(name, entityTarget, offset.x, offset.y,
 | |
|             offset.z, rot.x, rot.y, rot.z, boneIndex, scale + 0.0, false, false, false)
 | |
|         if color then
 | |
|             if color[1] and type(color[1]) == 'table' then
 | |
|                 local randomIndex = math.random(1, #color)
 | |
|                 color = color[randomIndex]
 | |
|             end
 | |
|             SetParticleFxLoopedAlpha(PlayerParticles[plyId], color.A)
 | |
|             SetParticleFxLoopedColour(PlayerParticles[plyId], color.R / 255, color.G / 255, color.B / 255, false)
 | |
|         end
 | |
|         DebugPrint("Started PTFX: " .. PlayerParticles[plyId])
 | |
|     else
 | |
|         DebugPrint("Stopped PTFX: " .. PlayerParticles[plyId])
 | |
|         StopParticleFxLooped(PlayerParticles[plyId], false)
 | |
|         RemoveNamedPtfxAsset(stateBag.ptfxAsset)
 | |
|         PlayerParticles[plyId] = nil
 | |
|     end
 | |
| end)
 | |
| 
 | |
| function EmotesOnCommand(source, args, raw)
 | |
|     local EmotesCommand = ""
 | |
|     for a in PairsByKeys(RP.Emotes) do
 | |
|         EmotesCommand = EmotesCommand .. "" .. a .. ", "
 | |
|     end
 | |
|     EmoteChatMessage(EmotesCommand)
 | |
|     EmoteChatMessage(Translate('emotemenucmd'))
 | |
| end
 | |
| 
 | |
| function EmoteMenuStart(name, category, textureVariation)
 | |
|     if category == "dances" then
 | |
|         if RP.Dances[name] ~= nil then
 | |
|             OnEmotePlay(RP.Dances[name], name)
 | |
|         end
 | |
|     elseif category == "animals" then
 | |
|         if RP.AnimalEmotes[name] ~= nil then
 | |
|             CheckAnimalAndOnEmotePlay(RP.AnimalEmotes[name], name)
 | |
|         end
 | |
|     elseif category == "props" then
 | |
|         if RP.PropEmotes[name] ~= nil then
 | |
|             OnEmotePlay(RP.PropEmotes[name], name, textureVariation)
 | |
|         end
 | |
|     elseif category == "emotes" then
 | |
|         if RP.Emotes[name] ~= nil then
 | |
|             OnEmotePlay(RP.Emotes[name], name)
 | |
|         end
 | |
|     elseif category == "expression" then
 | |
|         if RP.Expressions[name] ~= nil then
 | |
|             SetPlayerPedExpression(RP.Expressions[name][1], true)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function EmoteMenuStartClone(name, category)
 | |
|     if category == "dances" then
 | |
|         if RP.Dances[name] then
 | |
|             OnEmotePlayClone(RP.Dances[name])
 | |
|         end
 | |
|     elseif category == "props" then
 | |
|         if RP.PropEmotes[name] then
 | |
|             OnEmotePlayClone(RP.PropEmotes[name])
 | |
|         end
 | |
|     elseif category == "emotes" then
 | |
|         if RP.Emotes[name] then
 | |
|             OnEmotePlayClone(RP.Emotes[name])
 | |
|         end
 | |
|     elseif category == "expression" then
 | |
|         if RP.Expressions[name] then
 | |
|             SetFacialIdleAnimOverride(ClonedPed, RP.Expressions[name][1], 0)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function EmoteCommandStart(source, args, raw)
 | |
|     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
 | |
|         elseif name == "help" then
 | |
|             EmotesOnCommand()
 | |
|             return
 | |
|         end
 | |
| 
 | |
|         local emote = RP.Emotes[name] or RP.Dances[name] or RP.AnimalEmotes[name] or RP.PropEmotes[name] or RP.Expressions[name] or RP.Exits[name]
 | |
|         if emote then
 | |
|             if RP.AnimalEmotes[name] then
 | |
|                 if Config.AnimalEmotesEnabled then
 | |
|                     CheckAnimalAndOnEmotePlay(RP.AnimalEmotes[name], name)
 | |
|                 else
 | |
|                     EmoteChatMessage(Translate('animaldisabled'))
 | |
|                 end
 | |
|                 return
 | |
|             end
 | |
| 
 | |
|             if RP.PropEmotes[name] and RP.PropEmotes[name].AnimationOptions.PropTextureVariations then
 | |
|                 if #args > 1 then
 | |
|                     local textureVariation = tonumber(args[2])
 | |
|                     if (RP.PropEmotes[name].AnimationOptions.PropTextureVariations[textureVariation] ~= nil) then
 | |
|                         OnEmotePlay(RP.PropEmotes[name], name, textureVariation - 1)
 | |
|                         return
 | |
|                     else
 | |
|                         local str = ""
 | |
|                         for k, v in ipairs(RP.PropEmotes[name].AnimationOptions.PropTextureVariations) do
 | |
|                             str = str .. string.format("\n(%s) - %s", k, v.Name)
 | |
|                         end
 | |
| 
 | |
|                         EmoteChatMessage(string.format(Translate('invalidvariation'), str), true)
 | |
|                         OnEmotePlay(RP.PropEmotes[name], name, 0)
 | |
|                         return
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
| 
 | |
|             OnEmotePlay(emote, name)
 | |
|         else
 | |
|             EmoteChatMessage("'" .. name .. "' " .. Translate('notvalidemote') .. "")
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function CheckAnimalAndOnEmotePlay(emoteData, 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(emoteData, name)
 | |
|     else
 | |
|         EmoteChatMessage(Translate('notvalidpet'))
 | |
|     end
 | |
| end
 | |
| 
 | |
| ---@param isClone boolean | nil
 | |
| 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
 | |
| 
 | |
| function AddProp(prop1, bone, off1, off2, off3, rot1, rot2, rot3, textureVariation, isClone)
 | |
|     local target = isClone and ClonedPed or PlayerPedId()
 | |
| 
 | |
|     local x, y, z = table.unpack(GetEntityCoords(target))
 | |
| 
 | |
|     if not IsModelValid(prop1) then
 | |
|         DebugPrint(tostring(prop1) .. " is not a valid model!")
 | |
|         return false
 | |
|     end
 | |
| 
 | |
|     if not HasModelLoaded(prop1) then
 | |
|         LoadPropDict(prop1)
 | |
|     end
 | |
| 
 | |
| 
 | |
|     attachedProp = CreateObject(joaat(prop1), x, y, z + 0.2, not isClone, true, true)
 | |
| 
 | |
|     if textureVariation ~= nil then
 | |
|         SetObjectTextureVariation(attachedProp, textureVariation)
 | |
|     end
 | |
| 
 | |
| 
 | |
|     if isClone then
 | |
|         AttachEntityToEntity(attachedProp, target, GetPedBoneIndex(target, bone), off1, off2, off3, rot1, rot2, rot3,
 | |
|             true, true, false, true, 1, true)
 | |
|         table.insert(PreviewPedProps, attachedProp)
 | |
|     else
 | |
|         AttachEntityToEntity(attachedProp, target, GetPedBoneIndex(target, bone), off1, off2, off3, rot1, rot2, rot3,
 | |
|             true, true, false, true, 1, true)
 | |
|         table.insert(PlayerProps, attachedProp)
 | |
|     end
 | |
| 
 | |
| 
 | |
|     SetModelAsNoLongerNeeded(prop1)
 | |
|     DebugPrint("Added prop to " .. (isClone and "clone" or "player"))
 | |
|     return true
 | |
| end
 | |
| 
 | |
| function CheckGender()
 | |
|     local playerPed = PlayerPedId()
 | |
| 
 | |
|     if GetEntityModel(playerPed) == joaat("mp_f_freemode_01") then
 | |
|         PlayerGender = "female"
 | |
|     else
 | |
|         PlayerGender = "male"
 | |
|     end
 | |
| 
 | |
|     DebugPrint("Set gender as = (" .. PlayerGender .. ")")
 | |
| end
 | |
| 
 | |
| function OnEmotePlay(emoteData, name, textureVariation)
 | |
|     if not LocalPlayer.state.canEmote then return end
 | |
| 
 | |
|     cleanScenarioObjects(false)
 | |
| 
 | |
|     InVehicle = IsPedInAnyVehicle(PlayerPedId(), true)
 | |
|     Pointing = false
 | |
| 
 | |
|     if not Config.AllowedInCars and InVehicle then
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     if not DoesEntityExist(PlayerPedId()) then
 | |
|         return false
 | |
|     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(emoteData, 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 ChosenAnimOptions?.ExitEmote and animOption and animOption.ExitEmote then
 | |
|         if not (animOption and ChosenAnimOptions.ExitEmote == animOption.ExitEmote) and RP.Exits[ChosenAnimOptions.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
 | |
|     ChosenAnimOptions = animOption
 | |
| 
 | |
|     if Config.DisarmPlayer then
 | |
|         if IsPedArmed(PlayerPedId(), 7) then
 | |
|             SetCurrentPedWeapon(PlayerPedId(), joaat('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 -- Default movement type
 | |
| 
 | |
|     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, 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(animOption.Prop, animOption.PropBone, PropPl1, PropPl2, PropPl3, PropPl4, PropPl5, PropPl6, textureVariation, false) then return end
 | |
| 
 | |
|         if animOption.SecondProp then
 | |
|             SecondPropPl1, SecondPropPl2, SecondPropPl3, SecondPropPl4, SecondPropPl5, SecondPropPl6 = table.unpack(animOption.SecondPropPlacement)
 | |
|             if not AddProp(animOption.SecondProp, animOption.SecondPropBone, SecondPropPl1, SecondPropPl2, SecondPropPl3, SecondPropPl4, SecondPropPl5, SecondPropPl6, textureVariation, false) 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(emoteData)
 | |
|     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 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("'" .. ename .. "' " .. Translate('notvalidemote') .. "")
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local movementType = 0 -- Default movement type
 | |
| 
 | |
|     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, 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(animOption.Prop, animOption.PropBone, PropPl1, PropPl2, PropPl3, PropPl4, PropPl5, PropPl6, nil, true) then return end
 | |
| 
 | |
|         if animOption.SecondProp then
 | |
|             local SecondPropPl1, SecondPropPl2, SecondPropPl3, SecondPropPl4, SecondPropPl5, SecondPropPl6 = table.unpack(animOption.SecondPropPlacement)
 | |
| 
 | |
|             if not AddProp(animOption.SecondProp, animOption.SecondPropBone, SecondPropPl1, SecondPropPl2, SecondPropPl3, SecondPropPl4, SecondPropPl5, SecondPropPl6, nil, true) then
 | |
|                 DestroyAllProps(true)
 | |
|                 return
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function PlayExitAndEnterEmote(emoteName, 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 ChosenAnimOptions?.ExitEmote then
 | |
|         -- If the emote exit type is not spesifed it defaults to Emotes
 | |
|         local options = ChosenAnimOptions
 | |
|         local ExitEmoteType = options.ExitEmoteType or "Emotes"
 | |
| 
 | |
|         -- Checks that the exit emote actually exists
 | |
|         if not RP[ExitEmoteType] or not RP[ExitEmoteType][options.ExitEmote] then
 | |
|             DebugPrint("Exit emote was invalid")
 | |
|             ClearPedTasks(ped)
 | |
|             IsInAnimation = false
 | |
|             return
 | |
|         end
 | |
|         OnEmotePlay(RP[ExitEmoteType][options.ExitEmote], ExitEmoteType)
 | |
|         DebugPrint("Playing exit animation")
 | |
| 
 | |
|         -- Check that the exit emote has a duration, and if so, set InExitEmote variable
 | |
|         local animationOptions = RP[ExitEmoteType][options.ExitEmote].AnimationOptions
 | |
|         if animationOptions and animationOptions.EmoteDuration then
 | |
|             InExitEmote = true
 | |
|             SetTimeout(animationOptions.EmoteDuration, function()
 | |
|                 InExitEmote = false
 | |
|                 DestroyAllProps(true)
 | |
|                 ClearPedTasks(ped)
 | |
|                 OnEmotePlay(emoteName, name, textureVariation)
 | |
|                 ExitAndPlay = false
 | |
|             end)
 | |
|             return
 | |
|         end
 | |
|     else
 | |
|         ClearPedTasks(ped)
 | |
|         IsInAnimation = false
 | |
|         ExitAndPlay = false
 | |
|         DestroyAllProps(true)
 | |
|         OnEmotePlay(emoteName, name, CurrentTextureVariation)
 | |
|     end
 | |
| end
 | |
| 
 | |
| exports("EmoteCommandStart", function(emoteName, textureVariation)
 | |
|     EmoteCommandStart(nil, { emoteName, textureVariation }, nil)
 | |
| end)
 | |
| exports("EmoteCancel", EmoteCancel)
 | |
| exports("CanCancelEmote", function(State)
 | |
|     CanCancel = State == true
 | |
| end)
 | |
| exports('IsPlayerInAnim', function()
 | |
|     return LocalPlayer.state.currentEmote
 | |
| end)
 | |
| exports('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)
 | |
| 
 | |
|     local emote = RP.Emotes[CurrentAnimationName] or RP.PropEmotes[CurrentAnimationName] or RP.Dances[CurrentAnimationName] or RP.AnimalEmotes[CurrentAnimationName]
 | |
|     if not emote then
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     ClearPedTasks(PlayerPedId())
 | |
|     DestroyAllProps()
 | |
|     OnEmotePlay(emote, 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
 | |
| 
 | |
|     local emote = RP.Emotes[CurrentAnimationName] or RP.PropEmotes[CurrentAnimationName] or RP.Dances[CurrentAnimationName] or RP.AnimalEmotes[CurrentAnimationName]
 | |
|     if not emote then
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     isBumpingPed = false
 | |
|     ClearPedTasks(PlayerPedId())
 | |
|     Wait(125)
 | |
|     DestroyAllProps()
 | |
|     OnEmotePlay(emote, CurrentAnimationName, CurrentTextureVariation)
 | |
| end)
 | 
