From 0b4bb441ab2755540b867c0d4ddc19bf110607b7 Mon Sep 17 00:00:00 2001 From: Lenix <149199196+LenixDev@users.noreply.github.com> Date: Thu, 8 Jan 2026 18:35:16 -0800 Subject: [PATCH 1/5] fix(client): prevent adding nil items in radial menu conversion - Skip adding converted items if conversion returns nil - Return early if converted items list is empty - Ensure only valid converted items are added to radial menu - Improve stability by avoiding nil entries in menu setup loop --- client/main.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/main.lua b/client/main.lua index 638c541..22b5869 100644 --- a/client/main.lua +++ b/client/main.lua @@ -55,8 +55,12 @@ local function convert(tbl) if tbl.items then local items = {} for _, v in pairs(tbl.items) do - items[#items + 1] = convert(v) + local converted = convert(v) + if converted then + items[#items + 1] = converted + end end + if #items == 0 then return end lib.registerRadial({ id = tbl.id .. 'Menu', @@ -143,7 +147,10 @@ local function setupRadialMenu() setupVehicleMenu() for _, v in pairs(config.menuItems) do - lib.addRadialItem(convert(v)) + local converted = convert(v) + if converted then + lib.addRadialItem(converted) + end end if config.gangItems[QBX.PlayerData.gang.name] then From 65a184c4adc7f40445f040cbc73a56419e7066e4 Mon Sep 17 00:00:00 2001 From: Lenix <149199196+LenixDev@users.noreply.github.com> Date: Thu, 8 Jan 2026 18:49:33 -0800 Subject: [PATCH 2/5] feat(vehicle): add quick give keys option and flip vehicle menu item - Add "quick give keys" as a vehicle menu item configurable in client settings - Introduce conditional flip vehicle option in the radial menu when vehicle is flipped - Support additional vehicle items and windows configuration in the menu setup - Update English locale with "Quick Give Keys" label for new menu item - Ensure vehicle extras menu item appears when enabled in config --- client/main.lua | 19 ++++++++++++++++--- config/client.lua | 11 +++++++++++ locales/en.json | 3 ++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/client/main.lua b/client/main.lua index 22b5869..c18e566 100644 --- a/client/main.lua +++ b/client/main.lua @@ -113,7 +113,9 @@ function setupVehicleMenu(seat) menu = 'vehicleMenu' } - local vehicleItems = {{ + local vehicleItems = {} + if vehicleFlipped then + vehicleItems[#vehicleItems + 1] = { id = 'vehicle-flip', label = locale('options.flip'), icon = 'car-burst', @@ -121,11 +123,22 @@ function setupVehicleMenu(seat) TriggerEvent('radialmenu:flipVehicle') lib.hideRadial() end - }} + } + end - vehicleItems[#vehicleItems + 1] = convert(config.vehicleDoors) + if config.vehicleItems then + for i = 1, #config.vehicleItems do + vehicleItems[#vehicleItems + 1] = convert(config.vehicleItems[i]) + end + end + if config.vehicleWindows then vehicleItems[#vehicleItems + 1] = convert(config.vehicleWindows) + end + + if config.vehicleDoors then + vehicleItems[#vehicleItems + 1] = convert(config.vehicleDoors) + end if config.enableExtraMenu then vehicleItems[#vehicleItems + 1] = convert(config.vehicleExtras) diff --git a/config/client.lua b/config/client.lua index d9a051e..408b359 100644 --- a/config/client.lua +++ b/config/client.lua @@ -4,6 +4,17 @@ return { enableExtraMenu = true, flipTime = 15000, + vehicleItems = { + { + id = 'quickgivevehkeys', + icon = 'key', + label = locale('options.quickgivekeys'), + serverEvent = 'qbx_vehiclekeys:server:quickgivekeys', + outSideVehicleOnly = true, + nearByPlayerOnly = true + }, + }, + menuItems = { { id = 'citizen', diff --git a/locales/en.json b/locales/en.json index a5ee086..99e492d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -52,6 +52,7 @@ "passenger_seat": "Passenger Seat", "other_seats": "Other Seat", "rear_left_seat": "Rear Left Seat", - "rear_right_seat": "Rear Right Seat" + "rear_right_seat": "Rear Right Seat", + "quickgivekeys": "Quick Give Keys" } } From aaba2d8a145140969e3f020ca63ed30e4eba3b00 Mon Sep 17 00:00:00 2001 From: Lenix <149199196+LenixDev@users.noreply.github.com> Date: Thu, 8 Jan 2026 18:50:00 -0800 Subject: [PATCH 3/5] feat(radialmenu): add proximity and vehicle state conditions for menu items - Introduce flags to track player proximity to vehicles and players, and vehicle flipped state - Update menu setup to conditionally show items based on player state (onVehicleOnly, outSideVehicleOnly, nearByVehicleOnly, nearByPlayerOnly) - Add continuous background thread to monitor player and vehicle statuses and refresh radial menu accordingly - Extend config entries with new condition fields to control menu item visibility - Refactor vehicle menu setup to include vehicle flip option only when applicable --- client/main.lua | 76 +++++++++++++++++++++++++++++++++++++++++------ config/client.lua | 8 +++-- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/client/main.lua b/client/main.lua index c18e566..61fa22e 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,9 +1,25 @@ local config = require 'config.client' +local lastOutSideVehicle = false +local lastNearVehicle = false +local onVehicle = false +local lastNearPlayer = false +local vehicleFlipped = false + ----------------------- ------- Events -------- ----------------------- +lib.onCache('vehicle', function(vehicle) + if vehicle then + onVehicle = true + setupVehicleMenu(true) + else + onVehicle = false + setupVehicleMenu(false) + end +end) + if config.vehicleSeats then lib.onCache('vehicle', function(vehicle) if vehicle then @@ -52,6 +68,18 @@ end ----------------------- local function convert(tbl) + if tbl.onVehicleOnly and not onVehicle then + return + end + if tbl.outSideVehicleOnly and not lastOutSideVehicle then + return + end + if tbl.nearByVehicleOnly and not lastNearVehicle then + return + end + if tbl.nearByPlayerOnly and not lastNearPlayer then + return + end if tbl.items then local items = {} for _, v in pairs(tbl.items) do @@ -101,7 +129,11 @@ local function convert(tbl) action() end end, - keepOpen = tbl.keepOpen + keepOpen = tbl.keepOpen, + onVehicleOnly = tbl.onVehicleOnly, + outSideVehicleOnly = tbl.outSideVehicleOnly, + nearByVehicleOnly = tbl.nearByVehicleOnly, + nearByPlayerOnly = tbl.nearByPlayerOnly } end @@ -116,13 +148,13 @@ function setupVehicleMenu(seat) local vehicleItems = {} if vehicleFlipped then vehicleItems[#vehicleItems + 1] = { - id = 'vehicle-flip', - label = locale('options.flip'), - icon = 'car-burst', - onSelect = function() - TriggerEvent('radialmenu:flipVehicle') - lib.hideRadial() - end + id = 'vehicle-flip', + label = locale('options.flip'), + icon = 'car-burst', + onSelect = function() + TriggerEvent('radialmenu:flipVehicle') + lib.hideRadial() + end } end @@ -133,7 +165,7 @@ function setupVehicleMenu(seat) end if config.vehicleWindows then - vehicleItems[#vehicleItems + 1] = convert(config.vehicleWindows) + vehicleItems[#vehicleItems + 1] = convert(config.vehicleWindows) end if config.vehicleDoors then @@ -417,6 +449,32 @@ RegisterNetEvent('QBCore:Client:OnGangUpdate', function(gang) end end) +CreateThread(function() + while true do + local outSideVehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 5.0, false) ~= nil + local nearByVehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 5.0, true) ~= nil + local closestPlayer, _ = lib.getClosestPlayer(GetEntityCoords(cache.ped)) + local nearPlayer = closestPlayer ~= nil + + local playerCoords = GetEntityCoords(cache.ped) + local closestVehicle = lib.getClosestVehicle(playerCoords, 3.0) + local flipableVehicle = closestVehicle and not IsVehicleOnAllWheels(closestVehicle) + + local outsideStatusChanged = outSideVehicle ~= lastOutSideVehicle + local nearByVehicleChanged = nearByVehicle ~= lastNearVehicle + local nearPlayerChanged = nearPlayer ~= lastNearPlayer + local vehicleFlipStateChanged = IsVehicleOnAllWheels(closestVehicle) ~= flipableVehicle + if outsideStatusChanged or nearByVehicleChanged or nearPlayerChanged or closestVehicle and not vehicleFlipStateChanged then + lastOutSideVehicle = outSideVehicle + lastNearVehicle = nearByVehicle + lastNearPlayer = nearPlayer + vehicleFlipped = flipableVehicle + setupRadialMenu() + end + Wait(1500) + end +end) + local function createQBExport(name, cb) AddEventHandler(('__cfx_export_qb-radialmenu_%s'):format(name), function(setCB) setCB(cb) diff --git a/config/client.lua b/config/client.lua index 408b359..802b8f4 100644 --- a/config/client.lua +++ b/config/client.lua @@ -25,13 +25,15 @@ return { id = 'givenum', icon = 'address-book', label = 'Give Contact Details', - event = 'qb-phone:client:GiveContactDetails' + event = 'qb-phone:client:GiveContactDetails', + nearByPlayerOnly = true }, { id = 'getintrunk', icon = 'car', label = 'Get In Trunk', - event = 'qb-trunk:client:GetIn' + event = 'qb-trunk:client:GetIn', + outSideVehicleOnly = true }, { id = 'cornerselling', @@ -61,6 +63,7 @@ return { icon = 'car-side', label = 'Take Out Vehicle', event = 'police:client:SetPlayerOutVehicle', + nearByVehicleOnly = true }, { id = 'stealPlayer', @@ -377,6 +380,7 @@ return { icon = 'eye-slash', label = 'Show/Hide Meter', event = 'qb-taxi:client:toggleMeter', + onVehicleOnly = true }, { id = 'togglemouse', From db05648ef28886bcf8fa22cf0ea24293ce79a165 Mon Sep 17 00:00:00 2001 From: Lenix <149199196+LenixDev@users.noreply.github.com> Date: Thu, 8 Jan 2026 19:07:37 -0800 Subject: [PATCH 4/5] feat(blips): add toggleable blip categories with persistence - Implement toggleable blip categories with show/hide functionality - Save and load blip visibility preferences in player metadata - Register toggleable blips and apply saved visibility on player load - Add commands and events to toggle specific blip categories like shops - Update locale strings for blip shown/hidden notifications - Bump fxmanifest version to 0.3.0 to reflect new features --- client/main.lua | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ fxmanifest.lua | 2 +- locales/en.json | 4 +- server/main.lua | 8 ++++ 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/client/main.lua b/client/main.lua index 61fa22e..541e7c9 100644 --- a/client/main.lua +++ b/client/main.lua @@ -6,6 +6,10 @@ local onVehicle = false local lastNearPlayer = false local vehicleFlipped = false +local toggleableBlips = {} +local categoryVisible = {} +local metadataLoaded = false + ----------------------- ------- Events -------- ----------------------- @@ -449,6 +453,119 @@ RegisterNetEvent('QBCore:Client:OnGangUpdate', function(gang) end end) +local loadBlipPreferences = function() + if metadataLoaded then return end + + local playerData = QBX.PlayerData + if playerData and playerData.metadata then + local blipPreferences = playerData.metadata.blipPreferences or {} + + for category, isVisible in pairs(blipPreferences) do + categoryVisible[category] = isVisible + + if toggleableBlips[category] then + for blipId, _ in pairs(toggleableBlips[category]) do + if DoesBlipExist(blipId) then + SetBlipAlpha(blipId, isVisible and 255 or 0) + end + end + end + end + + metadataLoaded = true + end +end + +local saveBlipPreferences = function () + TriggerServerEvent('radialmenu:server:saveBlipPreferences', categoryVisible) +end + +local toggleBlipsForCategory = function(category) + if not category then + lib.print.error("No category provided for toggling blips.") + return + end + + if categoryVisible[category] == nil then + categoryVisible[category] = true -- Default to visible + else + categoryVisible[category] = not categoryVisible[category] + end + + local isVisible = categoryVisible[category] + local toggledCount = 0 + local blipCategoryName = string.gsub(category, "_", " ") -- Replaces underscores with spaces + blipCategoryName = blipCategoryName:sub(1,1):upper() .. blipCategoryName:sub(2) -- Capitalizes the first letter + + if toggleableBlips[category] then + for blipId, _ in pairs(toggleableBlips[category]) do + if DoesBlipExist(blipId) then + if isVisible then + SetBlipAlpha(blipId, 255) -- Show blip + else + SetBlipAlpha(blipId, 0) -- Hide blip + end + toggledCount = toggledCount + 1 + else + if toggleableBlips[category] then + toggleableBlips[category][blipId] = nil + end + end + end + end + + saveBlipPreferences() + + local status = isVisible and locale('general.blip_shown') or locale('general.blip_hidden') + exports.qbx_core:Notify(("%s %s"):format(blipCategoryName, status), 'info') + return isVisible +end + +RegisterNetEvent('qbx_radialmenu:registerToggleableBlip', function(blipId, category) + if not category then + lib.print.error(("No category provided when registering blipId: %s"):format(tostring(blipId))) + return + end + if not blipId then + lib.print.error(("No blipId provided for registration in category: %s"):format(category)) + return + end + + if DoesBlipExist(blipId) then + if not toggleableBlips[category] then + toggleableBlips[category] = {} + end + toggleableBlips[category][blipId] = true + + if not metadataLoaded then + loadBlipPreferences() + end + + local isCurrentlyVisible = categoryVisible[category] + if isCurrentlyVisible == nil then + isCurrentlyVisible = true -- default new categories/blips to visible + categoryVisible[category] = true + saveBlipPreferences() + end + + if isCurrentlyVisible then + SetBlipAlpha(blipId, 255) + else + SetBlipAlpha(blipId, 0) + end + else + lib.print.warn(("Attempted to register non-existent blip %s for category: %s"):format(tostring(blipId), category)) + end +end) + +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + loadBlipPreferences() +end) + +RegisterNetEvent('qbx_radialmenu:client:toggleShops', function() + toggleBlipsForCategory('shops') +end) + CreateThread(function() while true do local outSideVehicle = lib.getClosestVehicle(GetEntityCoords(cache.ped), 5.0, false) ~= nil diff --git a/fxmanifest.lua b/fxmanifest.lua index df15db9..2116562 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -3,7 +3,7 @@ game 'gta5' description 'qbx_radialmenu' repository 'https://github.com/Qbox-project/qbx_radialmenu' -version '0.1.0' +version '0.3.0' ox_lib 'locale' diff --git a/locales/en.json b/locales/en.json index 99e492d..2547fc5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -42,7 +42,9 @@ "getintrunk_command_desc": "Enter the Trunk", "putintrunk_command_desc": "Put the Player in the Trunk", "gang_radial": "Gang", - "job_radial": "Job" + "job_radial": "Job", + "blip_shown": "Blip shown", + "blip_hidden": "Blip hidden" }, "options": { "flip": "Flip the Vehicle", diff --git a/server/main.lua b/server/main.lua index 35865be..e886400 100644 --- a/server/main.lua +++ b/server/main.lua @@ -7,3 +7,11 @@ end) RegisterNetEvent('hospital:server:SetDeathStatus', function(isDead) TriggerClientEvent('radialmenu:client:deadradial', source, isDead) end) + +RegisterNetEvent('qbx_radialmenu:server:saveBlipPreferences', function(blipPreferences) + local playerData = exports.qbx_core:GetPlayer(source) + + if playerData then + playerData.Functions.SetMetaData("blipPreferences", blipPreferences) + end +end) \ No newline at end of file From be5df3387b88094c3dc117142f5d48c51ca88c33 Mon Sep 17 00:00:00 2001 From: Lenix <149199196+LenixDev@users.noreply.github.com> Date: Fri, 9 Jan 2026 08:23:16 -0800 Subject: [PATCH 5/5] docs(blips): add documentation for blip usage and API - Added blip usage section to README including toggleBlip and registerToggleableBlip functions - Documented parameters and example usage for registerToggleableBlip feat(blips): implement toggleable blips with persistence support - Added loadBlipPreferences function to load blip visibility from player metadata - Set blip alpha visibility based on saved preferences on player load - Implemented toggleBlipsForCategory to show/hide all blips in a category - Created registerToggleableBlip to register individual blip handles under categories - Integrated blip toggling with metadata persistence and visibility updates - Exposed toggleBlip and registerToggleableBlip functions as exports for external use --- README.md | 25 ++++++++++++++ client/main.lua | 91 ++++++++++++++++++++++++------------------------- 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 3fbecd1..feec039 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,28 @@ Radial Menu Used With qbx_Core :arrows_counterclockwise: **Supports FontAwesome Icons!** Get the name from [FontAwesome](https://fontawesome.com/v5.0/icons?d=gallery&p=2&s=brands,light,regular,solid&m=free) (v5) and use the icon's name in the config.lua for the icon (no `fa-` or `#` just the name like `arrow-right`) + + +## Blip Usage + +## toggleBlip +```lua +exports.qbx_radialmenu:toggleBlip(category) +``` +### Parameters +- `category` (string): The unique category of the blip to toggle + + +## registerToggleableBlip +```lua +exports.qbx_radialmenu:registerToggleableBlip(blipId, category) +``` +### Parameters +- `blipId` (number): The blip handle to register +- `category` (string): The unique category of the blip + +### Example +```lua +local blipHandle = AddBlipForCoord(0.0, 0.0, 0.0) +exports.qbx_radialmenu:registerToggleableBlip(blipHandle, "shops") +``` \ No newline at end of file diff --git a/client/main.lua b/client/main.lua index 541e7c9..1581482 100644 --- a/client/main.lua +++ b/client/main.lua @@ -231,6 +231,29 @@ local function isEMS() return QBX.PlayerData.job.type == 'ems' and QBX.PlayerData.job.onduty end +local loadBlipPreferences = function() + if metadataLoaded then return end + + local playerData = QBX.PlayerData + if playerData and playerData.metadata then + local blipPreferences = playerData.metadata.blipPreferences or {} + + for category, isVisible in pairs(blipPreferences) do + categoryVisible[category] = isVisible + + if toggleableBlips[category] then + for blipId, _ in pairs(toggleableBlips[category]) do + if DoesBlipExist(blipId) then + SetBlipAlpha(blipId, isVisible and 255 or 0) + end + end + end + end + + metadataLoaded = true + end +end + -- Events RegisterNetEvent('radialmenu:client:deadradial', function(isDead) if isDead then @@ -422,6 +445,7 @@ end) -- Sets the metadata when the player spawns AddEventHandler('QBCore:Client:OnPlayerLoaded', function() setupRadialMenu() + loadBlipPreferences() end) RegisterNetEvent('QBCore:Client:OnJobUpdate', function(job) @@ -453,49 +477,26 @@ RegisterNetEvent('QBCore:Client:OnGangUpdate', function(gang) end end) -local loadBlipPreferences = function() - if metadataLoaded then return end - - local playerData = QBX.PlayerData - if playerData and playerData.metadata then - local blipPreferences = playerData.metadata.blipPreferences or {} - - for category, isVisible in pairs(blipPreferences) do - categoryVisible[category] = isVisible - - if toggleableBlips[category] then - for blipId, _ in pairs(toggleableBlips[category]) do - if DoesBlipExist(blipId) then - SetBlipAlpha(blipId, isVisible and 255 or 0) - end - end - end - end - - metadataLoaded = true - end -end - local saveBlipPreferences = function () TriggerServerEvent('radialmenu:server:saveBlipPreferences', categoryVisible) end +---@param category string local toggleBlipsForCategory = function(category) if not category then - lib.print.error("No category provided for toggling blips.") + lib.print.warn("No category provided for toggling blips.") return end + local toggledCount = 0 + local blipCategoryName = string.gsub(category, "_", " ") -- Replaces underscores with spaces + blipCategoryName = blipCategoryName:sub(1,1):upper() .. blipCategoryName:sub(2) -- Capitalizes the first letter if categoryVisible[category] == nil then - categoryVisible[category] = true -- Default to visible + categoryVisible[category] = true -- Default to visible else - categoryVisible[category] = not categoryVisible[category] + categoryVisible[category] = not categoryVisible[category] end - local isVisible = categoryVisible[category] - local toggledCount = 0 - local blipCategoryName = string.gsub(category, "_", " ") -- Replaces underscores with spaces - blipCategoryName = blipCategoryName:sub(1,1):upper() .. blipCategoryName:sub(2) -- Capitalizes the first letter if toggleableBlips[category] then for blipId, _ in pairs(toggleableBlips[category]) do @@ -521,21 +522,23 @@ local toggleBlipsForCategory = function(category) return isVisible end -RegisterNetEvent('qbx_radialmenu:registerToggleableBlip', function(blipId, category) +---@param blipHandle number +---@param category string +local registerToggleableBlip = function(blipHandle, category) if not category then - lib.print.error(("No category provided when registering blipId: %s"):format(tostring(blipId))) + lib.print.error(("No category provided when registering blipId: %s"):format(tostring(blipHandle))) return end - if not blipId then + if not blipHandle then lib.print.error(("No blipId provided for registration in category: %s"):format(category)) return end - if DoesBlipExist(blipId) then + if DoesBlipExist(blipHandle) then if not toggleableBlips[category] then toggleableBlips[category] = {} end - toggleableBlips[category][blipId] = true + toggleableBlips[category][blipHandle] = true if not metadataLoaded then loadBlipPreferences() @@ -549,22 +552,15 @@ RegisterNetEvent('qbx_radialmenu:registerToggleableBlip', function(blipId, categ end if isCurrentlyVisible then - SetBlipAlpha(blipId, 255) + SetBlipAlpha(blipHandle, 255) else - SetBlipAlpha(blipId, 0) + SetBlipAlpha(blipHandle, 0) end else - lib.print.warn(("Attempted to register non-existent blip %s for category: %s"):format(tostring(blipId), category)) + lib.print.warn(("Attempted to register non-existent blip %s for category: %s"):format(tostring(blipHandle), category)) end -end) - -RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() - loadBlipPreferences() -end) +end -RegisterNetEvent('qbx_radialmenu:client:toggleShops', function() - toggleBlipsForCategory('shops') -end) CreateThread(function() while true do @@ -613,3 +609,6 @@ end exports('RemoveOption', removeOption) createQBExport('RemoveOption', removeOption) + +exports('toggleBlip', toggleBlipsForCategory) +exports('registerToggleableBlip', registerToggleableBlip) \ No newline at end of file