544 lines
22 KiB
Lua
544 lines
22 KiB
Lua
|
|
--[[
|
|||
|
|
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)
|