lib.locale() KVP = require 'client.modules.kvp' Utils = require 'shared.modules.utils' Config = lib.load('shared.data.config') Walks = lib.load('shared.data.walks') Scenarios = lib.load('shared.data.scenarios') Expressions = lib.load('shared.data.expressions') Emotes = { lib.load('shared.data.emotes.general_emotes'), lib.load('shared.data.emotes.prop_emotes'), lib.load('shared.data.emotes.consumable_emotes'), lib.load('shared.data.emotes.dance_emotes'), lib.load('shared.data.emotes.synchronized_emotes'), lib.load('shared.data.emotes.synchronized_dance_emotes'), lib.load('shared.data.emotes.animal_emotes') } ExitEmotes = lib.load('shared.data.emotes.exit_emotes') EmoteBinds = KVP.getTable('keybinds') PlayerState = LocalPlayer.state local pedTypes = lib.load('client.data.ped_types') local scenarioModels = lib.load('client.data.scenario_models') local placement = require 'client.modules.placement' local keybinds = {} local registeredEmotes = {} local pedProps = {} local pedPtfx = {} ---Check if the player is in an emote ---@return boolean function IsInEmote() return PlayerState.isInEmote end exports('isInEmote', IsInEmote) ---Get the last emote a player used ---@return table?, number? function GetLastEmote() if not PlayerState.lastEmote then return end return PlayerState.lastEmote[1], PlayerState.lastEmote[2] end exports('getLastEmote', GetLastEmote) ---Play an emote or scenario ---@param data table ---@param variation? number function PlayEmote(data, variation) if PlayerState.isLimited then return end if data.Synchronized and not data.StartSynchronized then local coords = GetEntityCoords(cache.ped) local targetId = lib.getClosestPlayer(coords, 3.0, false) if not targetId then Utils.notify('error', locale('no_players_nearby')) return end local otherEmote for i = 1, #Emotes do local emotes = Emotes[i] for k = 1, #emotes.options do local emote = emotes.options[k] if emote.Command == data.Options.Shared.OtherEmote then otherEmote = emote break end end if otherEmote then break end end if not otherEmote then Utils.notify('error', locale('not_valid_emote')) return end TriggerServerEvent('scully_emotemenu:requestSynchronizedEmote', GetPlayerServerId(targetId), data, otherEmote) return end if data.PedTypes then if IsPedHuman(cache.ped) then Utils.notify('error', locale('not_valid_ped')) return end local isValid = false local model = GetEntityModel(cache.ped) for i = 1, #data.PedTypes do local allowed = pedTypes[data.PedTypes[i]] local found = lib.table.contains(allowed, model) if found then isValid = true break end end if not isValid then Utils.notify('error', locale('not_valid_ped')) return end end if not data.PedTypes and not IsPedHuman(cache.ped) then Utils.notify('error', locale('not_valid_ped')) return end if Config.enableWeaponBlock and IsPedArmed(cache.ped, 7) then Utils.notify('error', locale('not_with_weapon')) return end if Config.enableAimShootBlock then CreateThread(function() while PlayerState.isInEmote and not IsPedRagdoll(cache.ped) do Wait(0) DisableControlAction(0, 25, true) DisablePlayerFiring(cache.playerId, true) end CancelEmote() end) end local duration, movementFlag = nil, cache.vehicle and 51 or 0 if not Config.enableEmotesInVehicles and cache.vehicle then Utils.notify('error', locale('not_in_vehicle')) return end local gameTimer = GetGameTimer() local emoteCooldown = PlayerState.emoteCooldown if emoteCooldown and (gameTimer - emoteCooldown) < Config.emoteCooldown then return end PlayerState:set('emoteCooldown', gameTimer, true) PlayerState:set('emoteProps', nil, true) PlayerState:set('emotePtfx', false, true) if data.Scenario then PlayScenario(data.Scenario) return end if Config.enableEmotePlacement and data.Placement and not data.Advanced then lib.hideMenu() placement.start(data) return end local dictionaryName, animationName = data.Dictionary, data.Animation if type(dictionaryName) == 'table' and type(animationName) == 'table' then local randomIndex = math.random(1, #animationName) dictionaryName = dictionaryName[randomIndex] animationName = animationName[randomIndex] end local isValid = lib.requestAnimDict(dictionaryName) if not isValid then Utils.notify('error', locale('not_valid_emote')) return end if data.NSFW and Config.enableNSFWEmotes == 'limited' and not PlayerState.allowNSFWEmotes then Utils.notify('error', locale('nsfw_limited')) return end local options = data.Options if options then duration = options.duration if options.Delay then Wait(options.Delay) end if not cache.vehicle and options.Flags then movementFlag = options.Flags.Stuck and 50 or options.Flags.Move and 51 or options.Flags.Loop and 1 or movementFlag if options.Flags.Loop then PlayerState.lastEmote = { data, variation } end end if options.Props then local props = {} for i = 1, #options.Props do local prop = options.Props[i] if variation then if prop.Variations and prop.Variations[variation] then prop.Variant = prop.Variations[variation] end end props[i] = { hash = joaat(prop.Name), bone = prop.Bone, placement = prop.Placement, variant = prop.Variant, hasPtfx = options?.Ptfx?.AttachToProp } end PlayerState:set('emoteProps', props, true) end local ptfx = options.Ptfx if ptfx then local hasAutomatedPtfx = Config.enableAutoPtfx and ptfx.Auto PlayerState:set('ptfx', { asset = ptfx.Asset, name = ptfx.Name, placement = ptfx.Placement, bone = ptfx.Bone, color = ptfx.Color, canHold = ptfx.CanHold }, true) if Config.ptfxKey and not hasAutomatedPtfx then keybinds.Ptfx:disable(false) Utils.notify('success', (locale('press_for_ptfx'):format(keybinds.Ptfx:getCurrentKey()))) else keybinds.Ptfx:disable(true) if hasAutomatedPtfx then PlayerState:set('emotePtfx', true, true) end end else if Config.ptfxKey then keybinds.Ptfx:disable(true) end end end if data.Advanced then local coords = data.Advanced.Coords TaskPlayAnimAdvanced(cache.ped, dictionaryName, animationName, coords.x, coords.y, coords.z, 0, 0, data.Advanced.Heading, 2.0, 2.0, duration or -1, movementFlag, 1.0, false, false) else TaskPlayAnim(cache.ped, dictionaryName, animationName, 2.0, 2.0, duration or -1, movementFlag, 0, false, false, false) end RemoveAnimDict(dictionaryName) local secondaryEmote = options?.SecondaryEmote if secondaryEmote then local isSecondaryValid = lib.requestAnimDict(secondaryEmote.Dictionary, 1000) if not isSecondaryValid then Utils.notify('error', locale('not_valid_emote')) return end TaskPlayAnim(cache.ped, secondaryEmote.Dictionary, secondaryEmote.Animation, 2.0, 2.0, secondaryEmote.Duration or -1, 51, 0, false, false, false) RemoveAnimDict(secondaryEmote.Dictionary) end PlayerState:set('isInEmote', true, true) end exports('playEmote', PlayEmote) ---Cancel an emote or scenario ---@param skipReset? boolean function CancelEmote(skipReset) if PlayerState.isInEmote and not PlayerState.isLimited then :: cancelEmote :: local isScenario = IsPedUsingAnyScenario(cache.ped) if isScenario then SetPedShouldPlayImmediateScenarioExit(cache.ped) end DetachEntity(cache.ped, true, false) PlayerState:set('isInEmote', false, true) PlayerState:set('emoteProps', nil, true) PlayerState:set('emotePtfx', false, true) if Config.ptfxKey then keybinds.Ptfx:disable(true) end if PlayerState.inSynchronizedEmote then PlayerState:set('inSynchronizedEmote', nil, true) end if isScenario then local coords = GetEntityCoords(cache.ped) for i = 1, #scenarioModels do local model = scenarioModels[i] local entity = GetClosestObjectOfType(coords.x, coords.y, coords.z, 1.0, model, false, true, true) if entity ~= 0 then SetEntityAsMissionEntity(entity, false, true) DeleteEntity(entity) end end end local exitEmoteName = PlayerState?.lastEmote?[1]?.Options?.ExitEmote if not skipReset then PlayerState.lastEmote = nil if exitEmoteName then local exitEmote = ExitEmotes[exitEmoteName] PlayEmote(exitEmote) if exitEmote?.Options?.Duration then Wait(exitEmote.Options.Duration) goto cancelEmote end return end end ClearPedTasks(cache.ped) end end exports('cancelEmote', CancelEmote) ---Play an emote by command ---@param command string ---@param variant? number function PlayEmoteByCommand(command, variant) local found for i = 1, #Scenarios do local scenario = Scenarios[i] if scenario.Command == command then found = scenario break end end if not found then for i = 1, #Emotes do local emotes = Emotes[i] for k = 1, #emotes.options do local emote = emotes.options[k] if emote.Command == command then found = emote break end end end end if not found then Utils.notify('error', locale('not_valid_emote')) return end PlayEmote(found, variant) end exports('playEmoteByCommand', PlayEmoteByCommand) ---Get the players current walk style ---@return string? function GetCurrentWalk() return PlayerState.walkstyle end exports('getCurrentWalk', GetCurrentWalk) ---Set the players current walk style ---@param name string function SetWalk(name) lib.requestAnimSet(name, 1000) SetPedMovementClipset(cache.ped, name, 0.2) RemoveAnimSet(name) if Config.preventWalkStyleAbuse then local dict = Utils.isPedMale(cache.ped) and 'move_m@multiplayer' or 'move_f@multiplayer' lib.requestAnimDict(dict) SetPedAlternateMovementAnim(cache.ped, 2, dict, 'run', 0, true) RemoveAnimDict(dict) end KVP.update('walkstyle', name) PlayerState:set('walkstyle', name, true) end exports('setWalk', SetWalk) ---Reset the players walk style function ResetWalk() ResetPedMovementClipset(cache.ped, 0) KVP.delete('walkstyle') PlayerState:set('walkstyle', nil, true) end exports('resetWalk', resetWalk) ---Set the players current walk style using the command ---@param command string function SetWalkByCommand(command) local found for i = 1, #Walks do local walk = Walks[i] if walk.Command == command then found = walk break end end if not found then Utils.notify('error', locale('not_valid_walk')) return end SetWalk(found.Walk) end exports('setWalkByCommand', SetWalkByCommand) ---Play a scenario ---@param name string function PlayScenario(name) if cache.vehicle then Utils.notify('error', locale('no_scenarios_in_vehicle')) return end ClearPedTasks(cache.ped) TaskStartScenarioInPlace(cache.ped, name, 0, true) PlayerState:set('isInEmote', true, true) end ---Get the players current expression ---@return string function GetCurrentExpression() return PlayerState.expression end exports('getCurrentExpression', GetCurrentExpression) ---Set the players current expression ---@param name string function SetExpression(name) SetFacialIdleAnimOverride(cache.ped, name, 0) KVP.update('expression', name) PlayerState:set('expression', name, true) end exports('setExpression', SetExpression) ---Reset the players expression function ResetExpression() ClearFacialIdleAnimOverride(cache.ped) KVP.delete('expression') PlayerState:set('expression', nil, true) end exports('resetExpression', ResetExpression) ---Set the players current expression using the command ---@param command string function SetExpressionByCommand(command) local found for i = 1, #Expressions do local expression = Expressions[i] if expression.Command == command then found = expression break end end if not found then Utils.notify('error', locale('not_valid_expression')) return end SetExpression(found.Expression) end exports('setExpressionByCommand', SetExpressionByCommand) ---Register emotes to be used within external resources ---@param emote table function RegisterEmote(emote) registeredEmotes[emote.Name] = emote end exports('registerEmote', RegisterEmote) ---Play a registered emote ---@param emote string function PlayRegisteredEmote(emote) local registeredEmote = registeredEmotes[emote] if not registeredEmote then Utils.notify('error', locale('not_valid_emote')) return end PlayEmote(emote) end exports('playRegisteredEmote', PlayRegisteredEmote) ---Add an emote to the menu ---@param data table function AddEmoteToMenu(menu, data) for i = 1, #Emotes do local emotes = Emotes[i] if emotes.name == menu then emotes.options[#emotes.options + 1] = data break end end RegisterMenu() end exports('addEmoteToMenu', AddEmoteToMenu) ---Add multiple emotes to the menu ---@param data table function AddEmotesToMenu(menu, data) for i = 1, #Emotes do local emotes = Emotes[i] if emotes.name == menu then for k = 1, #data do emotes.options[#emotes.options + 1] = data[k] end break end end RegisterMenu() end exports('addEmotesToMenu', AddEmotesToMenu) ---Get a list of props from your current emote ---@return table? function GetEmoteProps() return pedProps[cache.serverId] end exports('getEmoteProps', GetEmoteProps) ---Create props and attach them to a ped ---@param ped number ---@param data table ---@return table function CreateProps(ped, data) local props = {} for i = 1, #data do local prop = data[i] lib.requestModel(prop.hash) local coords = GetEntityCoords(ped) local object = CreateObject(prop.hash, coords.x, coords.y, coords.z, false, false, false) SetEntityCollision(object, false, false) if prop.variant then SetObjectTextureVariation(object, prop.variant) end AttachEntityToEntity(object, ped, GetPedBoneIndex(ped, prop.bone), prop.placement[1].x, prop.placement[1].y, prop.placement[1].z, prop.placement[2].x, prop.placement[2].y, prop.placement[2].z, true, true, false, true, 1, true) SetModelAsNoLongerNeeded(prop.hash) props[i] = { entity = object, hasPtfx = prop.hasPtfx } end return props end ---Delete props attached to a player ---@param serverId number function DeleteProps(serverId) local props = pedProps[serverId] if not props then return end for i = 1, #props do local prop = props[i] if DoesEntityExist(prop.entity) then DeleteEntity(prop.entity) end end props = nil end ---Creates a list for the chosen table ---@param title string ---@param tbl table ---@param command string function CreateList(title, tbl, command) local list = {} for i = 1, #tbl do local entry = tbl[i] if entry.icon then list[#list + 1] = ('__**%s**__'):format(entry.name) for k = 1, #entry.options do local option = entry.options[k] list[#list + 1] = ('- %s (/%s %s)'):format(option.Label, command, option.Command) end list[#list + 1] = '\n' else list[i] = ('- %s (/%s %s)'):format(entry.Label, command, entry.Command) end end list = table.concat(list, '\n') lib.alertDialog({ header = title, content = list, centered = true, size = 'lg', overflow = true, labels = { cancel = '', confirm = locale('close') } }) end ---Toggle player limitations ---@param limited boolean function SetLimitation(limited) PlayerState:set('isLimited', limited, true) end exports('setLimitation', SetLimitation) ---Check if the player is currently limited ---@return boolean function IsLimited() return PlayerState.isLimited end exports('isLimited', IsLimited) local IsControlJustPressed = IsControlJustPressed RegisterNetEvent('scully_emotemenu:synchronizedEmoteRequest', function(sender, senderData, targetData) PlaySoundFrontend(-1, 'NAV', 'HUD_AMMO_SHOP_SOUNDSET', false) lib.showTextUI(locale('accept_deny', targetData.Label)) while true do Wait(0) if IsControlJustPressed(0, 246) then lib.hideTextUI() TriggerServerEvent('scully_emotemenu:synchronizedEmoteResponse', sender, senderData, targetData) break elseif IsControlJustPressed(0, 306) then lib.hideTextUI() break end end end) RegisterNetEvent('scully_emotemenu:targetStartSynchronizedEmote', function(sender, senderData, targetData) local isValid = lib.requestAnimDict(targetData.Dictionary) if not isValid then Utils.notify('error', locale('not_valid_emote')) return end if targetData.Options.Shared.Attach and not senderData.Options.Shared.Attach then local senderId = GetPlayerFromServerId(sender) local senderPed = GetPlayerPed(senderId) local bone = targetData.Options.Shared.Bone or -1 local placement = targetData.Options.Shared.Placement local xP, yP, zP, xR, yR, zR = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 if placement then xP, yP, zP, xR, yR, zR = placement[1].x, placement[1].y, placement[1].z, placement[2].x, placement[2].y, placement[2].z end AttachEntityToEntity(cache.ped, senderPed, GetPedBoneIndex(senderPed, bone), xP, yP, zP, xR, yR, zR, false, false, false, true, 1, true) end targetData.StartSynchronized = true PlayEmote(targetData) PlayerState:set('inSynchronizedEmote', sender, true) end) RegisterNetEvent('scully_emotemenu:senderStartSynchronizedEmote', function(target, senderData) local isValid = lib.requestAnimDict(senderData.Dictionary) if not isValid then Utils.notify('error', locale('not_valid_emote')) return end local targetId = GetPlayerFromServerId(target) local targetPed = GetPlayerPed(targetId) local frontOffset, sideOffset, heightOffset, headingOffset = 1.0, 0.0, 0.0, 180.1 if senderData.Options.Shared.FrontOffset then frontOffset = senderData.Options.Shared.FrontOffset + 0.0 end if senderData.Options.Shared.SideOffset then sideOffset = senderData.Options.Shared.SideOffset + 0.0 end if senderData.Options.Shared.HeightOffset then heightOffset = senderData.Options.Shared.HeightOffset + 0.0 end if senderData.Options.Shared.HeadingOffset then headingOffset = senderData.Options.Shared.HeadingOffset + 0.0 end if senderData.Options.Shared.Attach then local bone = senderData.Options.Shared.Bone or -1 local placement = senderData.Options.Shared.Placement local xP, yP, zP, xR, yR, zR = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 if placement then xP, yP, zP, xR, yR, zR = placement[1].x, placement[1].y, placement[1].z, placement[2].x, placement[2].y, placement[2].z end AttachEntityToEntity(cache.ped, targetPed, GetPedBoneIndex(targetPed, bone), xP, yP, zP, xR, yR, zR, false, false, false, true, 1, true) end local targetCoords = GetOffsetFromEntityInWorldCoords(targetPed, sideOffset, frontOffset, heightOffset) local targetHeading = GetEntityHeading(targetPed) SetEntityHeading(cache.ped, targetHeading - headingOffset) SetEntityCoordsNoOffset(cache.ped, targetCoords.x, targetCoords.y, targetCoords.z, false, false, false) senderData.StartSynchronized = true PlayEmote(senderData) PlayerState:set('inSynchronizedEmote', target, true) end) RegisterNetEvent('scully_emotemenu:toggleMenu', ToggleMenu) RegisterNetEvent('scully_emotemenu:closeMenu', CloseMenu) RegisterNetEvent('scully_emotemenu:addEmoteMenuOption', AddEmoteMenuOption) RegisterNetEvent('scully_emotemenu:playEmote', PlayEmote) RegisterNetEvent('scully_emotemenu:cancelEmote', CancelEmote) RegisterNetEvent('scully_emotemenu:playEmoteByCommand', PlayEmoteByCommand) RegisterNetEvent('scully_emotemenu:setWalk', SetWalk) RegisterNetEvent('scully_emotemenu:resetWalk', ResetWalk) RegisterNetEvent('scully_emotemenu:setWalkByCommand', SetWalkByCommand) RegisterNetEvent('scully_emotemenu:setExpression', SetExpression) RegisterNetEvent('scully_emotemenu:resetExpression', ResetExpression) RegisterNetEvent('scully_emotemenu:setExpressionByCommand', SetExpressionByCommand) RegisterNetEvent('scully_emotemenu:registerEmote', RegisterEmote) RegisterNetEvent('scully_emotemenu:playRegisteredEmote', PlayRegisteredEmote) RegisterNetEvent('scully_emotemenu:setLimitation', SetLimitation) RegisterNetEvent('scully_emotemenu:addEmoteToMenu', AddEmoteToMenu) RegisterNetEvent('scully_emotemenu:addEmotesToMenu', AddEmotesToMenu) RegisterNetEvent('onPlayerDropped', function(serverId) DeleteProps(serverId) local hasPtfx = pedPtfx[serverId] if hasPtfx then StopParticleFxLooped(hasPtfx, false) pedPtfx[serverId] = nil end end) AddStateBagChangeHandler('emoteProps', nil, function(bagName, key, value, reserved, replicated) if replicated then return end local player = GetPlayerFromStateBagName(bagName) if player == 0 then return end local ped = GetPlayerPed(player) local serverId = GetPlayerServerId(player) if not value then DeleteProps(serverId) return end pedProps[serverId] = CreateProps(ped, value) end) AddStateBagChangeHandler('emotePtfx', nil, function(bagName, key, value, reserved, replicated) if replicated then return end local player = GetPlayerFromStateBagName(bagName) if player == 0 then return end local ped = GetPlayerPed(player) local serverId = GetPlayerServerId(player) local ptfx = Player(serverId).state.ptfx if not ptfx then return end if not value then local hasPtfx = pedPtfx[serverId] if hasPtfx then StopParticleFxLooped(hasPtfx, false) pedPtfx[serverId] = nil end return end Wait(100) local props = pedProps[serverId] local target = ped if props then for i = 1, #props do local prop = props[i] if prop.hasPtfx then target = prop.entity break end end end local boneIndex = ptfx.bone and GetPedBoneIndex(ped, ptfx.bone) or GetEntityBoneIndexByName(ptfx.name, 'VFX') lib.requestNamedPtfxAsset(ptfx.asset) UseParticleFxAsset(ptfx.asset) local effect = StartParticleFxLoopedOnEntityBone(ptfx.name, target, ptfx.placement[1].x, ptfx.placement[1].y, ptfx.placement[1].z, ptfx.placement[2].x, ptfx.placement[2].y, ptfx.placement[2].z, boneIndex, ptfx.placement[3] + 0.0, false, false, false) if ptfx.color then if ptfx.color[1] and type(ptfx.color[1]) == 'table' then local randomIndex = math.random(1, #ptfx.color) ptfx.color = ptfx.color[randomIndex] end SetParticleFxLoopedAlpha(effect, ptfx.color.A) SetParticleFxLoopedColour(effect, ptfx.color.R / 255, ptfx.color.G / 255, ptfx.color.B / 255, false) end RemoveNamedPtfxAsset(ptfx.asset) pedPtfx[serverId] = effect end) AddStateBagChangeHandler('inSynchronizedEmote', nil, function(bagName, key, value, reserved, replicated) if replicated then return end local inSynchronizedEmote = PlayerState.inSynchronizedEmote if not inSynchronizedEmote then return end local player = GetPlayerFromStateBagName(bagName) if player == 0 then return end local serverId = GetPlayerServerId(player) if not value and inSynchronizedEmote == serverId then Utils.notify('error', locale('emote_cancelled')) CancelEmote() end end) if Config.emoteCommands then for i = 1, #Config.emoteCommands do Utils.addCommand(Config.emoteCommands[i], { help = locale('emote_command_help'), params = { { name = locale('emote_command_param1_name'), help = locale('emote_command_param1_help') }, { name = locale('emote_command_param2_name'), help = locale('emote_command_param2_help') } } }, function(source, args, raw) local param1 = args[1] if not param1 then Utils.notify('error', locale('not_valid_emote')) return end local emoteName = param1:lower() if emoteName == 'c' then CancelEmote() return end if emoteName == 'l' then CreateList(locale('emotes'), Emotes, Config.emoteCommands[1]) return end local variant local param2 = args[2] if param2 then local index = tonumber(param2) if index then variant = index end end PlayEmoteByCommand(emoteName, variant) end) end end if Config.walkCommands then for i = 1, #Config.walkCommands do Utils.addCommand(Config.walkCommands[i], { help = locale('walk_command_help'), params = { { name = locale('walk_command_param1_name'), help = locale('walk_command_param1_help') } } }, function(source, args, raw) local param1 = args[1] if not param1 then Utils.notify('error', locale('not_valid_walk')) return end local walkName = param1:lower() if walkName == 'c' then ResetWalk() return end if walkName == 'l' then CreateList(locale('walking_styles'), Walks, Config.walkCommands[1]) return end SetWalkByCommand(walkName) end) end end if Config.expressionCommands then for i = 1, #Config.expressionCommands do Utils.addCommand(Config.expressionCommands[i], { help = locale('expression_command_help'), params = { { name = locale('expression_command_param1_name'), help = locale('expression_command_param1_help') } } }, function(source, args, raw) local param1 = args[1] if not param1 then Utils.notify('error', locale('not_valid_expression')) return end local expressionName = param1:lower() if expressionName == 'c' then ResetExpression() return end if expressionName == 'l' then CreateList(locale('facial_expressions'), Expressions, Config.expressionCommands[1]) return end SetExpressionByCommand(expressionName) end) end end if Config.emoteCancelKey then lib.addKeybind({ name = 'emoteCancelKey', description = locale('cancel_emote'), defaultKey = Config.emoteCancelKey, onPressed = function() CancelEmote() end }) end if Config.ptfxKey then keybinds.Ptfx = lib.addKeybind({ name = 'ptfxKey', disabled = true, description = locale('play_ptfx'), defaultKey = Config.ptfxKey, onPressed = function() PlayerState:set('emotePtfx', true, true) if not PlayerState?.ptfx?.canHold then PlayerState:set('emotePtfx', false, true) end end, onReleased = function() if PlayerState?.ptfx?.canHold then PlayerState:set('emotePtfx', false, true) end end }) end if Config.enableEmoteBinds then for i = 1, 5 do local index = tostring(i) lib.addKeybind({ name = ('emotebindKey-%s'):format(index), description = locale('emote_bind', index), defaultKey = '', onPressed = function() local emote = EmoteBinds[index] if emote then PlayEmote(emote) end end }) end end lib.onCache('ped', function(ped) local walkstyle = KVP.getString('walkstyle') if walkstyle then SetWalk(walkstyle) end local expression = KVP.getString('expression') if expression then SetExpression(expression) end end) AddEventHandler('playerSpawned', function() Wait(1000) local walkstyle = KVP.getString('walkstyle') if walkstyle then SetWalk(walkstyle) end local expression = KVP.getString('expression') if expression then SetExpression(expression) end end) AddEventHandler('entityDamaged', function(entity) if cache.ped == entity then if not IsPedFatallyInjured(cache.ped) then return end CancelEmote() end end) local openingDoor = false local IsPedOpeningADoor = IsPedOpeningADoor AddEventHandler('CEventOpenDoor', function() if not PlayerState.isInEmote then return end if openingDoor then return end openingDoor = true while IsPedOpeningADoor(cache.ped) do Wait(100) end openingDoor = false local lastEmote = PlayerState.lastEmote if lastEmote then CancelEmote(true) Wait(10) PlayEmote(lastEmote[1], lastEmote[2]) end end) local hitTimeout, hittingPed = 500, false AddEventHandler('CEventPlayerCollisionWithPed', function() if not PlayerState.isInEmote then return end if hittingPed then hitTimeout = 500 return end hitTimeout, hittingPed = 500, true while hitTimeout > 0 do Wait(100) hitTimeout -= 100 end local lastEmote = PlayerState.lastEmote if lastEmote then hitTimeout, hittingPed = 500, false CancelEmote(true) Wait(10) PlayEmote(lastEmote[1], lastEmote[2]) end end) if Config.handsUpKey ~= '' then require 'client.modules.handsup' end if Config.stanceKey ~= '' then require 'client.modules.stance' end if Config.pointKey ~= '' then require 'client.modules.point' end if Config.ragdollKey ~= '' then require 'client.modules.ragdoll' end