fixes etc

This commit is contained in:
Nordi98 2025-06-12 03:36:12 +02:00
parent 4a0c8c6204
commit 453b281a4b
644 changed files with 1907 additions and 2456 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
-- Emotes you add in the file will automatically be added to AnimationList.lua
-- If you have multiple custom list files they MUST be added between AnimationList.lua and Emote.lua in fxmanifest.lua!
-- Don't change 'CustomDP' it is local to this file!

-- Remove the } from the = {} then enter your own animation code ---
-- Don't forget to close the tables.

local CustomDP = {}

CustomDP.Expressions = {}
CustomDP.Walks = {}
CustomDP.Shared = {}
CustomDP.Dances = {}
CustomDP.AnimalEmotes = {}
CustomDP.Exits = {}
CustomDP.Emotes = {}
CustomDP.PropEmotes = {}

-----------------------------------------------------------------------------------------
--| I don't think you should change the code below unless you know what you are doing |--
-----------------------------------------------------------------------------------------

function LoadAddonEmotes()
local prefixes = {
Shared = '🤼 ',
AnimalEmotes = '🐶 ',
PropEmotes = '📦 '
}

for arrayName, array in pairs(CustomDP) do
if RP[arrayName] then
local prefix = prefixes[arrayName]
for emoteName, emoteData in pairs(array) do
if prefix then
emoteData[3] = prefix .. emoteData[3]
end
RP[arrayName][emoteName] = emoteData
end
end
end
-- Free memory
CustomDP = nil
end

View file

@ -0,0 +1,168 @@
IsUsingBinoculars = false

if Config.BinocularsEnabled then
RegisterCommand("binoculars", function()
UseBinocular()
end, false)
TriggerEvent('chat:addSuggestion', '/binoculars', 'Use binoculars', {})

local fov = 40.0
local index = 0
local cam
local prop_binoc
local instructions = true
local scaleform_bin
local scaleform_instructions

local function CleanupBinoculars()
ClearPedTasks(PlayerPedId())
ClearTimecycleModifier()
RenderScriptCams(false, false, 0, true, false)
SetScaleformMovieAsNoLongerNeeded(scaleform_bin)
SetScaleformMovieAsNoLongerNeeded(scaleform_instructions)
DestroyCam(cam, false)
if prop_binoc then
DeleteEntity(prop_binoc)
end
SetNightvision(false)
SetSeethrough(false)
end

function UseBinocular()
if IsPedSittingInAnyVehicle(PlayerPedId()) then
return
end
if IsInActionWithErrorMessage({ ['IsUsingBinoculars'] = true }) then
return
end
IsUsingBinoculars = not IsUsingBinoculars

if IsUsingBinoculars then
CreateThread(function()
DestroyAllProps()
ClearPedTasks(PlayerPedId())
RequestAnimDict("amb@world_human_binoculars@male@idle_a")
while not HasAnimDictLoaded("amb@world_human_binoculars@male@idle_a") do
Wait(5)
end

-- attach the prop to the player
local boneIndex = GetPedBoneIndex(PlayerPedId(), 28422)
local x, y, z = table.unpack(GetEntityCoords(PlayerPedId(), true))
if not HasModelLoaded("prop_binoc_01") then
LoadPropDict("prop_binoc_01")
end
prop_binoc = CreateObject(`prop_binoc_01`, x, y, z + 0.2, true, true, true)
AttachEntityToEntity(prop_binoc, PlayerPedId(), boneIndex, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, true,
false, true, 1, true)

TaskPlayAnim(PlayerPedId(), "amb@world_human_binoculars@male@idle_a", "idle_c", 5.0, 5.0, -1, 51, 0,
false, false, false)
PlayAmbientSpeech1(PlayerPedId(), "GENERIC_CURSE_MED", "SPEECH_PARAMS_FORCE")
SetCurrentPedWeapon(PlayerPedId(), `WEAPON_UNARMED`, true)

RemoveAnimDict("amb@world_human_binoculars@male@idle_a")
SetModelAsNoLongerNeeded("prop_binoc_01")
end)

Wait(200)

SetTimecycleModifier("default")
SetTimecycleModifierStrength(0.3)
scaleform_bin = RequestScaleformMovie("BINOCULARS")
while not HasScaleformMovieLoaded(scaleform_bin) do
Wait(10)
end

cam = CreateCam("DEFAULT_SCRIPTED_FLY_CAMERA", true)

AttachCamToEntity(cam, PlayerPedId(), 0.0, 0.0, 1.2, true)
SetCamRot(cam, 0.0, 0.0, GetEntityHeading(PlayerPedId()))
SetCamFov(cam, fov)
RenderScriptCams(true, false, 0, true, false)
PushScaleformMovieFunction(scaleform_bin, "SET_CAM_LOGO")
PushScaleformMovieFunctionParameterInt(0) -- 0 for nothing, 1 for LSPD logo
PopScaleformMovieFunctionVoid()

local keyList
if Config.AllowVisionsToggling then
keyList = {
{ key = 177, text = 'exit_binoculars' },
{ key = 19, text = 'toggle_binoculars_vision' },
{ key = 47, text = 'toggle_instructions' }
}
else
keyList = {
{ key = 177, text = 'exit_binoculars' },
{ key = 47, text = 'toggle_instructions' }
}
end

scaleform_instructions = SetupButtons(keyList)

while IsUsingBinoculars and not IsEntityDead(PlayerPedId()) and not IsPedSittingInAnyVehicle(PlayerPedId()) do
if IsControlJustPressed(0, 177) then
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
IsUsingBinoculars = false
end


fov = HandleZoomAndCheckRotation(cam, fov)

HideHUDThisFrame()
DisableControlAction(0, 25, true) -- disable aim
DisableControlAction(0, 44, true) -- INPUT_COVER
DisableControlAction(0, 37, true) -- INPUT_SELECT_WEAPON
DisableControlAction(0, 24, true) -- Attack
DisablePlayerFiring(PlayerPedId(), true) -- Disable weapon firing


if IsControlJustPressed(0, 19) and Config.AllowVisionsToggling then
-- if index = 0, toggle night vision, if index = 1, toggle thermal vision, if index = 2, toggle normal vision and reset index
if index == 0 then
SetNightvision(true)
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
index = index + 1
elseif index == 1 then
SetSeethrough(true)
SetNightvision(false)
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
index = index + 1
elseif index == 2 then
SetNightvision(false)
SetSeethrough(false)
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
index = 0
end
end

if IsControlJustPressed(0, 47) then
instructions = not instructions
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
end

DrawScaleformMovieFullscreen(scaleform_bin, 255, 255, 255, 255)
if instructions then
DrawScaleformMovieFullscreen(scaleform_instructions, 255, 255, 255, 255)
end
Wait(1)
end
end

-- RESET EVERYTHING
IsUsingBinoculars = false
index = 0

CleanupBinoculars()
end

AddEventHandler('onResourceStop', function(resource)
if resource == GetCurrentResourceName() then
CleanupBinoculars()
end
end)

CreateExport('toggleBinoculars', function()
UseBinocular()
end)
end

View file

@ -0,0 +1,107 @@
Framework = 'standalone'
PlayerLoaded, PlayerData = nil, {}

local function InitializeFramework()
if GetResourceState('es_extended') == 'started' then
ESX = exports['es_extended']:getSharedObject()
Framework = 'esx'

RegisterNetEvent('esx:playerLoaded', function(xPlayer)
PlayerData = xPlayer
PlayerLoaded = true
end)

RegisterNetEvent('esx:onPlayerLogout', function()
PlayerData = {}
PlayerLoaded = false
end)

AddEventHandler('esx:setPlayerData', function(key, value)
PlayerData[key] = value
end)

AddEventHandler('onResourceStart', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
PlayerData = ESX.GetPlayerData()
PlayerLoaded = true
end)
elseif GetResourceState('qb-core') == 'started' then
QBCore = exports['qb-core']:GetCoreObject()
Framework = 'qb'

AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
PlayerData = QBCore.Functions.GetPlayerData()
end)

RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
PlayerData = {}
end)

AddEventHandler('onResourceStart', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
PlayerData = QBCore.Functions.GetPlayerData()
end)
end

print('[RPEmotes-Reborn] Framework initialized: ' .. Framework)
end

function CanDoAction()
if Framework == 'esx' then
return PlayerLoaded and not PlayerData.dead
elseif Framework == 'qb' then
return LocalPlayer.state.isLoggedIn and not (PlayerData.metadata.inlaststand or PlayerData.metadata.isdead)
end
-- here you can implement your own standalone framework check
return true
end

InitializeFramework()


-- EVENTS

RegisterNetEvent('animations:client:PlayEmote', function(args)
if CanDoAction() then
EmoteCommandStart(args)
end
end)

if Config.Keybinding then
RegisterNetEvent('animations:client:BindEmote', function(args)
if CanDoAction() then
EmoteBindStart(nil, args)
end
end)

RegisterNetEvent('animations:client:EmoteBinds', function()
if CanDoAction() then
ListKeybinds()
end
end)

RegisterNetEvent('animations:client:EmoteDelete', function(args)
if CanDoAction() then
DeleteEmote(args)
end
end)
end


RegisterNetEvent('animations:client:EmoteMenu', function()
if CanDoAction() then
OpenEmoteMenu()
end
end)

RegisterNetEvent('animations:client:Walk', function(args)
if CanDoAction() then
WalkCommandStart(args)
end
end)

RegisterNetEvent('animations:client:ListWalks', function()
if CanDoAction() then
WalksOnCommand()
end
end)

View file

@ -0,0 +1,501 @@
IsProne = false
local isCrouched = false
local isCrawling = false
local inAction = false
local proneType = 'onfront'
local lastKeyPress = 0
local forceEndProne = false

-- Crouching --

local function ResetCrouch()
local playerPed = PlayerPedId()

ResetPedStrafeClipset(playerPed)
ResetPedWeaponMovementClipset(playerPed)
SetPedMaxMoveBlendRatio(playerPed, 1.0)
SetPedCanPlayAmbientAnims(playerPed, true)

local walkstyle = GetResourceKvpString("walkstyle")
if walkstyle then
local toApply = RP[walkstyle]
if not toApply or type(toApply) ~= "table" or toApply.category ~= "Walks" then
ResetPedMovementClipset(playerPed, 0.5)
DeleteResourceKvp("walkstyle")
DebugPrint('Invalid walkstyle found in KVP, resetting to default.')
return
end
RequestWalking(toApply[1])
SetPedMovementClipset(PlayerPedId(), toApply[1], 0.5)
RemoveClipSet(toApply[1])
else
ResetPedMovementClipset(playerPed, 0.5)
end

RemoveAnimSet('move_ped_crouched')
end

local function CrouchLoop()
local playerId = PlayerId()

while isCrouched do
local playerPed = PlayerPedId()

if not CanPlayerCrouchCrawl(playerPed) then
isCrouched = false
break
end

if IsPlayerAiming(playerId) then
SetPedMaxMoveBlendRatio(playerPed, 0.15)
end

SetPedCanPlayAmbientAnims(playerPed, false)

DisableControlAction(0, 36, true)
if IsPedUsingActionMode(playerPed) == 1 then
SetPedUsingActionMode(playerPed, false, -1, 'DEFAULT_ACTION')
end

DisableFirstPersonCamThisFrame()

if Config.FpsMode == true then
DisableControlAction(2, 25, true) -- disables the aim control action entirely while crouched
end

Wait(0)
end

TriggerEvent('crouch_crawl:onCrouch', false)

ResetCrouch()
end

local function StartCrouch()
isCrouched = true
RequestWalking('move_ped_crouched')
local playerPed = PlayerPedId()

if GetPedStealthMovement(playerPed) ~= 0 then
SetPedStealthMovement(playerPed, false, 'DEFAULT_ACTION')
Wait(100)
end

if GetFollowPedCamViewMode() == 4 then
SetFollowPedCamViewMode(0) -- THIRD_PERSON_NEAR
end

SetPedMovementClipset(playerPed, 'move_ped_crouched', 0.6)
SetPedStrafeClipset(playerPed, 'move_ped_crouched_strafing')

-- For other scripts to use
TriggerEvent('crouch_crawl:onCrouch', true)

CreateThread(CrouchLoop)
end

---@param playerPed number
---@return boolean success
local function AttemptCrouch(playerPed)
if CanPlayerCrouchCrawl(playerPed) and IsPedHuman(playerPed) then
StartCrouch()
return true
else
return false
end
end

---Disables a control until it's key has been released
---@param padIndex integer
---@param control integer
local function DisableControlUntilReleased(padIndex, control)
CreateThread(function()
while IsDisabledControlPressed(padIndex, control) do
DisableControlAction(padIndex, control, true)
Wait(0)
end
end)
end

local function CrouchKeyPressed()
if not LocalPlayer.state.canEmote then return end

if inAction then
return
end

if IsPauseMenuActive() or IsNuiFocused() then
return
end

if isCrouched then
isCrouched = false
local crouchKey = GetControlInstructionalButton(0, `+crouch` | 0x80000000, false)
local lookBehindKey = GetControlInstructionalButton(0, 26, false) -- INPUT_LOOK_BEHIND

if crouchKey == lookBehindKey then
DisableControlUntilReleased(0, 26) -- INPUT_LOOK_BEHIND
end

return
end

local playerPed = PlayerPedId()

if not CanPlayerCrouchCrawl(playerPed) or not IsPedHuman(playerPed) then
return
end

local crouchKey = GetControlInstructionalButton(0, `+crouch` | 0x80000000, false)
local lookBehindKey = GetControlInstructionalButton(0, 26, false) -- INPUT_LOOK_BEHIND
local duckKey = GetControlInstructionalButton(0, 36, false) -- INPUT_DUCK

if crouchKey == lookBehindKey then
DisableControlUntilReleased(0, 26) -- INPUT_LOOK_BEHIND
end

if crouchKey == duckKey then
if Config.CrouchOverrideStealthMode then
DisableControlAction(0, 36, true) -- Disable INPUT_DUCK this frame
elseif not IsProne then
local timer = GetGameTimer()

if GetPedStealthMovement(playerPed) ~= 0 and timer - lastKeyPress < 1000 then
DisableControlAction(0, 36, true) -- Disable INPUT_DUCK this frame
lastKeyPress = 0
else
lastKeyPress = timer
return
end
end
end

StartCrouch()

if IsProne then
inAction = true
IsProne = false
PlayAnimOnce(playerPed, 'get_up@directional@transition@prone_to_knees@crawl', 'front', nil, nil, 780)
Wait(780)
inAction = false
end
end


-- Crawling --

---@param playerPed number
---@return boolean
local function ShouldPlayerDiveToCrawl(playerPed)
if IsPedRunning(playerPed) or IsPedSprinting(playerPed) then
return true
end

return false
end

---Stops the player from being prone
---@param force boolean If forced then no exit anim is played
local function stopPlayerProne(force)
IsProne = false
forceEndProne = force
end

---@param playerPed number
---@param heading number|nil
---@param blendInSpeed number|nil
local function PlayIdleCrawlAnim(playerPed, heading, blendInSpeed)
local playerCoords = GetEntityCoords(playerPed)
TaskPlayAnimAdvanced(playerPed, 'move_crawl', proneType..'_fwd', playerCoords.x, playerCoords.y, playerCoords.z, 0.0, 0.0, heading or GetEntityHeading(playerPed), blendInSpeed or 2.0, 2.0, -1, 2, 1.0, false, false)
end

---@param forceEnd boolean
local function PlayExitCrawlAnims(forceEnd)
if not forceEnd then
inAction = true
local playerPed = PlayerPedId()
local animDict, animName, waitTime

if proneType == 'onfront' then
animDict, animName, waitTime = 'get_up@directional@transition@prone_to_knees@crawl', 'front', 780
else
animDict, animName, waitTime = 'get_up@directional@transition@prone_to_seated@crawl', 'back', 950
end

PlayAnimOnce(playerPed, animDict, animName, nil, nil, waitTime)

if not isCrouched then
Wait(waitTime)
PlayAnimOnce(playerPed, 'get_up@directional@movement@from_'..(proneType == 'onfront' and 'knees' or 'seated')..'@standard', 'getup_l_0', nil, nil, 1300)
end
end
end

-- Crawls one "step" forward/backward
---@param playerPed number
---@param type string
---@param direction string
local function Crawl(playerPed, type, direction)
isCrawling = true

TaskPlayAnim(playerPed, 'move_crawl', type..'_'..direction, 8.0, -8.0, -1, 2, 0.0, false, false, false)

local time = {
['onfront'] = {
['fwd'] = 820,
['bwd'] = 990
},
['onback'] = {
['fwd'] = 1200,
['bwd'] = 1200
}
}

SetTimeout(time[type][direction], function()
isCrawling = false
end)
end

-- Flips the player when crawling
---@param playerPed number
local function CrawlFlip(playerPed)
inAction = true
local heading = GetEntityHeading(playerPed)

proneType = proneType == 'onfront' and 'onback' or 'onfront'

if proneType == 'onback' then
PlayAnimOnce(playerPed, 'get_up@directional_sweep@combat@pistol@front', 'front_to_prone', 2.0)
ChangeHeadingSmooth(playerPed, -18.0, 3600)
else
PlayAnimOnce(playerPed, 'move_crawlprone2crawlfront', 'back', 2.0, nil, -1)
ChangeHeadingSmooth(playerPed, 12.0, 1700)
end

PlayIdleCrawlAnim(playerPed, heading + 180.0)
Wait(400)
inAction = false
end

local function CrawlLoop()
Wait(400)

while IsProne do
local playerPed = PlayerPedId()

if not CanPlayerCrouchCrawl(playerPed) or IsEntityInWater(playerPed) then
ClearPedTasks(playerPed)
stopPlayerProne(true)
break
end

local forward, backwards = IsControlPressed(0, 32), IsControlPressed(0, 33) -- INPUT_MOVE_UP_ONLY, INPUT_MOVE_DOWN_ONLY
if not isCrawling then
if forward then -- Forward
Crawl(playerPed, proneType, 'fwd')
elseif backwards then -- Back
Crawl(playerPed, proneType, 'bwd')
end
end

-- Moving left/right
if IsControlPressed(0, 34) then -- INPUT_MOVE_LEFT_ONLY
if isCrawling then
local headingDiff = forward and 1.0 or -1.0
SetEntityHeading(playerPed, GetEntityHeading(playerPed) + headingDiff)
else
inAction = true
if proneType == 'onfront' then
local playerCoords = GetEntityCoords(playerPed)
TaskPlayAnimAdvanced(playerPed, 'move_crawlprone2crawlfront', 'left', playerCoords.x, playerCoords.y, playerCoords.z, 0.0, 0.0, GetEntityHeading(playerPed), 2.0, 2.0, -1, 2, 0.1, false, false)
ChangeHeadingSmooth(playerPed, -10.0, 300)
Wait(700)
else
PlayAnimOnce(playerPed, 'get_up@directional_sweep@combat@pistol@left', 'left_to_prone')
ChangeHeadingSmooth(playerPed, 25.0, 400)
PlayIdleCrawlAnim(playerPed)
Wait(600)
end
inAction = false
end
elseif IsControlPressed(0, 35) then -- INPUT_MOVE_RIGHT_ONLY
if isCrawling then
local headingDiff = backwards and 1.0 or -1.0
SetEntityHeading(playerPed, GetEntityHeading(playerPed) + headingDiff)
else
inAction = true
if proneType == 'onfront' then
local playerCoords = GetEntityCoords(playerPed)
TaskPlayAnimAdvanced(playerPed, 'move_crawlprone2crawlfront', 'right', playerCoords.x, playerCoords.y, playerCoords.z, 0.0, 0.0, GetEntityHeading(playerPed), 2.0, 2.0, -1, 2, 0.1, false, false)
ChangeHeadingSmooth(playerPed, 10.0, 300)
Wait(700)
else
PlayAnimOnce(playerPed, 'get_up@directional_sweep@combat@pistol@right', 'right_to_prone')
ChangeHeadingSmooth(playerPed, -25.0, 400)
PlayIdleCrawlAnim(playerPed)
Wait(600)
end
inAction = false
end
end

if not isCrawling then
if IsControlPressed(0, 22) then -- INPUT_JUMP
CrawlFlip(playerPed)
end
end

Wait(0)
end

TriggerEvent('crouch_crawl:onCrawl', false)

PlayExitCrawlAnims(forceEndProne)

isCrawling = false
inAction = false
forceEndProne = false
proneType = 'onfront'
SetPedConfigFlag(PlayerPedId(), 48, false) -- CPED_CONFIG_FLAG_BlockWeaponSwitching

RemoveAnimDict('move_crawl')
RemoveAnimDict('move_crawlprone2crawlfront')
end

local function CrawlKeyPressed()
if not LocalPlayer.state.canEmote then return end

if inAction then
return
end

if IsPauseMenuActive() or IsNuiFocused() then
return
end

if IsProne then
IsProne = false
return
end

if IsInAnimation then
EmoteCancel()
end

local wasCrouched = false
if isCrouched then
isCrouched = false
wasCrouched = true
end

local playerPed = PlayerPedId()
if not CanPlayerCrouchCrawl(playerPed) or IsEntityInWater(playerPed) or not IsPedHuman(playerPed) then
return
end
inAction = true

if Pointing then
Pointing = false
end

if InHandsup then
return
end

if IsInActionWithErrorMessage({['IsProne'] = true}) then
return
end

IsProne = true
SetPedConfigFlag(playerPed, 48, true) -- CPED_CONFIG_FLAG_BlockWeaponSwitching

if GetPedStealthMovement(playerPed) ~= 0 then
SetPedStealthMovement(playerPed, false, 'DEFAULT_ACTION')
Wait(100)
end

LoadAnim('move_crawl')
LoadAnim('move_crawlprone2crawlfront')

if ShouldPlayerDiveToCrawl(playerPed) then
PlayAnimOnce(playerPed, 'explosions', 'react_blown_forwards', nil, 3.0)
Wait(1100)
elseif wasCrouched then
PlayAnimOnce(playerPed, 'amb@world_human_sunbathe@male@front@enter', 'enter', nil, nil, -1, 0.3)
Wait(1500)
else
PlayAnimOnce(playerPed, 'amb@world_human_sunbathe@male@front@enter', 'enter')
Wait(3000)
end

if CanPlayerCrouchCrawl(playerPed) and not IsEntityInWater(playerPed) then
PlayIdleCrawlAnim(playerPed, nil, 3.0)
end

TriggerEvent('crouch_crawl:onCrawl', true)

inAction = false
CreateThread(CrawlLoop)
end


-- Commands & KeyMapping --
if Config.CrouchEnabled then
if Config.CrouchKeybindEnabled then
RegisterKeyMapping('+crouch', Translate('register_crouch'), 'keyboard', Config.CrouchKeybind)
RegisterCommand('+crouch', function() CrouchKeyPressed() end, false)
RegisterCommand('-crouch', function() end, false) -- This needs to be here to prevent warnings in chat
end
RegisterCommand('crouch', function()
if not LocalPlayer.state.canEmote then return end

if isCrouched then
isCrouched = false
return
end

AttemptCrouch(PlayerPedId())
end, false)
TriggerEvent('chat:addSuggestion', '/crouch', Translate('crouch'))
end

if Config.CrawlEnabled then
if Config.CrawlKeybindEnabled then
RegisterKeyMapping('+crawl', Translate('register_crawl'), 'keyboard', Config.CrawlKeybind)
RegisterCommand('+crawl', function() CrawlKeyPressed() end, false)
RegisterCommand('-crawl', function() end, false) -- This needs to be here to prevent warnings in chat
end
RegisterCommand('crawl', function() CrawlKeyPressed() end, false)
TriggerEvent('chat:addSuggestion', '/crawl', Translate('crawl'))
end

---Returns if the player is crouched
---@return boolean
local function IsPlayerCrouched()
return isCrouched
end
CreateExport('IsPlayerCrouched', IsPlayerCrouched)

---Returns if the player is prone (both when laying still and when moving)
---@return boolean
local function IsPlayerProne()
return IsProne
end
CreateExport('IsPlayerProne', IsPlayerProne)

---Returns if the player is crawling (only when moving forward/backward)
---@return boolean
local function IsPlayerCrawling()
return isCrawling
end
CreateExport('IsPlayerCrawling', IsPlayerCrawling)

---Returns either "onfront" or "onback", this can be used to check if the player is on his back or on his stomach. NOTE: This will still return a string even if the player is not prone. Use IsPlayerProne() to check if the player is prone.
---@return string
local function GetPlayerProneType()
return proneType
end
CreateExport('GetPlayerProneType', GetPlayerProneType)

-- Useful to call if the player gets handcuffed etc.
CreateExport('StopPlayerProne', stopPlayerProne)

View file

@ -0,0 +1,895 @@
-- 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)

View file

@ -0,0 +1,521 @@
local isSearching = false
local rightPosition = { x = 1430, y = 200 }
local leftPosition = { x = 0, y = 200 }
local menuPosition = { x = 0, y = 200 }
local menuHeader = "shopui_title_sm_hangar"

if GetAspectRatio() > 2.0 then
rightPosition = { x = 1200, y = 100 }
leftPosition = { x = -250, y = 100 }
end

if Config.MenuPosition then
if Config.MenuPosition == "left" then
menuPosition = leftPosition
elseif Config.MenuPosition == "right" then
menuPosition = rightPosition
end
end

if Config.CustomMenuEnabled then
local txd = CreateRuntimeTxd('Custom_Menu_Head')
CreateRuntimeTextureFromImage(txd, 'Custom_Menu_Head', 'header.png')
menuHeader = "Custom_Menu_Head"
end

local _menuPool = NativeUI.CreatePool()
local mainMenu = NativeUI.CreateMenu(Config.MenuTitle or "", "", menuPosition["x"], menuPosition["y"], menuHeader, menuHeader)
_menuPool:Add(mainMenu)

local sharemenu, shareddancemenu, infomenu

local EmoteTable = {}
local DanceTable = {}
local AnimalTable = {}
local PropTable = {}
local WalkTable = {}
local FaceTable = {}
local ShareTable = {}


function AddEmoteMenu(menu)
local submenu = _menuPool:AddSubMenu(menu, Translate('emotes'), "", true, true)
if Config.Search then
submenu:AddItem(NativeUI.CreateItem(Translate('searchemotes'), ""))
table.insert(EmoteTable, Translate('searchemotes'))
end
local dancemenu = _menuPool:AddSubMenu(submenu, Translate('danceemotes'), "", true, true)
local animalmenu
if Config.AnimalEmotesEnabled then
animalmenu = _menuPool:AddSubMenu(submenu, Translate('animalemotes'), "", true, true)
table.insert(EmoteTable, Translate('animalemotes'))
end
local propmenu = _menuPool:AddSubMenu(submenu, Translate('propemotes'), "", true, true)
table.insert(EmoteTable, Translate('danceemotes'))
table.insert(EmoteTable, Translate('danceemotes'))

if Config.SharedEmotesEnabled then
sharemenu = _menuPool:AddSubMenu(submenu, Translate('shareemotes'),
Translate('shareemotesinfo'), true, true)
shareddancemenu = _menuPool:AddSubMenu(sharemenu, Translate('sharedanceemotes'), "", true, true)
table.insert(ShareTable, 'none')
table.insert(EmoteTable, Translate('shareemotes'))
end

if Config.Keybinding then
table.insert(EmoteTable, "keybinds")
submenu:AddItem(NativeUI.CreateItem(Translate('keybinds'), Translate('keybindsinfo') .. " /emotebind [~y~num4-9~w~] [~g~emotename~w~]"))
end

for a, b in PairsByKeys(RP.Emotes) do
local x, y, z = table.unpack(b)
submenu:AddItem(NativeUI.CreateItem(z, "/e (" .. a .. ")"))
table.insert(EmoteTable, a)
end

for a, b in PairsByKeys(RP.Dances) do
local name = '🤼 ' .. b[3]
dancemenu:AddItem(NativeUI.CreateItem(name, "/e (" .. a .. ")"))
if Config.SharedEmotesEnabled then
shareddancemenu:AddItem(NativeUI.CreateItem(name, "/nearby (" .. a .. ")"))
end
table.insert(DanceTable, a)
end

if Config.AnimalEmotesEnabled then
for a, b in PairsByKeys(RP.AnimalEmotes) do
local name = '🐶 ' .. b[3]
animalmenu:AddItem(NativeUI.CreateItem(name, "/e (" .. a .. ")"))
table.insert(AnimalTable, a)
end
end

if Config.SharedEmotesEnabled then
for a, b in PairsByKeys(RP.Shared) do
local name = b[3]
local shareitem = NativeUI.CreateItem(name, "/nearby (~g~" .. a .. "~w~)" .. (otheremotename and " " .. Translate('makenearby') .. " (~y~" .. otheremotename .. "~w~)" or ""))
sharemenu:AddItem(shareitem)
table.insert(ShareTable, a)
end
end

for a, b in PairsByKeys(RP.PropEmotes) do
local name = '📦 ' .. b[3]
local propitem = b.AnimationOptions.PropTextureVariations and
NativeUI.CreateListItem(name, b.AnimationOptions.PropTextureVariations, 1, "/e (" .. a .. ")") or
NativeUI.CreateItem(name, "/e (" .. a .. ")")

propmenu:AddItem(propitem)

table.insert(PropTable, a)
end

-- Ped Emote on Change Index

dancemenu.OnIndexChange = function(_, newindex)
ClearPedTaskPreview()
EmoteMenuStartClone(DanceTable[newindex], "Dances")
end

propmenu.OnIndexChange = function(_, newindex)
ClearPedTaskPreview()
EmoteMenuStartClone(PropTable[newindex], "PropEmotes")
end

submenu.OnIndexChange = function(_, newindex)
if newindex > 5 then
ClearPedTaskPreview()
EmoteMenuStartClone(EmoteTable[newindex], "Emotes")
end
end

dancemenu.OnMenuClosed = function()
ClearPedTaskPreview()
end

dancemenu.OnItemSelect = function(_, _, index)
EmoteMenuStart(DanceTable[index], "Dances")
end

if Config.AnimalEmotesEnabled then
animalmenu.OnItemSelect = function(_, _, index)
EmoteMenuStart(AnimalTable[index], "AnimalEmotes")
end
end

if Config.SharedEmotesEnabled then
sharemenu.OnItemSelect = function(_, _, index)
if ShareTable[index] ~= 'none' then
local target, distance = GetClosestPlayer()
if (distance ~= -1 and distance < 3) then
TriggerServerEvent("rpemotes:server:requestEmote", GetPlayerServerId(target), ShareTable[index])
SimpleNotify(Translate('sentrequestto') .. GetPlayerName(target))
else
SimpleNotify(Translate('nobodyclose'))
end
end
end

shareddancemenu.OnItemSelect = function(_, _, index)
local target, distance = GetClosestPlayer()
if (distance ~= -1 and distance < 3) then
TriggerServerEvent("rpemotes:server:requestEmote", GetPlayerServerId(target), DanceTable[index], 'Dances')
SimpleNotify(Translate('sentrequestto') .. GetPlayerName(target))
else
SimpleNotify(Translate('nobodyclose'))
end
end
end

propmenu.OnItemSelect = function(_, _, index)
EmoteMenuStart(PropTable[index], "PropEmotes")
end

propmenu.OnListSelect = function(_, item, itemIndex, listIndex)
EmoteMenuStart(PropTable[itemIndex], "PropEmotes", item:IndexToItem(listIndex).Value)
end

submenu.OnItemSelect = function(_, _, index)
if Config.Search and EmoteTable[index] == Translate('searchemotes') then
EmoteMenuSearch(submenu)
else
EmoteMenuStart(EmoteTable[index], "Emotes")
end
end

submenu.OnMenuClosed = function()
if not isSearching then
ClosePedMenu()
end
end

end

if Config.Search then
local ignoredCategories = {
["Walks"] = true,
["Expressions"] = true,
["Shared"] = not Config.SharedEmotesEnabled
}

function EmoteMenuSearch(lastMenu)
ClosePedMenu()
AddTextEntry("PM_NAME_CHALL", Translate('searchinputtitle'))
DisplayOnscreenKeyboard(1, "PM_NAME_CHALL", "", "", "", "", "", 30)
while UpdateOnscreenKeyboard() == 0 do
DisableAllControlActions(0)
Wait(100)
end
local input = GetOnscreenKeyboardResult()
if input ~= nil then
local results = {}
for a, b in pairs(RP) do
if not ignoredCategories[b.category] then
if string.find(string.lower(a), string.lower(input)) or (b[3] ~= nil and string.find(string.lower(b[3]), string.lower(input))) then
table.insert(results, { table = b.category, name = a, data = b })
end
end
end

if #results > 0 then
isSearching = true

local searchMenu = _menuPool:AddSubMenu(lastMenu, string.format('%s '..Translate('searchmenudesc')..' ~r~%s~w~', #results, input), "", true, true)
local sharedDanceMenu

if Config.SharedEmotesEnabled then
sharedDanceMenu = _menuPool:AddSubMenu(searchMenu, Translate('sharedanceemotes'), "", true, true)
end

table.sort(results, function(a, b) return a.name < b.name end)
for k, v in pairs(results) do
local desc = ""
if v.table == "Shared" then
local otheremotename = v.data[4]
if otheremotename == nil then
desc = "/nearby (~g~" .. v.name .. "~w~)"
else
desc = "/nearby (~g~" .. v.name .. "~w~) " .. Translate('makenearby') .. " (~y~" .. otheremotename .. "~w~)"
end
else
desc = "/e (" .. v.name .. ")"
end

if v.data.AnimationOptions and v.data.AnimationOptions.PropTextureVariations then
searchMenu:AddItem(NativeUI.CreateListItem(v.data[3], v.data.AnimationOptions.PropTextureVariations, 1, desc))
else
searchMenu:AddItem(NativeUI.CreateItem(v.data[3], desc))
end

if v.table == "Dances" and Config.SharedEmotesEnabled then
sharedDanceMenu:AddItem(NativeUI.CreateItem(v.data[3], ""))
end
end

searchMenu.OnMenuChanged = function()
isSearching = false
ShowPedMenu()
end


searchMenu.OnIndexChange = function(_, newindex)
local data = results[newindex]

ClearPedTaskPreview()
EmoteMenuStartClone(data.name, data.data.category)
end


searchMenu.OnItemSelect = function(_, _, index)
local data = results[index]

if data == Translate('sharedanceemotes') then return end

if data.table == "Shared" then
local target, distance = GetClosestPlayer()
if (distance ~= -1 and distance < 3) then
TriggerServerEvent("rpemotes:server:requestEmote", GetPlayerServerId(target), data.name)
SimpleNotify(Translate('sentrequestto') .. GetPlayerName(target))
else
SimpleNotify(Translate('nobodyclose'))
end
else
EmoteMenuStart(data.name, data.data.category)
end
end

searchMenu.OnListSelect = function(_, item, itemIndex, listIndex)
EmoteMenuStart(results[itemIndex].name, "PropEmotes", item:IndexToItem(listIndex).Value)
end

if Config.SharedEmotesEnabled then
if #sharedDanceMenu.Items > 0 then
table.insert(results, 1, Translate('sharedanceemotes'))
sharedDanceMenu.OnItemSelect = function(_, _, index)
if not LocalPlayer.state.canEmote then return end

local data = results[index]
local target, distance = GetClosestPlayer()
if (distance ~= -1 and distance < 3) then
TriggerServerEvent("rpemotes:server:requestEmote", GetPlayerServerId(target), data.name, 'Dances')
SimpleNotify(Translate('sentrequestto') .. GetPlayerName(target))
else
SimpleNotify(Translate('nobodyclose'))
end
end
else
sharedDanceMenu:Clear()
searchMenu:RemoveItemAt(1)
end
end

searchMenu.OnMenuClosed = function()
searchMenu:Clear()
lastMenu:RemoveItemAt(#lastMenu.Items)
_menuPool:RefreshIndex()
results = {}
end

_menuPool:RefreshIndex()
_menuPool:CloseAllMenus()
searchMenu:Visible(true)
ShowPedMenu()
else
SimpleNotify(string.format(Translate('searchnoresult')..' ~r~%s~w~', input))
end
end
end
end

function AddCancelEmote(menu)
local newitem = NativeUI.CreateItem(Translate('cancelemote'), Translate('cancelemoteinfo'))
menu:AddItem(newitem)
newitem.Activated = function()
EmoteCancel()
DestroyAllProps()
end
end

ShowPedPreview = function(menu)
menu.OnItemSelect = function(_, _, index)
if index == 1 then
isSearching = false
ShowPedMenu()
elseif index == 4 then
ShowPedMenu(true)
end
end
end

function AddWalkMenu(menu)
local submenu = _menuPool:AddSubMenu(menu, Translate('walkingstyles'), "", true, true)

local walkreset = NativeUI.CreateItem(Translate('normalreset'), Translate('resetdef'))
submenu:AddItem(walkreset)
table.insert(WalkTable, Translate('resetdef'))

local sortedWalks = {}
for a, b in PairsByKeys(RP.Walks) do
if b[1] == "move_m@injured" then
table.insert(sortedWalks, 1, {label = a, anim = b[1]})
else
table.insert(sortedWalks, {label = a, anim = b[1]})
end
end

for _, walk in ipairs(sortedWalks) do
submenu:AddItem(NativeUI.CreateItem(walk.label, "/walk (" .. string.lower(walk.label) .. ")"))
table.insert(WalkTable, walk.label)
end

submenu.OnItemSelect = function(_, item, index)
if item == walkreset then
ResetWalk()
DeleteResourceKvp("walkstyle")
else
WalkMenuStart(WalkTable[index])
end
end
end

function AddFaceMenu(menu)
local submenu = _menuPool:AddSubMenu(menu, Translate('moods'), "", true, true)

local facereset = NativeUI.CreateItem(Translate('normalreset'), Translate('resetdef'))
submenu:AddItem(facereset)
table.insert(FaceTable, "")

for name, data in PairsByKeys(RP.Expressions) do
local faceitem = NativeUI.CreateItem(data[2] or name, "")
submenu:AddItem(faceitem)
table.insert(FaceTable, name)
end


submenu.OnIndexChange = function(_, newindex)
EmoteMenuStartClone(FaceTable[newindex], "Expressions")
end

submenu.OnItemSelect = function(_, item, index)
if item == facereset then
DeleteResourceKvp("Expressions")
ClearFacialIdleAnimOverride(PlayerPedId())
else
EmoteMenuStart(FaceTable[index], "Expressions")
end
end

submenu.OnMenuClosed = function()
ClosePedMenu()
end
end

function AddInfoMenu(menu)
infomenu = _menuPool:AddSubMenu(menu, Translate('infoupdate'), "~h~~y~The RPEmotes Developers~h~~y~", true, true)

for _,v in ipairs(Config.Credits) do
local item = NativeUI.CreateItem(v.title,v.subtitle or "")
infomenu:AddItem(item)
end
end

function OpenEmoteMenu()
if IsEntityDead(PlayerPedId()) then
-- show in chat
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
-- show in chat
TriggerEvent('chat:addMessage', {
color = {255, 0, 0},
multiline = true,
args = {"RPEmotes", Translate('swimming')}
})
return
end
if _menuPool:IsAnyMenuOpen() then
_menuPool:CloseAllMenus()
else
mainMenu:Visible(true)
ProcessMenu()
end
end

CreateThread(function()
LoadAddonEmotes()
AddEmoteMenu(mainMenu)
AddCancelEmote(mainMenu)
if Config.PreviewPed then
ShowPedPreview(mainMenu)
end
if Config.WalkingStylesEnabled then
AddWalkMenu(mainMenu)
end
if Config.ExpressionsEnabled then
AddFaceMenu(mainMenu)
end
AddInfoMenu(mainMenu)

_menuPool:RefreshIndex()

local newRP = {}
for emoteType, content in pairs(RP) do
for emoteName, emoteData in pairs(content) do
local shouldRemove = false

if Config.AdultEmotesDisabled and emoteData.AdultAnimation then
shouldRemove = true
end
if newRP[emoteName] then
print('WARNING - Duplicate emote name found: ' .. emoteName .. ' in ' .. emoteType .. ' and ' .. newRP[emoteName].category)
end
if shouldRemove then
elseif type(emoteData) == "table" then
newRP[emoteName] = {}
for k, v in pairs(emoteData) do
newRP[emoteName][k] = v
end
newRP[emoteName].category = emoteType
else
newRP[emoteName] = { emoteData }
newRP[emoteName].category = emoteType
end
end
newRP[emoteType] = nil
end
RP = newRP
CONVERTED = true
end)

local isMenuProcessing = false
function ProcessMenu()
if isMenuProcessing then return end
isMenuProcessing = true
while _menuPool:IsAnyMenuOpen() do
_menuPool:ProcessMenus()
Wait(0)
end
isMenuProcessing = false
end

-- While ped is dead, don't show menus
CreateThread(function()
while true do
Wait(500)
if IsEntityDead(PlayerPedId()) then
_menuPool:CloseAllMenus()
end
if (IsPedSwimming(PlayerPedId()) or IsPedSwimmingUnderWater(PlayerPedId())) and not Config.AllowInWater then
-- cancel emote, destroy props and close menu
if IsInAnimation then
EmoteCancel()
end
_menuPool:CloseAllMenus()
end
end
end)

View file

@ -0,0 +1,50 @@
function SetPlayerPedExpression(expression, saveToKvp)
local emote = RP[expression]
if emote and emote.category == "Expressions" then
SetFacialIdleAnimOverride(PlayerPedId(), emote[1], 0)
if Config.PersistentExpression and saveToKvp then SetResourceKvp("expression", emote[1]) end
else
ClearFacialIdleAnimOverride(PlayerPedId())
DeleteResourceKvp("expression")
end
end

if Config.ExpressionsEnabled then
RegisterCommand('mood', function(_source, args, _raw)
local expression = FirstToUpper(string.lower(args[1]))
local emote = RP[expression]
if emote and emote.category == "Expressions" then
SetPlayerPedExpression(RP[expression][1], true)
elseif expression == "Reset" then
ClearFacialIdleAnimOverride(PlayerPedId())
DeleteResourceKvp("expression")
else
EmoteChatMessage("'" .. expression .. "' is not a valid mood")
end
end, false)

TriggerEvent('chat:addSuggestion', '/mood', 'Set your current mood/expression.',
{ { name = "expression", help = "/moods for a list of valid moods" } })
TriggerEvent('chat:addSuggestion', '/moods', 'List available walking moods/expressions.')


local function LoadPersistentExpression()
local expression = GetResourceKvpString("expression")
if expression then
Wait(2500)
SetPlayerPedExpression(expression, false)
end
end

if Config.PersistentExpression then
AddEventHandler('playerSpawned', LoadPersistentExpression)
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', LoadPersistentExpression)
RegisterNetEvent('esx:playerLoaded', LoadPersistentExpression)
end

AddEventHandler('onResourceStart', function(resource)
if resource == GetCurrentResourceName() then
LoadPersistentExpression()
end
end)
end

View file

@ -0,0 +1,86 @@
local function HandsUpLoop()
CreateThread(function()
while InHandsup do
if Config.DisabledHandsupControls then
for control, state in pairs(Config.DisabledHandsupControls) 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.HandsupInCar 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.ReplayEmoteAfterHandsup and IsInAnimation then
local emote = RP[CurrentAnimationName]
if not emote then
return
end

Wait(400)
DestroyAllProps()
OnEmotePlay(CurrentAnimationName, CurrentTextureVariation)
end
end
end

TriggerEvent('chat:addSuggestion', '/handsup', Translate('handsup'))

if Config.HandsupKeybindEnabled then
RegisterKeyMapping("handsup", Translate('register_handsup'), "keyboard", Config.HandsupKeybind)
end

CreateExport('IsPlayerInHandsUp', function()
return InHandsup
end)
end

View file

@ -0,0 +1,96 @@
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(args) end, false)
RegisterCommand('emote', function(source, args, raw) EmoteCommandStart(args) end, false)
RegisterCommand('emotecancel', function() EmoteCancel() end, false)

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

if Config.EnableCancelKeybind then
RegisterKeyMapping("emotecancel", Translate('register_cancel_emote'), "keyboard", Config.CancelEmoteKey)
end

-- BINDING EMOTES TO KEYS
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)

for i = 1, #Config.KeybindKeys do
local cmd = string.format('emoteSelect%s', i)
RegisterCommand(cmd, function()
local emote = GetResourceKvpString(string.format('%s_emob%s', Config.keybindKVP, i))
if emote and emote ~= "" then
EmoteCommandStart({ emote, 0 })
end
end, false)
RegisterKeyMapping(cmd, string.format('Emote bind %s', i), 'keyboard', Config.KeybindKeys[i])
end

function EmoteBindStart(source, args, raw)
if #args > 0 then
local numkey = tonumber(args[1])
local emote = string.lower(args[2])
if not (numkey and emote) then
DebugPrint('Invalid arguments to EmoteBindStart')
return
end
if type(numkey) == "number" then
if RP[emote] then
SetResourceKvp(string.format('%s_emob%s', Config.keybindKVP, numkey), emote)
else
EmoteChatMessage("'" .. emote .. "' " .. Translate('notvalidemote') .. "")
end
else
EmoteChatMessage("'" .. numkey .. "' " .. Translate('notvalidkey'))
end
else
DebugPrint('Invalid number of arguments to EmoteBindStart')
end
end

function ListKeybinds()
for i = 1, #Config.KeybindKeys do
local emote = GetResourceKvpString(string.format('%s_emob%s', Config.keybindKVP, i))
if emote then
EmoteChatMessage(string.format('Emote %s : %s',i, emote))
end
end
end

function DeleteEmote(args)
if #args > 0 then
local numkey = tonumber(args[1])
if type(numkey) == "number" then
DeleteResourceKvp(string.format('%s_emob%s', Config.keybindKVP, numkey))
else
EmoteChatMessage("'" .. numkey .. "' " .. Translate('notvalidkey'))
end
else
DebugPrint("invalid")
end
end
end

View file

@ -0,0 +1,245 @@
IsUsingNewscam = false

if Config.NewscamEnabled then
RegisterCommand("newscam", function()
UseNewscam()
end, false)

TriggerEvent('chat:addSuggestion', '/newscam', 'Use newscam', {})

local fov = 40.0
local index = 0
local scaleform_instructions
local scaleform_news
local prop_newscam
local msg = "YOUR TEXT HERE"
local bottom = "YOUR TEXT HERE"
local title = "YOUR TEXT HERE"
local instructions = true
local cam

local function CleanupNewscam()
ClearPedTasks(PlayerPedId())
ClearTimecycleModifier()
RenderScriptCams(false, false, 0, true, false)
SetScaleformMovieAsNoLongerNeeded(breaking_news)
SetScaleformMovieAsNoLongerNeeded(scaleform_instructions)
DestroyCam(cam, false)
if prop_newscam then
DeleteEntity(prop_newscam)
end
SetNightvision(false)
SetSeethrough(false)
end

function UseNewscam()
if IsPedSittingInAnyVehicle(PlayerPedId()) then
return
end
if IsInActionWithErrorMessage({ ['IsUsingNewscam'] = true }) then
return
end
IsUsingNewscam = not IsUsingNewscam

if IsUsingNewscam then
CreateThread(function()
DestroyAllProps()
ClearPedTasks(PlayerPedId())
RequestAnimDict("missfinale_c2mcs_1")
while not HasAnimDictLoaded("missfinale_c2mcs_1") do
Wait(5)
end

-- attach the prop to the player
local boneIndex = GetPedBoneIndex(PlayerPedId(), 28422)
local x, y, z = table.unpack(GetEntityCoords(PlayerPedId(), true))
if not HasModelLoaded("prop_v_cam_01") then
LoadPropDict("prop_v_cam_01")
end
prop_newscam = CreateObject(`prop_v_cam_01`, x, y, z + 0.2, true, true, true)
AttachEntityToEntity(prop_newscam, PlayerPedId(), boneIndex, 0.0, 0.03, 0.01, 0.0, 0.0, 0.0, true, true, false, true, 1, true)

TaskPlayAnim(PlayerPedId(), "missfinale_c2mcs_1", "fin_c2_mcs_1_camman", 5.0, 5.0, -1, 51, 0, false, false, false)
PlayAmbientSpeech1(PlayerPedId(), "GENERIC_CURSE_MED", "SPEECH_PARAMS_FORCE")
SetCurrentPedWeapon(PlayerPedId(), `WEAPON_UNARMED`, true)

RemoveAnimDict("missfinale_c2mcs_1")
SetModelAsNoLongerNeeded("prop_v_cam_01")
end)

Wait(200)
SetTimecycleModifier("default")
SetTimecycleModifierStrength(0.3)
local breaking_news = RequestScaleformMovie("breaking_news")
while not HasScaleformMovieLoaded(breaking_news) do
Wait(10)
end


PushScaleformMovieFunction(breaking_news, "breaking_news")
PopScaleformMovieFunctionVoid()

BeginScaleformMovieMethod(breaking_news, 'SET_TEXT')
PushScaleformMovieMethodParameterString(msg)
PushScaleformMovieMethodParameterString(bottom)
EndScaleformMovieMethod()

BeginScaleformMovieMethod(breaking_news, 'SET_SCROLL_TEXT')
PushScaleformMovieMethodParameterInt(0) -- top ticker
PushScaleformMovieMethodParameterInt(0) -- Since this is the first string, start at 0
PushScaleformMovieMethodParameterString(title)

EndScaleformMovieMethod()

BeginScaleformMovieMethod(breaking_news, 'DISPLAY_SCROLL_TEXT')
PushScaleformMovieMethodParameterInt(0) -- Top ticker
PushScaleformMovieMethodParameterInt(0) -- Index of string

EndScaleformMovieMethod()

scaleform_news = breaking_news

cam = CreateCam("DEFAULT_SCRIPTED_FLY_CAMERA", true)

AttachCamToEntity(cam, PlayerPedId(), 0.0, 0.0, 1.2, true)
SetCamRot(cam, 0.0, 0.0, GetEntityHeading(PlayerPedId()))
SetCamFov(cam, fov)
RenderScriptCams(true, false, 0, true, false)

scaleform_instructions = SetupButtons({
{ key = 177, text = 'exit_news' },
{ key = 19, text = 'toggle_news_vision' },
{ key = 74, text = "edit_values_newscam" },
{ key = 47, text = 'toggle_instructions' }
})

while IsUsingNewscam and not IsEntityDead(PlayerPedId()) and not IsPedSittingInAnyVehicle(PlayerPedId()) do
if IsControlJustPressed(0, 177) then
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
IsUsingNewscam = false
end

fov = HandleZoomAndCheckRotation(cam, fov)

HideHUDThisFrame()
DisableControlAction(0, 25, true) -- disable aim
DisableControlAction(0, 44, true) -- INPUT_COVER
DisableControlAction(0, 37, true) -- INPUT_SELECT_WEAPON
DisableControlAction(0, 24, true) -- Attack
DisablePlayerFiring(PlayerPedId(), true) -- Disable weapon firing


if IsControlJustPressed(0, 19) then
-- if index = 0, show the "security_camera" scaleform, if index = 1, show the "breaking_news" scaleform and reset the index to 0
if index == 0 then
index = 1
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
scaleform_news = nil
CreateThread(function()
while index == 1 do
DrawRect(0.0, 0.0, 2.0, 0.2, 0, 0, 0, 255)
DrawRect(0.0, 1.0, 2.0, 0.2, 0, 0, 0, 255)
Wait(1)
end
end)
else
index = 0
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
scaleform_news = breaking_news
end
end

if IsControlJustPressed(0, 74) then
SetMsgBottomTitle()
end

if IsControlJustPressed(0, 47) then
instructions = not instructions
PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
end

DrawScaleformMovieFullscreen(scaleform_news, 255, 255, 255, 255)
if instructions then
DrawScaleformMovieFullscreen(scaleform_instructions, 255, 255, 255, 255)
end
Wait(1)
end
end

-- RESET EVERYTHING
IsUsingNewscam = false
index = 0

CleanupNewscam()
end

function SetMsgBottomTitle()
-- keyboard input to set the message and bottom title
AddTextEntry("top", "Enter the top message of the news")
DisplayOnscreenKeyboard(1, "top", "", "", "", "", "", 200)
while (UpdateOnscreenKeyboard() == 0) do
DisableAllControlActions(0);
Wait(0);
end
if (GetOnscreenKeyboardResult()) then
title = tostring(GetOnscreenKeyboardResult())
end

AddTextEntry("bottom", "Enter the bottom title of the news")
DisplayOnscreenKeyboard(1, "bottom", "", "", "", "", "", 200)
while (UpdateOnscreenKeyboard() == 0) do
DisableAllControlActions(0);
Wait(0);
end
if (GetOnscreenKeyboardResult()) then
bottom = tostring(GetOnscreenKeyboardResult())
end

AddTextEntry("title", "Enter the title of the news")
DisplayOnscreenKeyboard(1, "title", "", "", "", "", "", 200)
while (UpdateOnscreenKeyboard() == 0) do
DisableAllControlActions(0);
Wait(0);
end
if (GetOnscreenKeyboardResult()) then
msg = tostring(GetOnscreenKeyboardResult())
end


-- reset the scaleform and set the new values
SetScaleformMovieAsNoLongerNeeded(breaking_news)
breaking_news = RequestScaleformMovie("breaking_news")
while not HasScaleformMovieLoaded(breaking_news) do
Wait(10)
end

PushScaleformMovieFunction(breaking_news, "breaking_news")
PopScaleformMovieFunctionVoid()

BeginScaleformMovieMethod(breaking_news, 'SET_TEXT')
PushScaleformMovieMethodParameterString(msg)
PushScaleformMovieMethodParameterString(bottom)
EndScaleformMovieMethod()

BeginScaleformMovieMethod(breaking_news, 'SET_SCROLL_TEXT')
PushScaleformMovieMethodParameterInt(0) -- top ticker
PushScaleformMovieMethodParameterInt(0) -- Since this is the first string, start at 0
PushScaleformMovieMethodParameterString(title)
EndScaleformMovieMethod()

BeginScaleformMovieMethod(breaking_news, 'DISPLAY_SCROLL_TEXT')
PushScaleformMovieMethodParameterInt(0) -- Top ticker
PushScaleformMovieMethodParameterInt(0) -- Index of string
EndScaleformMovieMethod()
end

AddEventHandler('onResourceStop', function(resource)
if resource == GetCurrentResourceName() then
CleanupNewscam()
end
end)

CreateExport('toggleNewscam', function()
UseNewscam()
end)
end

View file

@ -0,0 +1,35 @@
if not Config.DisableIdleCam then return end

RegisterCommand('idlecamoff', function() -- help2 31, 167, 9
TriggerEvent('chat:addMessage', {
color = {227,8,0},
multiline = true,
args = {'[RPEmotes]', 'Idle Cam Is Now Off'}
})
DisableIdleCamera(true)
SetPedCanPlayAmbientAnims(PlayerPedId(), false)
SetResourceKvpInt("idleCamToggle", 1)
end, false)

RegisterCommand('idlecamon', function() -- help2 31, 167, 9
TriggerEvent('chat:addMessage', {
color = {31,167,9},
multiline = true,
args = {'[RPEmotes]', 'Idle Cam Is Now On'}
})
DisableIdleCamera(false)
SetPedCanPlayAmbientAnims(PlayerPedId(), true)
SetResourceKvpInt("idleCamToggle", 2)
end, false)

CreateThread(function()
TriggerEvent("chat:addSuggestion", "/idlecamon", "Re-enables the idle cam")
TriggerEvent("chat:addSuggestion", "/idlecamoff", "Disables the idle cam")

local idleCamKvp = GetResourceKvpInt("idleCamToggle")
if idleCamKvp == 0 then
return
end

DisableIdleCamera(idleCamKvp == 1)
end)

View file

@ -0,0 +1,65 @@
local PlayerParticles = {}

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', nil, true)
end

AddStateBagChangeHandler('ptfx', '', function(bagName, key, value, _unused, replicated)
local plyId = tonumber(bagName:gsub('player:', ''), 10)

if (PlayerParticles[plyId] and value) or (not PlayerParticles[plyId] and not value) then return end

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
local boneIndex = stateBag.ptfxBone and GetPedBoneIndex(plyPed, stateBag.ptfxBone) or GetEntityBoneIndexByName(stateBag.ptfxName, "VFX")
local entityTarget = plyPed

if stateBag.ptfxPropNet then
local propObj = NetToObj(stateBag.ptfxPropNet)
if DoesEntityExist(propObj) then
entityTarget = propObj
end
end

PtfxThis(stateBag.ptfxAsset)

local offset = stateBag.ptfxOffset
local rot = stateBag.ptfxRot
PlayerParticles[plyId] = StartNetworkedParticleFxLoopedOnEntityBone(stateBag.ptfxName, entityTarget, offset.x, offset.y, offset.z, rot.x, rot.y, rot.z, boneIndex, (stateBag.ptfxScale or 1) + 0.0, false, false, false)

local color = stateBag.ptfxColor
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)

View file

@ -0,0 +1,124 @@
Pointing = false

local function IsPlayerAiming(player)
return IsPlayerFreeAiming(player) or IsAimCamActive() or IsAimCamThirdPersonActive()
end

local function CanPlayerPoint()
local playerPed = PlayerPedId()
local playerId = PlayerId()
if not DoesEntityExist(playerPed) or IsPedOnAnyBike(playerPed) or IsPlayerAiming(playerId) or IsPedFalling(playerPed) or IsPedInjured(playerPed) or IsPedInMeleeCombat(playerPed) or IsPedRagdoll(playerPed) or not IsPedHuman(playerPed) then
return false
end

return true
end

local function PointingStopped()
local playerPed = PlayerPedId()

RequestTaskMoveNetworkStateTransition(playerPed, 'Stop')
SetPedConfigFlag(playerPed, 36, false)
if not IsPedInjured(playerPed) then
ClearPedSecondaryTask(playerPed)
end
RemoveAnimDict("anim@mp_point")
if Config.ReplayEmoteAfterPointing and IsInAnimation then
local emote = RP[CurrentAnimationName]
if not emote then
return
end

Wait(400)
DestroyAllProps()
OnEmotePlay(CurrentAnimationName, CurrentTextureVariation)
end
end

local function PointingThread()
CreateThread(function()
local playerPed = PlayerPedId()

while Pointing do
Wait(0)

if not CanPlayerPoint() then
Pointing = false
break
end

local camPitch = GetGameplayCamRelativePitch()
if camPitch < -70.0 then
camPitch = -70.0
elseif camPitch > 42.0 then
camPitch = 42.0
end

camPitch = (camPitch + 70.0) / 112.0

local camHeading = GetGameplayCamRelativeHeading()
local cosCamHeading = math.cos(camHeading)
local sinCamHeading = math.sin(camHeading)

if camHeading < -180.0 then
camHeading = -180.0
elseif camHeading > 180.0 then
camHeading = 180.0
end

camHeading = (camHeading + 180.0) / 360.0
local coords = GetOffsetFromEntityInWorldCoords(playerPed, (cosCamHeading * -0.2) - (sinCamHeading * (0.4 * camHeading + 0.3)), (sinCamHeading * -0.2) + (cosCamHeading * (0.4 * camHeading + 0.3)), 0.6)
local _, blocked = GetShapeTestResult(StartShapeTestCapsule(coords.x, coords.y, coords.z - 0.2, coords.x, coords.y, coords.z + 0.2, 0.4, 95, playerPed, 7))

SetTaskMoveNetworkSignalFloat(playerPed, 'Pitch', camPitch)
SetTaskMoveNetworkSignalFloat(playerPed, 'Heading', (camHeading * -1.0) + 1.0)
SetTaskMoveNetworkSignalBool(playerPed, 'isBlocked', blocked)
SetTaskMoveNetworkSignalBool(playerPed, 'isFirstPerson', GetCamViewModeForContext(GetCamActiveViewModeContext()) == 4)
end

PointingStopped()
end)
end

local function StartPointing()
if IsInActionWithErrorMessage() then
return
end

if not CanPlayerPoint() then
return
end

Pointing = not Pointing

if Pointing and LoadAnim("anim@mp_point") then
SetPedConfigFlag(PlayerPedId(), 36, true)
TaskMoveNetworkByName(PlayerPedId(), 'task_mp_pointing', 0.5, false, 'anim@mp_point', 24)
DestroyAllProps()
PointingThread()
end
end


-- Commands & KeyMapping --
if Config.PointingEnabled then
RegisterCommand('pointing', function()
if IsPedInAnyVehicle(PlayerPedId(), false) and not Config.PointingInCar then
return
end
StartPointing()
end, false)

if Config.PointingKeybindEnabled then
RegisterKeyMapping("pointing", Translate('register_pointing'), "keyboard", Config.PointingKeybind)
end

TriggerEvent('chat:addSuggestion', '/pointing', Translate('pointing'))
end


---@return boolean
local function IsPlayerPointing()
return Pointing
end
CreateExport('IsPlayerPointing', IsPlayerPointing)

View file

@ -0,0 +1,33 @@
if Config.RagdollEnabled then
RegisterCommand('+ragdoll', function() Ragdoll() end, false)
RegisterCommand('-ragdoll', function() StopRagdoll() end, false)
RegisterKeyMapping('+ragdoll', Translate('register_ragdoll'), 'keyboard', Config.RagdollKeybind)

local isRagdolling = true
function Ragdoll()
if IsInAnimation then return end

local ped = PlayerPedId()
if not IsPedOnFoot(ped) then return end

if Config.RagdollAsToggle then
isRagdolling = not isRagdolling
else
isRagdolling = true
end

while isRagdolling do
ped = PlayerPedId()
SetPedRagdollForceFall(ped)
ResetPedRagdollTimer(ped)
SetPedToRagdoll(ped, 1000, 1000, 3, false, false, false)
ResetPedRagdollTimer(ped)
Wait(0)
end
end

function StopRagdoll()
if Config.RagdollAsToggle then return end
isRagdolling = false
end
end

View file

@ -0,0 +1,164 @@
local isRequestAnim = false
local targetPlayerId

if Config.SharedEmotesEnabled then
RegisterCommand('nearby', function(source, args, raw)
if not LocalPlayer.state.canEmote then return end
if IsPedInAnyVehicle(PlayerPedId(), true) then
return EmoteChatMessage(Translate('not_in_a_vehicle'))
end

if #args > 0 then
local emotename = string.lower(args[1])
local target, distance = GetClosestPlayer()
if (distance ~= -1 and distance < 3) then
if RP[emotename] ~= nil and RP[emotename].category == "Shared" then
local _, _, ename = table.unpack(RP[emotename])
TriggerServerEvent("rpemotes:server:requestEmote", GetPlayerServerId(target), emotename)
SimpleNotify(Translate('sentrequestto') ..
GetPlayerName(target) .. " ~w~(~g~" .. ename .. "~w~)")
else
EmoteChatMessage("'" .. emotename .. "' " .. Translate('notvalidsharedemote') .. "")
end
else
SimpleNotify(Translate('nobodyclose'))
end
else
NearbysOnCommand()
end
end, false)
end

RegisterNetEvent("rpemotes:client:syncEmote", function(emote, player)
EmoteCancel()
Wait(300)
targetPlayerId = player
local plyServerId = GetPlayerFromServerId(player)

if IsPedInAnyVehicle(GetPlayerPed(plyServerId ~= 0 and plyServerId or GetClosestPlayer()), true) then
return EmoteChatMessage(Translate('not_in_a_vehicle'))
end

if RP[emote] then
local options = RP[emote].AnimationOptions
if options and options.Attachto then
local targetEmote = RP[emote][4]
if not targetEmote or not RP[targetEmote] or not RP[targetEmote].AnimationOptions or not RP[targetEmote].AnimationOptions.Attachto then
local ped = PlayerPedId()
local pedInFront = GetPlayerPed(plyServerId ~= 0 and plyServerId or GetClosestPlayer())

AttachEntityToEntity(
ped,
pedInFront,
GetPedBoneIndex(pedInFront, options.bone or -1),
options.xPos or 0.0,
options.yPos or 0.0,
options.zPos or 0.0,
options.xRot or 0.0,
options.yRot or 0.0,
options.zRot or 0.0,
false,
false,
false,
true,
1,
true
)
end
end

OnEmotePlay(emote)
return
else
DebugPrint("rpemotes:client:syncEmote : Emote not found")
end
end)

RegisterNetEvent("rpemotes:client:syncEmoteSource", function(emote, player)
local ped = PlayerPedId()
local plyServerId = GetPlayerFromServerId(player)
local pedInFront = GetPlayerPed(plyServerId ~= 0 and plyServerId or GetClosestPlayer())

if IsPedInAnyVehicle(ped, true) or IsPedInAnyVehicle(pedInFront, true) then
return EmoteChatMessage(Translate('not_in_a_vehicle'))
end

local options = RP[emote] and RP[emote].AnimationOptions
if options then
if (options.Attachto) then
AttachEntityToEntity(
ped,
pedInFront,
GetPedBoneIndex(pedInFront, options.bone or -1),
options.xPos or 0.0,
options.yPos or 0.0,
options.zPos or 0.0,
options.xRot or 0.0,
options.yRot or 0.0,
options.zRot or 0.0,
false,
false,
false,
true,
1,
true
)
end
end

local coords = GetOffsetFromEntityInWorldCoords(pedInFront, (options and options.SyncOffsetSide or 0) + 0.0, (options and options.SyncOffsetFront or 1) + 0.0, (options and options.SyncOffsetHeight or 0) + 0.0)
local heading = GetEntityHeading(pedInFront)
SetEntityHeading(ped, heading - (options and options.SyncOffsetHeading or 180) + 0.0)
SetEntityCoordsNoOffset(ped, coords.x, coords.y, coords.z)
EmoteCancel()
Wait(300)

targetPlayerId = player
if RP[emote] ~= nil then
OnEmotePlay(emote)
return
end
end)

RegisterNetEvent("rpemotes:client:cancelEmote", function(player)
if targetPlayerId and targetPlayerId == player then
targetPlayerId = nil
EmoteCancel()
end
end)

function CancelSharedEmote()
if targetPlayerId then
TriggerServerEvent("rpemotes:server:cancelEmote", targetPlayerId)
targetPlayerId = nil
end
end

RegisterNetEvent("rpemotes:client:requestEmote", function(emotename, etype, target)
isRequestAnim = true

local displayed = RP[emotename] and select(3, table.unpack(RP[emotename]))

PlaySound(-1, "NAV", "HUD_AMMO_SHOP_SOUNDSET", false, 0, true)
SimpleNotify(Translate('doyouwanna') .. displayed .. "~w~)")
-- The player has now 10 seconds to accept the request
local timer = 10 * 1000
while isRequestAnim do
Wait(5)
timer = timer - 5
if timer <= 0 then
isRequestAnim = false
SimpleNotify(Translate('refuseemote'))
end

if IsControlJustPressed(1, 246) then
isRequestAnim = false

local otheremote = RP[emotename] and RP[emotename][4] or emotename
TriggerServerEvent("rpemotes:server:confirmEmote", target, emotename, otheremote)
elseif IsControlJustPressed(1, 182) then
isRequestAnim = false
SimpleNotify(Translate('refuseemote'))
end
end
end)

View file

@ -0,0 +1,395 @@
-- You can edit this function to add support for your favorite notification system
function SimpleNotify(message)
if Config.NotificationsAsChatMessage then
TriggerEvent("chat:addMessage", { color = { 255, 255, 255 }, args = { tostring(message) } })
else
BeginTextCommandThefeedPost("STRING")
AddTextComponentSubstringPlayerName(message)
EndTextCommandThefeedPostTicker(true, true)
end
end

-- Don't touch after this line if you don't know what you're doing
CreateExport = function(name, func)
AddEventHandler('__cfx_export_rpemotes_'..name, function(setCb)
setCb(function(...)
return func(...)
end)
end)
exports(name, func)
end

function DebugPrint(...)
if Config.DebugDisplay then
print(...)
end
end

function FirstToUpper(str)
return (str:gsub("^%l", string.upper))
end

function IsPlayerAiming(player)
return (IsPlayerFreeAiming(player) or IsAimCamActive() or IsAimCamThirdPersonActive()) and
tonumber(GetSelectedPedWeapon(player)) ~= tonumber(GetHashKey("WEAPON_UNARMED"))
end

function CanPlayerCrouchCrawl(playerPed)
if not IsPedOnFoot(playerPed) or IsPedJumping(playerPed) or IsPedFalling(playerPed) or IsPedInjured(playerPed) or IsPedInMeleeCombat(playerPed) or IsPedRagdoll(playerPed) then
return false
end

return true
end

function PlayAnimOnce(playerPed, animDict, animName, blendInSpeed, blendOutSpeed, duration, startTime)
LoadAnim(animDict)
TaskPlayAnim(playerPed, animDict, animName, blendInSpeed or 2.0, blendOutSpeed or 2.0, duration or -1, 0,
startTime or 0.0, false, false, false)
RemoveAnimDict(animDict)
end

function ChangeHeadingSmooth(playerPed, amount, time)
local times = math.abs(amount)
local test = amount / times
local wait = time / times

for _i = 1, times do
Wait(wait)
SetEntityHeading(playerPed, GetEntityHeading(playerPed) + test)
end
end

function EmoteChatMessage(msg, multiline)
if msg then
TriggerEvent("chat:addMessage", {
multiline = multiline == true or false,
color = { 255, 255, 255 },
args = { "^1Help^0", tostring(msg) }
})
end
end

function PairsByKeys(t, f)
local a = {}
for n in pairs(t) do
table.insert(a, n)
end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function() -- iterator function
i = i + 1
if a[i] == nil then
return nil
else
return a[i], t[a[i]]
end
end
return iter
end

function LoadAnim(dict)
if not DoesAnimDictExist(dict) then
return false
end

local timeout = 2000
while not HasAnimDictLoaded(dict) and timeout > 0 do
RequestAnimDict(dict)
Wait(5)
timeout = timeout - 5
end
if timeout == 0 then
DebugPrint("Loading anim dict " .. dict .. " timed out")
return false
else
return true
end
end

function LoadPropDict(model)
if not HasModelLoaded(GetHashKey(model)) then
RequestModel(GetHashKey(model))
local timeout = 2000
while not HasModelLoaded(GetHashKey(model)) and timeout > 0 do
Wait(5)
timeout = timeout - 5
end
if timeout == 0 then
DebugPrint("Loading model " .. model .. " timed out")
return
end
end
end

function TableHasKey(table, key)
return table[key] ~= nil
end

function RequestWalking(set)
local timeout = GetGameTimer() + 5000
while not HasAnimSetLoaded(set) and GetGameTimer() < timeout do
RequestAnimSet(set)
Wait(5)
end
end

function GetPedInFront()
local player = PlayerId()
local plyPed = GetPlayerPed(player)
local plyPos = GetEntityCoords(plyPed, false)
local plyOffset = GetOffsetFromEntityInWorldCoords(plyPed, 0.0, 1.3, 0.0)
local rayHandle = StartShapeTestCapsule(plyPos.x, plyPos.y, plyPos.z, plyOffset.x, plyOffset.y, plyOffset.z, 10.0, 12
, plyPed, 7)
local _, _, _, _, ped2 = GetShapeTestResult(rayHandle)
return ped2
end

function NearbysOnCommand(source, args, raw)
local NearbysCommand = ""
for a, b in PairsByKeys(RP) do
if type(b) == "table" and b.category == "Shared" then
NearbysCommand = NearbysCommand .. a .. ", "
end
end
EmoteChatMessage(NearbysCommand)
EmoteChatMessage(Translate('emotemenucmd'))
end

function GetClosestPlayer()
local players = GetPlayers()
local closestDistance = -1
local closestPlayer
local ped = PlayerPedId()
local pedCoords = GetEntityCoords(ped, false)

for index, value in ipairs(players) do
local target = GetPlayerPed(value)
if (target ~= ped) then
local targetCoords = GetEntityCoords(GetPlayerPed(value), false)
local distance = GetDistanceBetweenCoords(targetCoords["x"], targetCoords["y"], targetCoords["z"],
pedCoords["x"], pedCoords["y"], pedCoords["z"], true)
if (closestDistance == -1 or closestDistance > distance) then
closestPlayer = value
closestDistance = distance
end
end
end
return closestPlayer, closestDistance
end

function GetPlayers()
local players = {}

for i = 0, 255 do
if NetworkIsPlayerActive(i) then
table.insert(players, i)
end
end

return players
end

-- Function that'll check if player is already proning, using news cam or else

---@param ignores? table | nil key string is the ignored value
function IsInActionWithErrorMessage(ignores)
if ignores then DebugPrint(ignores) end
DebugPrint('IsProne', IsProne)
DebugPrint('IsUsingNewscam', IsUsingNewscam)
DebugPrint('IsUsingBinoculars', IsUsingBinoculars)
if (ignores == nil) then ignores = {} end

if not ignores['IsProne'] and IsProne then
EmoteChatMessage(Translate('no_anim_crawling'))
return true
end
if not ignores['IsUsingNewscam'] and IsUsingNewscam then
-- TODO: use specific error message
EmoteChatMessage(Translate('no_anim_right_now'))
return true
end
if not ignores['IsUsingBinoculars'] and IsUsingBinoculars then
-- TODO: use specific error message
EmoteChatMessage(Translate('no_anim_right_now'))
return true
end

return false
end

function HideHUDThisFrame()
HideHelpTextThisFrame()
HideHudAndRadarThisFrame()
HideHudComponentThisFrame(19) -- weapon wheel
HideHudComponentThisFrame(1) -- Wanted Stars
HideHudComponentThisFrame(2) -- Weapon icon
HideHudComponentThisFrame(3) -- Cash
HideHudComponentThisFrame(4) -- MP CASH
HideHudComponentThisFrame(13) -- Cash Change
HideHudComponentThisFrame(11) -- Floating Help Text
HideHudComponentThisFrame(12) -- more floating help text
HideHudComponentThisFrame(15) -- Subtitle Text
HideHudComponentThisFrame(18) -- Game Stream
end

function SetupButtons(button)
local scaleform = RequestScaleformMovie("instructional_buttons")
while not HasScaleformMovieLoaded(scaleform) do
Wait(10)
end
PushScaleformMovieFunction(scaleform, "CLEAR_ALL")
PopScaleformMovieFunctionVoid()

PushScaleformMovieFunction(scaleform, "SET_CLEAR_SPACE")
PushScaleformMovieFunctionParameterInt(200)
PopScaleformMovieFunctionVoid()

for i, btn in pairs(button) do
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
PushScaleformMovieFunctionParameterInt(i - 1)
ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(0, btn.key, true))
BeginTextCommandScaleformString("STRING")
AddTextComponentScaleform(Translate(btn.text))
EndTextCommandScaleformString()
PopScaleformMovieFunctionVoid()
end

PushScaleformMovieFunction(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS")
PopScaleformMovieFunctionVoid()

return scaleform
end

function HandleZoomAndCheckRotation(cam, fov)
local zoomspeed = 10.0 -- camera zoom speed
local lPed = PlayerPedId()

local fov_max = 70.0
local fov_min = 10.0 -- max zoom level (smaller fov is more zoom)
local speed_lr = 8.0 -- speed by which the camera pans left-right
local speed_ud = 8.0 -- speed by which the camera pans up-down

local zoomvalue = (1.0 / (fov_max - fov_min)) * (fov - fov_min)
local rightAxisX = GetDisabledControlNormal(0, 220)
local rightAxisY = GetDisabledControlNormal(0, 221)
local rotation = GetCamRot(cam, 2)

if rightAxisX ~= 0.0 or rightAxisY ~= 0.0 then
local new_z = rotation.z + rightAxisX * -1.0 * (speed_ud) * (zoomvalue + 0.1)
local new_x = math.max(math.min(20.0, rotation.x + rightAxisY * -1.0 * (speed_lr) * (zoomvalue + 0.1)), -29.5)
SetCamRot(cam, new_x, 0.0, new_z, 2)
end

if not (IsPedSittingInAnyVehicle(lPed)) then
if IsControlJustPressed(0, 241) then -- Scrollup
fov = math.max(fov - zoomspeed, fov_min)
end
if IsControlJustPressed(0, 242) then
fov = math.min(fov + zoomspeed, fov_max) -- ScrollDown
end
local current_fov = GetCamFov(cam)
if math.abs(fov - current_fov) < 0.1 then
fov = current_fov
end
SetCamFov(cam, current_fov + (fov - current_fov) * 0.05)
else
if IsControlJustPressed(0, 17) then -- Scrollup
fov = math.max(fov - zoomspeed, fov_min)
end
if IsControlJustPressed(0, 16) then
fov = math.min(fov + zoomspeed, fov_max) -- ScrollDown
end
local current_fov = GetCamFov(cam)
if math.abs(fov - current_fov) < 0.1 then -- the difference is too small, just set the value directly to avoid unneeded updates to FOV of order 10^-5
fov = current_fov
end
SetCamFov(cam, current_fov + (fov - current_fov) * 0.05) -- Smoothing of camera zoom
end

return fov
end

----------------------------------------------------------------------

ShowPed = false

function ShowPedMenu(zoom)
if not Config.PreviewPed then return end

if not ShowPed then
CreateThread(function()
local playerPed = PlayerPedId()
local coords = GetEntityCoords(playerPed) - vector3(0.0, 0.0, 10.0)
ClonedPed = CreatePed(26, GetEntityModel(playerPed), coords.x, coords.y, coords.z, 0, false, false)
ClonePedToTarget(playerPed, ClonedPed)

SetEntityInvincible(ClonedPed, true)
SetEntityLocallyVisible(ClonedPed)
NetworkSetEntityInvisibleToNetwork(ClonedPed, true)
SetEntityCanBeDamaged(ClonedPed, false)
SetBlockingOfNonTemporaryEvents(ClonedPed, true)
SetEntityAlpha(ClonedPed, 254, false)
SetEntityCollision(ClonedPed, false, false)

ShowPed = true

local positionBuffer = {}
local bufferSize = 5

while ShowPed do
local screencoordsX = zoom and 0.6 or 0.65135417461395
local screencoordsY = zoom and 1.9 or 0.77

if Config.MenuPosition == "left" then
screencoordsX = 1.0 - screencoordsX
end

local world, normal = GetWorldCoordFromScreenCoord(screencoordsX, screencoordsY)
local depth = zoom and 2.0 or 3.5
local target = world + normal * depth
local camRot = GetGameplayCamRot(2)

table.insert(positionBuffer, target)
if #positionBuffer > bufferSize then
table.remove(positionBuffer, 1)
end

local averagedTarget = vector3(0, 0, 0)
for _, position in ipairs(positionBuffer) do
averagedTarget = averagedTarget + position
end
averagedTarget = averagedTarget / #positionBuffer

SetEntityCoords(ClonedPed, averagedTarget.x, averagedTarget.y, averagedTarget.z, false, false, false, true)
local heading_offset = Config.MenuPosition == "left" and 170.0 or 190.0
SetEntityHeading(ClonedPed, camRot.z + heading_offset)
SetEntityRotation(ClonedPed, camRot.x * (-1), 0.0, camRot.z + 170.0, 2, false)

Wait(4)
end

DeleteEntity(ClonedPed)
ClonedPed = nil
end)
end
end

function ClosePedMenu()
if not Config.PreviewPed then return end

if ClonedPed then
ShowPed = false
ClearPedTaskPreview()
DeleteEntity(ClonedPed)
end
end

function ClearPedTaskPreview()
if not Config.PreviewPed then return end

if ClonedPed then
DestroyAllProps(true)
ClearPedTasksImmediately(ClonedPed)
end
end

View file

@ -0,0 +1,124 @@
local canChange = true
local unable_message = "You are unable to change your walking style right now."

function WalkMenuStart(name, force)
if not canChange and not force then
EmoteChatMessage(unable_message)
return
end

if not name or name == "" then
ResetWalk()
return
end
if not RP[name] or type(RP[name]) ~= "table" or RP[name].category ~= "Walks" then
EmoteChatMessage("'" .. tostring(name) .. "' is not a valid walk")
return
end

local walk = RP[name][1]
RequestWalking(walk)
SetPedMovementClipset(PlayerPedId(), walk, 0.2)
RemoveAnimSet(walk)

if Config.PersistentWalk then SetResourceKvp("walkstyle", name) end
end

function ResetWalk()
if not canChange then
EmoteChatMessage(unable_message)
return
end
ResetPedMovementClipset(PlayerPedId(), 0.0)
end

function WalksOnCommand()
local WalksCommand = ""
for name, data in PairsByKeys(RP) do
if type(data) == "table" and data.category == "Walks" then
WalksCommand = WalksCommand .. string.lower(name) .. ", "
end
end
EmoteChatMessage(WalksCommand)
EmoteChatMessage("To reset do /walk reset")
end

function WalkCommandStart(name)
if not canChange then
EmoteChatMessage(unable_message)
return
end
name = FirstToUpper(string.lower(name))

if name == "Reset" then
ResetPedMovementClipset(PlayerPedId(), 0.0)
DeleteResourceKvp("walkstyle")
return
end

WalkMenuStart(name, true)
end

if Config.WalkingStylesEnabled and Config.PersistentWalk then
local function walkstyleExists(kvp)
while not CONVERTED do
Wait(0)
end
if not kvp or kvp == "" then
return false
end

local walkstyle = RP[kvp]
if walkstyle and type(walkstyle) == "table" and walkstyle.category == "Walks" then
return true
end
return false
end

local function handleWalkstyle()
local kvp = GetResourceKvpString("walkstyle")

if kvp then
if walkstyleExists(kvp) then
WalkMenuStart(kvp, true)
else
ResetPedMovementClipset(PlayerPedId(), 0.0)
DeleteResourceKvp("walkstyle")
end
end
end

AddEventHandler('playerSpawned', function()
Wait(3000)
handleWalkstyle()
end)

RegisterNetEvent('QBCore:Client:OnPlayerLoaded', handleWalkstyle)
RegisterNetEvent('esx:playerLoaded', handleWalkstyle)

AddEventHandler('onResourceStart', function(resource)
if resource == GetCurrentResourceName() then
handleWalkstyle()
end
end)
end

if Config.WalkingStylesEnabled then
RegisterCommand('walks', function() WalksOnCommand() end, false)
RegisterCommand('walk', function(_, args, _) WalkCommandStart(tostring(args[1])) end, false)
TriggerEvent('chat:addSuggestion', '/walk', 'Set your walkingstyle.', { { name = "style", help = "/walks for a list of valid styles" } })
TriggerEvent('chat:addSuggestion', '/walks', 'List available walking styles.')
end

CreateExport('toggleWalkstyle', function(bool, message)
canChange = bool
if message then
unable_message = message
end
end)

CreateExport('getWalkstyle', function()
return GetResourceKvpString("walkstyle")
end)

CreateExport('setWalkstyle', WalkMenuStart)