diff --git a/resources.cfg b/resources.cfg index 61519341..01e69b47 100644 --- a/resources.cfg +++ b/resources.cfg @@ -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] diff --git a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/.fxap b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/.fxap index 1305d28f..052517e3 100644 Binary files a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/.fxap and b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/.fxap differ diff --git a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/client.lua b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/client.lua index 306ec65c..d045826c 100644 Binary files a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/client.lua and b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/client.lua differ diff --git a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/config.lua b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/config.lua index 64f45cc1..763f6995 100644 --- a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/config.lua +++ b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/config.lua @@ -11,6 +11,7 @@ Config.WeapDraw = { 'WEAPON_REVOLVER', 'WEAPON_SNSPISTOL', 'WEAPON_HEAVYPISTOL', - 'WEAPON_VINTAGEPISTOL' + 'WEAPON_VINTAGEPISTOL', + 'WEAPON_SPECIALCARBINE' } } diff --git a/resources/[framework]/[addons]/rv-itemsonback/client.lua b/resources/[framework]/[addons]/rv-itemsonback/client.lua new file mode 100644 index 00000000..2d720d59 --- /dev/null +++ b/resources/[framework]/[addons]/rv-itemsonback/client.lua @@ -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) diff --git a/resources/[framework]/[addons]/rv-itemsonback/config.lua b/resources/[framework]/[addons]/rv-itemsonback/config.lua new file mode 100644 index 00000000..d502b3da --- /dev/null +++ b/resources/[framework]/[addons]/rv-itemsonback/config.lua @@ -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 diff --git a/resources/[framework]/[addons]/rv-itemsonback/data/items.json b/resources/[framework]/[addons]/rv-itemsonback/data/items.json new file mode 100644 index 00000000..186f693c --- /dev/null +++ b/resources/[framework]/[addons]/rv-itemsonback/data/items.json @@ -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}}] \ No newline at end of file diff --git a/resources/[framework]/[addons]/rv-itemsonback/fxmanifest.lua b/resources/[framework]/[addons]/rv-itemsonback/fxmanifest.lua new file mode 100644 index 00000000..41568ef7 --- /dev/null +++ b/resources/[framework]/[addons]/rv-itemsonback/fxmanifest.lua @@ -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' +} diff --git a/resources/[framework]/[addons]/rv-itemsonback/server.lua b/resources/[framework]/[addons]/rv-itemsonback/server.lua new file mode 100644 index 00000000..98cc00f2 --- /dev/null +++ b/resources/[framework]/[addons]/rv-itemsonback/server.lua @@ -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()