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 )