forked from Simnation/Main
896 lines
27 KiB
Lua
896 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)
|