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:
@@ -7,6 +7,7 @@ ensure [auth]
|
||||
ensure [ui]
|
||||
ensure [jobs]
|
||||
ensure [addons]
|
||||
ensure qs-weaponsonback # safety net ne asiguram ca se incarca
|
||||
ensure rv-license-dialog
|
||||
ensure kq_carheist
|
||||
ensure [mlos]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,7 @@ Config.WeapDraw = {
|
||||
'WEAPON_REVOLVER',
|
||||
'WEAPON_SNSPISTOL',
|
||||
'WEAPON_HEAVYPISTOL',
|
||||
'WEAPON_VINTAGEPISTOL'
|
||||
'WEAPON_VINTAGEPISTOL',
|
||||
'WEAPON_SPECIALCARBINE'
|
||||
}
|
||||
}
|
||||
|
||||
543
resources/[framework]/[addons]/rv-itemsonback/client.lua
Normal file
543
resources/[framework]/[addons]/rv-itemsonback/client.lua
Normal 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)
|
||||
43
resources/[framework]/[addons]/rv-itemsonback/config.lua
Normal file
43
resources/[framework]/[addons]/rv-itemsonback/config.lua
Normal 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
|
||||
@@ -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}}]
|
||||
19
resources/[framework]/[addons]/rv-itemsonback/fxmanifest.lua
Normal file
19
resources/[framework]/[addons]/rv-itemsonback/fxmanifest.lua
Normal 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'
|
||||
}
|
||||
127
resources/[framework]/[addons]/rv-itemsonback/server.lua
Normal file
127
resources/[framework]/[addons]/rv-itemsonback/server.lua
Normal 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()
|
||||
Reference in New Issue
Block a user