From 373d179cfb92632e9e06df8f7c846b9e2e309915 Mon Sep 17 00:00:00 2001 From: Kotzu Date: Thu, 2 Apr 2026 02:11:26 +0300 Subject: [PATCH] resursa iteme vizibile pe corp cu admin menu + disable idle cam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- resources.cfg | 1 + .../[addons]/[quasar]/qs-weapondraw/.fxap | Bin 185 -> 178 bytes .../[quasar]/qs-weapondraw/client.lua | Bin 6847 -> 6803 bytes .../[quasar]/qs-weapondraw/config.lua | 3 +- .../[addons]/rv-itemsonback/client.lua | 543 ++++++++++++++++++ .../[addons]/rv-itemsonback/config.lua | 43 ++ .../[addons]/rv-itemsonback/data/items.json | 1 + .../[addons]/rv-itemsonback/fxmanifest.lua | 19 + .../[addons]/rv-itemsonback/server.lua | 127 ++++ 9 files changed, 736 insertions(+), 1 deletion(-) create mode 100644 resources/[framework]/[addons]/rv-itemsonback/client.lua create mode 100644 resources/[framework]/[addons]/rv-itemsonback/config.lua create mode 100644 resources/[framework]/[addons]/rv-itemsonback/data/items.json create mode 100644 resources/[framework]/[addons]/rv-itemsonback/fxmanifest.lua create mode 100644 resources/[framework]/[addons]/rv-itemsonback/server.lua 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 1305d28f78f3bfe0d642124af9e1e68f5e435ac9..052517e34e9579db5543fe37ff2e97d577ef8f64 100644 GIT binary patch literal 178 zcmV;j08Rf!SV2$$0000007FW@?E2YWv|&%NW0D`i%91cCamXQgntpO(ch73}eb{X( zOJV*$9Xe<7UVGWgo32zsvf9F2CKaEB2Gf9E0yxQiZ`49SP{>(|F_g&^(O{5uvj)B# zqploGa(LbvS&Whb{_!Y}sac7Wp|)m)dLB{>#@r8a;gv)WtK4s!7?D-rb g+)jQXG`yr+L1UOF2|pWPEZW+J)?wW99>52-vy+xo-v9sr literal 185 zcmV;q07m~tSV2$$00000020E^!uG0rqIZ5l?uK=P;KsUeIyP>h^&8qcsWb%Q)pi2E zGM*_~?>Cpvpw3o!1JiqX2#h=jQ|TnK8@I&k3R=wAm0aygA=3E`HfzD_R$&P_ilSz0 zfOjxKSCfA%QmzBlA&MW0xX=U{M|Lzs&GOY8SKfa6zzqHeZp_%9hNaoR_1)P!@hNJm nBos8-mz4Sc87-~j-N~dq!y`I&FyYX+W0M_`15PQ*`i#p|MkHD; diff --git a/resources/[framework]/[addons]/[quasar]/qs-weapondraw/client.lua b/resources/[framework]/[addons]/[quasar]/qs-weapondraw/client.lua index 306ec65cf8794effa42d389704914084b7bc924f..d045826c7a79683b365cf45fa5981bd64e15ae69 100644 GIT binary patch literal 6803 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 BMzR0^ literal 6847 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 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()