Files
red-valley/resources/[framework]/[addons]/rv-itemsonback/client.lua
Kotzu 373d179cfb resursa iteme vizibile pe corp cu admin menu + disable idle cam
- rv-itemsonback v2.0: afișare props pe corp din inventar (medikit etc.)
- Admin menu /itemsonback cu ox_lib (search items, adjust pos/rot, select bone)
- Live preview cu auto-cleanup + /clearpreview emergency
- Config persistent JSON (data/items.json)
- Disable idle camera + idle animations (InvalidateIdleCam)
- resources.cfg: safety net ensure qs-weaponsonback
- fix(qs-inventory): Config.Genders[0]=Male pt QBCore compatibility
2026-04-02 02:11:26 +03:00

544 lines
22 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--[[
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)