2025-06-07 08:51:21 +02:00
|
|
|
-- 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
|
|
|
|
|
2025-06-12 03:36:12 +02:00
|
|
|
-- 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
|
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
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)
|
2025-06-12 03:36:12 +02:00
|
|
|
if not HasModelLoaded(GetHashKey(model)) then
|
|
|
|
RequestModel(GetHashKey(model))
|
2025-06-07 08:51:21 +02:00
|
|
|
local timeout = 2000
|
2025-06-12 03:36:12 +02:00
|
|
|
while not HasModelLoaded(GetHashKey(model)) and timeout > 0 do
|
2025-06-07 08:51:21 +02:00
|
|
|
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 = ""
|
2025-06-12 03:36:12 +02:00
|
|
|
for a, b in PairsByKeys(RP) do
|
|
|
|
if type(b) == "table" and b.category == "Shared" then
|
|
|
|
NearbysCommand = NearbysCommand .. a .. ", "
|
|
|
|
end
|
2025-06-07 08:51:21 +02:00
|
|
|
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)
|
2025-06-12 03:36:12 +02:00
|
|
|
if ignores then DebugPrint(ignores) end
|
2025-06-07 08:51:21 +02:00
|
|
|
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
|