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
This commit is contained in:
2026-04-02 02:11:26 +03:00
parent a3d124dc45
commit 373d179cfb
9 changed files with 736 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ ensure [auth]
ensure [ui] ensure [ui]
ensure [jobs] ensure [jobs]
ensure [addons] ensure [addons]
ensure qs-weaponsonback # safety net ne asiguram ca se incarca
ensure rv-license-dialog ensure rv-license-dialog
ensure kq_carheist ensure kq_carheist
ensure [mlos] ensure [mlos]

View File

@@ -11,6 +11,7 @@ Config.WeapDraw = {
'WEAPON_REVOLVER', 'WEAPON_REVOLVER',
'WEAPON_SNSPISTOL', 'WEAPON_SNSPISTOL',
'WEAPON_HEAVYPISTOL', 'WEAPON_HEAVYPISTOL',
'WEAPON_VINTAGEPISTOL' 'WEAPON_VINTAGEPISTOL',
'WEAPON_SPECIALCARBINE'
} }
} }

View File

@@ -0,0 +1,543 @@
--[[
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)

View File

@@ -0,0 +1,43 @@
--[[
rv-itemsonback — Config
Bone presets și setări generale
]]
Config = {}
-- Bones comune pentru atașare (name → bone ID)
Config.Bones = {
{ label = '🦴 Pelvis (Curea)', value = 11816 }, -- SKEL_Pelvis
{ label = '🦴 Spate Jos', value = 57597 }, -- SKEL_Spine0
{ label = '🦴 Spate Mijloc', value = 24816 }, -- SKEL_Spine2
{ label = '🦴 Spate Sus', value = 24818 }, -- SKEL_Spine3
{ label = '🦴 Umăr Drept', value = 10706 }, -- SKEL_R_Clavicle
{ label = '🦴 Umăr Stâng', value = 64729 }, -- SKEL_L_Clavicle
{ label = '🦴 Coapsă Dreaptă', value = 51826 }, -- SKEL_R_Thigh
{ label = '🦴 Coapsă Stângă', value = 58271 }, -- SKEL_L_Thigh
{ label = '🦴 Mână Dreaptă', value = 28422 }, -- PH_R_Hand
{ label = '🦴 Piept', value = 24818 }, -- SKEL_Spine3
}
-- Prop-uri sugerate (pentru referință rapidă)
Config.SuggestedProps = {
'prop_ld_health_pack', -- First aid kit
'prop_tool_box_04', -- Toolbox
'prop_fire_exting_1a', -- Fire extinguisher
'prop_cs_binoculars', -- Binoculars
'prop_ammo_box_01', -- Ammo box
'prop_cs_heist_bag_01', -- Heist bag
'prop_paper_bag_small', -- Paper bag
'prop_tool_torch', -- Torch
'prop_cs_cardbox_01', -- Cardboard box
'prop_tool_jackham', -- Jackhammer
}
-- Increment-uri pentru ajustare
Config.PosStep = 0.02 -- pas mic pentru poziție
Config.PosStepBig = 0.08 -- pas mare pentru poziție
Config.RotStep = 5.0 -- pas mic pentru rotație
Config.RotStepBig = 15.0 -- pas mare pentru rotație
-- Interval verificare inventar (ms)
Config.CheckInterval = 2000

View File

@@ -0,0 +1 @@
[{"model":"prop_ld_health_pack","bone":11816,"rot":{"z":25.0,"y":-90.0,"x":5.0},"item":"medikit","pos":{"z":0.12,"y":-0.1,"x":0.0}}]

View File

@@ -0,0 +1,19 @@
fx_version 'bodacious'
game 'gta5'
lua54 'yes'
author 'Red Valley'
description 'Afiseaza iteme din inventar ca props pe corpul jucatorului + admin menu'
version '2.0.0'
shared_script '@ox_lib/init.lua'
server_scripts {
'config.lua',
'server.lua'
}
client_scripts {
'config.lua',
'client.lua'
}

View File

@@ -0,0 +1,127 @@
--[[
rv-itemsonback — Server
Gestionare configurări persistent (JSON), sync către clienți
]]
print('[rv-itemsonback] ^2Server script loading...^0')
local QBCore = exports['qb-core']:GetCoreObject()
local savedItems = {}
local dataFile = 'data/items.json'
-----------------------------------------
-- Încarcă config din JSON
-----------------------------------------
local function LoadConfig()
local file = LoadResourceFile(GetCurrentResourceName(), dataFile)
if file then
savedItems = json.decode(file) or {}
print('[rv-itemsonback] Loaded ' .. #savedItems .. ' item configs')
else
savedItems = {
-- Default: medikit
{
item = 'medikit',
model = 'prop_ld_health_pack',
bone = 11816,
pos = { x = 0.15, y = -0.15, z = -0.05 },
rot = { x = 0.0, y = 0.0, z = 0.0 }
}
}
SaveConfig()
print('[rv-itemsonback] Created default config with medikit')
end
end
-----------------------------------------
-- Salvează config în JSON
-----------------------------------------
function SaveConfig()
SaveResourceFile(GetCurrentResourceName(), dataFile, json.encode(savedItems), -1)
end
-----------------------------------------
-- Callback: trimite config-ul la client
-----------------------------------------
lib.callback.register('rv-itemsonback:getConfig', function(source)
return savedItems
end)
-----------------------------------------
-- Callback: salvează un item nou/editat
-----------------------------------------
lib.callback.register('rv-itemsonback:saveItem', function(source, data)
-- Verifică admin
if not QBCore.Functions.HasPermission(source, 'god') and
not QBCore.Functions.HasPermission(source, 'admin') and
not IsPlayerAceAllowed(source, 'command') then
return false
end
-- Caută dacă itemul există deja
local found = false
for i, item in ipairs(savedItems) do
if item.item == data.item then
savedItems[i] = data
found = true
break
end
end
if not found then
savedItems[#savedItems + 1] = data
end
SaveConfig()
-- Sync la toți clienții
TriggerClientEvent('rv-itemsonback:syncConfig', -1, savedItems)
return true
end)
-----------------------------------------
-- Callback: șterge un item
-----------------------------------------
lib.callback.register('rv-itemsonback:deleteItem', function(source, itemName)
if not QBCore.Functions.HasPermission(source, 'god') and
not QBCore.Functions.HasPermission(source, 'admin') and
not IsPlayerAceAllowed(source, 'command') then
return false
end
for i, item in ipairs(savedItems) do
if item.item == itemName then
table.remove(savedItems, i)
SaveConfig()
TriggerClientEvent('rv-itemsonback:syncConfig', -1, savedItems)
return true
end
end
return false
end)
-----------------------------------------
-- Sync la connect
-----------------------------------------
RegisterNetEvent('QBCore:Server:PlayerLoaded', function()
local src = source
TriggerClientEvent('rv-itemsonback:syncConfig', src, savedItems)
end)
-----------------------------------------
-- Comandă admin
-----------------------------------------
RegisterCommand('itemsonback', function(source)
if source == 0 then return end -- consola
if QBCore.Functions.HasPermission(source, 'god') or
QBCore.Functions.HasPermission(source, 'admin') or
IsPlayerAceAllowed(source, 'command') then
TriggerClientEvent('rv-itemsonback:openMenu', source)
else
TriggerClientEvent('QBCore:Notify', source, 'Nu ai permisiune', 'error')
end
end, false)
-----------------------------------------
-- Init
-----------------------------------------
LoadConfig()