2025-04-07 01:41:12 +00:00

1138 lines
32 KiB
Lua

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