--[[ 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)