2026-04-02 02:11:26 +03:00
--[[
rv - itemsonback — Client
Afișează iteme din inventar ca props pe corpul jucătorului
+ Admin menu cu ox_lib pentru configurare
] ]
local QBCore = exports [ ' qb-core ' ] : GetCoreObject ( )
local attachedProps = { }
local itemConfigs = { }
local previewProp = nil
local editingItem = nil
local isEditing = false
-----------------------------------------
-- Verifică dacă playerul are un item
-----------------------------------------
local function HasItem ( itemName )
local PlayerData = QBCore.Functions . GetPlayerData ( )
if not PlayerData or not PlayerData.items then return false end
for _ , item in pairs ( PlayerData.items ) do
if item and item.name == itemName and item.amount and item.amount > 0 then
return true
end
end
return false
end
-----------------------------------------
-- Atașează prop pe jucător
-----------------------------------------
local function AttachProp ( itemName , config )
if attachedProps [ itemName ] then return end
local ped = PlayerPedId ( )
local modelHash = GetHashKey ( config.model )
RequestModel ( modelHash )
local timeout = 0
while not HasModelLoaded ( modelHash ) and timeout < 50 do
Wait ( 100 )
timeout = timeout + 1
end
if not HasModelLoaded ( modelHash ) then return end
local boneIndex = GetPedBoneIndex ( ped , config.bone )
local prop = CreateObject ( modelHash , 0.0 , 0.0 , 0.0 , true , true , false )
AttachEntityToEntity ( prop , ped , boneIndex ,
config.pos . x , config.pos . y , config.pos . z ,
config.rot . x , config.rot . y , config.rot . z ,
true , true , false , true , 1 , true )
SetModelAsNoLongerNeeded ( modelHash )
attachedProps [ itemName ] = prop
end
-----------------------------------------
-- Detașează prop
-----------------------------------------
local function DetachProp ( itemName )
if attachedProps [ itemName ] then
if DoesEntityExist ( attachedProps [ itemName ] ) then
DeleteEntity ( attachedProps [ itemName ] )
end
attachedProps [ itemName ] = nil
end
end
-----------------------------------------
-- Detașează TOATE propurile
-----------------------------------------
local function DetachAll ( )
for itemName , _ in pairs ( attachedProps ) do
DetachProp ( itemName )
end
end
-----------------------------------------
-- Preview prop (pentru admin editing)
-----------------------------------------
local function ShowPreview ( config )
ClearPreview ( )
local ped = PlayerPedId ( )
local modelHash = GetHashKey ( config.model )
RequestModel ( modelHash )
local timeout = 0
while not HasModelLoaded ( modelHash ) and timeout < 50 do
Wait ( 100 )
timeout = timeout + 1
end
if not HasModelLoaded ( modelHash ) then return end
local boneIndex = GetPedBoneIndex ( ped , config.bone )
previewProp = CreateObject ( modelHash , 0.0 , 0.0 , 0.0 , true , true , false )
AttachEntityToEntity ( previewProp , ped , boneIndex ,
config.pos . x , config.pos . y , config.pos . z ,
config.rot . x , config.rot . y , config.rot . z ,
true , true , false , true , 1 , true )
-- Efect vizual: ușor transparent pentru a indica modul preview
SetEntityAlpha ( previewProp , 200 , false )
SetModelAsNoLongerNeeded ( modelHash )
end
function ClearPreview ( )
if previewProp and DoesEntityExist ( previewProp ) then
DeleteEntity ( previewProp )
end
previewProp = nil
end
-----------------------------------------
-- Rebuild configs din tabel
-----------------------------------------
local function BuildItemConfigs ( )
local configs = { }
for _ , data in ipairs ( itemConfigs ) do
configs [ data.item ] = {
model = data.model ,
bone = data.bone ,
pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) ,
rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) ,
}
end
return configs
end
-----------------------------------------
-- Sync config de la server
-----------------------------------------
RegisterNetEvent ( ' rv-itemsonback:syncConfig ' , function ( configs )
itemConfigs = configs
-- Reattach cu config-urile noi
DetachAll ( )
end )
-----------------------------------------
-- ADMIN MENU: Main
-----------------------------------------
local function OpenMainMenu ( )
local options = { }
-- Lista itemelor configurate
for _ , data in ipairs ( itemConfigs ) do
options [ # options + 1 ] = {
title = ' 📦 ' .. data.item ,
description = ' Prop: ' .. data.model ,
icon = ' cube ' ,
arrow = true ,
onSelect = function ( )
editingItem = data
OpenEditMenu ( data )
end
}
end
-- Adaugă item nou
options [ # options + 1 ] = {
title = ' ➕ Adaugă Item Nou' ,
description = ' Configurează un item nou pe corp ' ,
icon = ' plus ' ,
onSelect = function ( )
OpenAddMenu ( )
end
}
lib.registerContext ( {
id = ' rv_itemsonback_main ' ,
title = ' 🎒 Items on Back — Admin ' ,
options = options
} )
lib.showContext ( ' rv_itemsonback_main ' )
end
-----------------------------------------
-- ADMIN MENU: Add New Item
-----------------------------------------
function OpenAddMenu ( )
-- Step 1: Item name
local items = QBCore.Shared . Items
local itemOptions = { }
for k , v in pairs ( items ) do
itemOptions [ # itemOptions + 1 ] = { value = k , label = v.label .. ' ( ' .. k .. ' ) ' }
end
-- Sortează alfabetic
table.sort ( itemOptions , function ( a , b ) return a.label < b.label end )
local input = lib.inputDialog ( ' Adaugă Item Nou ' , {
{ type = ' select ' , label = ' Item ' , description = ' Selectează itemul din inventar ' , options = itemOptions , required = true , searchable = true } ,
{ type = ' input ' , label = ' Prop Model ' , description = ' Numele propului GTA (ex: prop_ld_health_pack) ' , required = true , default = ' prop_ld_health_pack ' } ,
{ type = ' select ' , label = ' Locație (Bone) ' , description = ' Unde pe corp ' , options = Config.Bones , required = true } ,
} )
if not input then return OpenMainMenu ( ) end
local newData = {
item = input [ 1 ] ,
model = input [ 2 ] ,
bone = input [ 3 ] ,
pos = { x = 0.0 , y = - 0.1 , z = 0.0 } ,
rot = { x = 0.0 , y = 0.0 , z = 0.0 }
}
-- Preview
ShowPreview ( {
model = newData.model ,
bone = newData.bone ,
pos = vector3 ( newData.pos . x , newData.pos . y , newData.pos . z ) ,
rot = vector3 ( newData.rot . x , newData.rot . y , newData.rot . z ) ,
} )
editingItem = newData
OpenEditMenu ( newData )
end
-----------------------------------------
-- ADMIN MENU: Edit Item
-----------------------------------------
function OpenEditMenu ( data )
isEditing = true
-- Arată preview
ShowPreview ( {
model = data.model ,
bone = data.bone ,
pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) ,
rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) ,
} )
-- Găsește numele bone-ului
local boneName = ' Unknown '
for _ , b in ipairs ( Config.Bones ) do
if b.value == data.bone then
boneName = b.label
break
end
end
lib.registerContext ( {
id = ' rv_itemsonback_edit ' ,
title = ' ✏️ Edit: ' .. data.item ,
menu = ' rv_itemsonback_main ' ,
options = {
{
title = ' 📍 Poziție: X= ' .. string.format ( ' %.2f ' , data.pos . x ) .. ' Y= ' .. string.format ( ' %.2f ' , data.pos . y ) .. ' Z= ' .. string.format ( ' %.2f ' , data.pos . z ) ,
description = ' Ajustează poziția propului ' ,
icon = ' arrows-alt ' ,
arrow = true ,
onSelect = function ( )
OpenPositionMenu ( data )
end
} ,
{
title = ' 🔄 Rotație: X= ' .. string.format ( ' %.0f ' , data.rot . x ) .. ' Y= ' .. string.format ( ' %.0f ' , data.rot . y ) .. ' Z= ' .. string.format ( ' %.0f ' , data.rot . z ) ,
description = ' Ajustează rotația propului ' ,
icon = ' sync-alt ' ,
arrow = true ,
onSelect = function ( )
OpenRotationMenu ( data )
end
} ,
{
title = ' 🦴 Bone: ' .. boneName ,
description = ' Schimbă locația pe corp ' ,
icon = ' bone ' ,
arrow = true ,
onSelect = function ( )
OpenBoneMenu ( data )
end
} ,
{
title = ' 🔧 Schimbă Prop Model ' ,
description = ' Model curent: ' .. data.model ,
icon = ' cube ' ,
onSelect = function ( )
local input = lib.inputDialog ( ' Schimbă Prop Model ' , {
{ type = ' input ' , label = ' Prop Model ' , default = data.model , required = true }
} )
if input then
data.model = input [ 1 ]
OpenEditMenu ( data )
else
OpenEditMenu ( data )
end
end
} ,
{
title = ' 💾 Salvează ' ,
description = ' Salvează configurația ' ,
icon = ' save ' ,
onSelect = function ( )
ClearPreview ( )
local success = lib.callback . await ( ' rv-itemsonback:saveItem ' , false , data )
if success then
lib.notify ( { title = ' Salvat! ' , description = data.item .. ' configurat cu succes ' , type = ' success ' } )
else
lib.notify ( { title = ' Eroare ' , description = ' Nu ai permisiune ' , type = ' error ' } )
end
end
} ,
{
title = ' 🗑️ Șterge ' ,
description = ' Șterge acest item din configurare ' ,
icon = ' trash ' ,
onSelect = function ( )
ClearPreview ( )
local confirm = lib.alertDialog ( {
header = ' Confirmare ștergere ' ,
content = ' Sigur vrei să ștergi ** ' .. data.item .. ' **? ' ,
centered = true ,
cancel = true
} )
if confirm == ' confirm ' then
lib.callback . await ( ' rv-itemsonback:deleteItem ' , false , data.item )
lib.notify ( { title = ' Șters! ' , description = data.item .. ' eliminat ' , type = ' success ' } )
OpenMainMenu ( )
else
OpenEditMenu ( data )
end
end
} ,
}
} )
lib.showContext ( ' rv_itemsonback_edit ' )
end
-----------------------------------------
-- ADMIN MENU: Position Adjustment
-----------------------------------------
function OpenPositionMenu ( data )
local step = Config.PosStep
local bigStep = Config.PosStepBig
lib.registerContext ( {
id = ' rv_itemsonback_pos ' ,
title = ' 📍 Poziție: ' .. string.format ( ' X=%.2f Y=%.2f Z=%.2f ' , data.pos . x , data.pos . y , data.pos . z ) ,
menu = ' rv_itemsonback_edit ' ,
options = {
-- X axis
{ title = ' ➡️ X + ' .. step , icon = ' arrow-right ' , onSelect = function ( ) data.pos . x = data.pos . x + step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
{ title = ' ⬅️ X - ' .. step , icon = ' arrow-left ' , onSelect = function ( ) data.pos . x = data.pos . x - step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
-- Y axis
{ title = ' ⬆️ Y + ' .. step , icon = ' arrow-up ' , onSelect = function ( ) data.pos . y = data.pos . y + step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
{ title = ' ⬇️ Y - ' .. step , icon = ' arrow-down ' , onSelect = function ( ) data.pos . y = data.pos . y - step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
-- Z axis
{ title = ' 🔼 Z + ' .. step , icon = ' chevron-up ' , onSelect = function ( ) data.pos . z = data.pos . z + step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
{ title = ' 🔽 Z - ' .. step , icon = ' chevron-down ' , onSelect = function ( ) data.pos . z = data.pos . z - step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
-- Big steps
{ title = ' ⏩ X + ' .. bigStep .. ' (mare) ' , icon = ' fast-forward ' , onSelect = function ( ) data.pos . x = data.pos . x + bigStep ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
{ title = ' ⏪ X - ' .. bigStep .. ' (mare) ' , icon = ' fast-backward ' , onSelect = function ( ) data.pos . x = data.pos . x - bigStep ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenPositionMenu ( data ) end } ,
-- Manual input
{
title = ' ✏️ Input Manual ' ,
description = ' Introdu valori exacte ' ,
icon = ' keyboard ' ,
onSelect = function ( )
local input = lib.inputDialog ( ' Poziție Manuală ' , {
{ type = ' number ' , label = ' X ' , default = data.pos . x , step = 0.01 } ,
{ type = ' number ' , label = ' Y ' , default = data.pos . y , step = 0.01 } ,
{ type = ' number ' , label = ' Z ' , default = data.pos . z , step = 0.01 } ,
} )
if input then
data.pos . x = input [ 1 ] or data.pos . x
data.pos . y = input [ 2 ] or data.pos . y
data.pos . z = input [ 3 ] or data.pos . z
ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } )
end
OpenPositionMenu ( data )
end
} ,
}
} )
lib.showContext ( ' rv_itemsonback_pos ' )
end
-----------------------------------------
-- ADMIN MENU: Rotation Adjustment
-----------------------------------------
function OpenRotationMenu ( data )
local step = Config.RotStep
local bigStep = Config.RotStepBig
lib.registerContext ( {
id = ' rv_itemsonback_rot ' ,
title = ' 🔄 Rotație: ' .. string.format ( ' X=%.0f Y=%.0f Z=%.0f ' , data.rot . x , data.rot . y , data.rot . z ) ,
menu = ' rv_itemsonback_edit ' ,
options = {
{ title = ' 🔄 RotX + ' .. step , icon = ' redo ' , onSelect = function ( ) data.rot . x = data.rot . x + step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenRotationMenu ( data ) end } ,
{ title = ' 🔄 RotX - ' .. step , icon = ' undo ' , onSelect = function ( ) data.rot . x = data.rot . x - step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenRotationMenu ( data ) end } ,
{ title = ' 🔄 RotY + ' .. step , icon = ' redo ' , onSelect = function ( ) data.rot . y = data.rot . y + step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenRotationMenu ( data ) end } ,
{ title = ' 🔄 RotY - ' .. step , icon = ' undo ' , onSelect = function ( ) data.rot . y = data.rot . y - step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenRotationMenu ( data ) end } ,
{ title = ' 🔄 RotZ + ' .. step , icon = ' redo ' , onSelect = function ( ) data.rot . z = data.rot . z + step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenRotationMenu ( data ) end } ,
{ title = ' 🔄 RotZ - ' .. step , icon = ' undo ' , onSelect = function ( ) data.rot . z = data.rot . z - step ; ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } ) ; OpenRotationMenu ( data ) end } ,
{
title = ' ✏️ Input Manual ' ,
icon = ' keyboard ' ,
onSelect = function ( )
local input = lib.inputDialog ( ' Rotație Manuală ' , {
{ type = ' number ' , label = ' Rot X ' , default = data.rot . x , step = 1 } ,
{ type = ' number ' , label = ' Rot Y ' , default = data.rot . y , step = 1 } ,
{ type = ' number ' , label = ' Rot Z ' , default = data.rot . z , step = 1 } ,
} )
if input then
data.rot . x = input [ 1 ] or data.rot . x
data.rot . y = input [ 2 ] or data.rot . y
data.rot . z = input [ 3 ] or data.rot . z
ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } )
end
OpenRotationMenu ( data )
end
} ,
}
} )
lib.showContext ( ' rv_itemsonback_rot ' )
end
-----------------------------------------
-- ADMIN MENU: Bone Selection
-----------------------------------------
function OpenBoneMenu ( data )
local options = { }
for _ , bone in ipairs ( Config.Bones ) do
options [ # options + 1 ] = {
title = bone.label ,
icon = ' bone ' ,
onSelect = function ( )
data.bone = bone.value
ShowPreview ( { model = data.model , bone = data.bone , pos = vector3 ( data.pos . x , data.pos . y , data.pos . z ) , rot = vector3 ( data.rot . x , data.rot . y , data.rot . z ) } )
OpenEditMenu ( data )
end
}
end
lib.registerContext ( {
id = ' rv_itemsonback_bone ' ,
title = ' 🦴 Selectează Bone ' ,
menu = ' rv_itemsonback_edit ' ,
options = options
} )
lib.showContext ( ' rv_itemsonback_bone ' )
end
-----------------------------------------
-- Event: deschide meniul admin
-----------------------------------------
RegisterNetEvent ( ' rv-itemsonback:openMenu ' , function ( )
OpenMainMenu ( )
end )
-----------------------------------------
-- Cleanup preview la închiderea meniului
-----------------------------------------
AddEventHandler ( ' ox_lib:onCloseContext ' , function ( context )
if not context then return end
if string.find ( context , ' rv_itemsonback ' ) then
ClearPreview ( )
isEditing = false
end
end )
-- Safety: curăță preview dacă a rămas blocat
CreateThread ( function ( )
while true do
Wait ( 3000 )
if previewProp and not isEditing then
ClearPreview ( )
end
end
end )
-- Comandă emergency cleanup
RegisterCommand ( ' clearpreview ' , function ( )
ClearPreview ( )
isEditing = false
lib.notify ( { title = ' Preview curățat ' , type = ' success ' } )
end , false )
-----------------------------------------
-- LOOP PRINCIPAL: atașare props din inventar
-----------------------------------------
CreateThread ( function ( )
-- Așteaptă config de la server (retry dacă serverul nu e gata)
while # itemConfigs == 0 do
Wait ( 2000 )
local ok , result = pcall ( lib.callback . await , ' rv-itemsonback:getConfig ' , false )
if ok and result then
itemConfigs = result
end
end
while true do
Wait ( Config.CheckInterval )
local ped = PlayerPedId ( )
local configs = BuildItemConfigs ( )
for itemName , config in pairs ( configs ) do
local hasIt = HasItem ( itemName )
local isDead = IsEntityDead ( ped )
local inVehicle = IsPedInAnyVehicle ( ped , false )
if hasIt and not isDead and not inVehicle then
AttachProp ( itemName , config )
else
DetachProp ( itemName )
end
end
end
end )
-----------------------------------------
-- Cleanup la resource stop
-----------------------------------------
AddEventHandler ( ' onResourceStop ' , function ( resourceName )
if GetCurrentResourceName ( ) ~= resourceName then return end
DetachAll ( )
ClearPreview ( )
end )
-----------------------------------------
-- Disable idle animations + idle camera
-----------------------------------------
CreateThread ( function ( )
while true do
Wait ( 500 )
local ped = PlayerPedId ( )
if DoesEntityExist ( ped ) then
SetPedCanPlayAmbientAnims ( ped , false )
SetPedCanPlayAmbientBaseAnims ( ped , false )
SetPedCanPlayGestureAnims ( ped , false )
InvalidateIdleCam ( )
N_0xf4f2c0d4ee209e20 ( ) -- DisableIdleCamera
end
end
end )
2026-04-02 22:10:34 +03:00
-----------------------------------------
-- Target Dummy PED (testing)
-----------------------------------------
local spawnedDummies = { }
RegisterCommand ( ' spawndummy ' , function ( )
local ped = PlayerPedId ( )
local coords = GetEntityCoords ( ped )
local heading = GetEntityHeading ( ped )
-- Spawn în fața playerului
local rad = math.rad ( heading )
local spawnX = coords.x - math.sin ( rad ) * 2.0
local spawnY = coords.y + math.cos ( rad ) * 2.0
local models = {
' s_m_y_cop_01 ' , ' a_m_y_business_01 ' , ' a_m_m_farmer_01 ' ,
' a_m_y_hipster_01 ' , ' s_m_m_paramedic_01 ' , ' a_m_m_bevhills_01 '
}
local model = GetHashKey ( models [ math.random ( # models ) ] )
RequestModel ( model )
while not HasModelLoaded ( model ) do Wait ( 50 ) end
local dummy = CreatePed ( 4 , model , spawnX , spawnY , coords.z - 1.0 , heading + 180.0 , true , false )
SetEntityAsMissionEntity ( dummy , true , true )
SetEntityInvincible ( dummy , false )
SetBlockingOfNonTemporaryEvents ( dummy , true )
SetPedFleeAttributes ( dummy , 0 , false )
SetPedCombatAttributes ( dummy , 46 , true )
SetPedCanRagdollFromPlayerImpact ( dummy , true )
TaskStandStill ( dummy , - 1 )
SetModelAsNoLongerNeeded ( model )
spawnedDummies [ # spawnedDummies + 1 ] = dummy
lib.notify ( { title = ' Dummy spawnat ' , description = ' /killdummy pentru cleanup ' , type = ' success ' } )
end , false )
RegisterCommand ( ' killdummy ' , function ( )
for _ , dummy in ipairs ( spawnedDummies ) do
if DoesEntityExist ( dummy ) then
DeleteEntity ( dummy )
end
end
spawnedDummies = { }
lib.notify ( { title = ' Dummies șterse ' , type = ' success ' } )
end , false )