Retro Radial
Development
Section titled โDevelopmentโThis file is for developers integrating other resources with rs_radial or extending the system.
Integration Shape
Section titled โIntegration Shapeโrs_radial is centered around:
- a static config page system (
Config.Pages) - a runtime API for other resources to add / remove / submenu items
- server-side validation that re-runs visibility rules on dispatch
- a predicate registry for sharing reusable
canSeecallbacks - a customer hook surface (
custom_functions.lua) for inline action handlers
When integrating with it, think in terms of:
- registering items from your resource
- registering predicates your items can reference by name
- listening to hook events (
rs_radial:event:*) - respecting the server-authority model for any item with side effects
Important Integration Areas
Section titled โImportant Integration AreasโRuntime Item API
Section titled โRuntime Item APIโClient-side:
exports['rs_radial']:AddMenuItem('main', { id = 'my_action', label = 'MY ACTION', icon = 'fa-bolt', iconType = 'class', type = 'client', event = 'my_resource:client:doThing'})
exports['rs_radial']:RemoveMenuItem('main', 'my_action')exports['rs_radial']:UpdateMenuItem('main', 'my_action', { label = 'NEW LABEL' })
exports['rs_radial']:AddSubMenu('my_page', { label = 'MY PAGE', items = { ... }})Server-side, per-player:
exports['rs_radial']:AddMenuItemForPlayer(src, 'main', { ... })exports['rs_radial']:RemoveMenuItemForPlayer(src, 'main', 'my_action')When the resource that called AddMenuItem stops, rs_radial sweeps all items it owns. Reloads stay clean.
Predicate Registry
Section titled โPredicate RegistryโReusable canSee callbacks shared across items.
Client-side:
exports['rs_radial']:RegisterPredicate('isOnDuty', function(ctx) return ctx.onDuty == trueend)Then in config.lua or any item:
{ id='x', label='X', visibleIf = 'isOnDuty', ... }Server-side (for items with Config.Security.serverSideValidation = true):
exports['rs_radial']:RegisterServerPredicate('isCaptain', function(src) return Bridge.getJob(src).grade >= 3end)Hook Events
Section titled โHook Eventsโrs_radial fires events for every significant action. Listen with AddEventHandler('rs_radial:event:<name>', fn).
Client hooks:
menuOpened,menuClosedpageChanged(fromPage, toPage)itemClicked(itemId, pageId, payload)itemConfirmed(itemId, pageId)/itemCancelled(itemId, pageId)itemFailed(itemId, reason)reasonis one of'cooldown','blocked','cuffed','dead', etc.settingsSaved('save' | 'reset')vehicleControlUsed(controlId, active)blipToggled(catId, enabled)
Server hooks:
server:dispatched(src, pageId, itemId)fired AFTER a server-validated item runsserver:blocked(src, pageId, itemId, reason)fired when server validation rejects an item
Custom Functions
Section titled โCustom FunctionsโFor ultra-simple inline actions without registering a new event:
-- custom_functions.luaCustomFunctions = CustomFunctions or {}
CustomFunctions.toggleEngine = function(args) local veh = GetVehiclePedIsIn(PlayerPedId(), false) SetVehicleEngineOn(veh, not GetIsVehicleEngineRunning(veh), false, true)endThen in config.lua:
{ id='engine', label='ENGINE', iconType='image', icon='engine.png', type='customFunction', event='toggleEngine' }custom_functions.lua is loaded BEFORE client.lua so the table exists when items are registered.
Async Items
Section titled โAsync ItemsโFor items that take time server-side:
-- config.lua{ id='heist', label='HEIST', async = true, progressText = 'Crackingโฆ', type='server', event='heist:start' }
-- server-side handlerRegisterNetEvent('heist:start', function() local src = source local ok, err = doHeist(src) exports['rs_radial']:CompleteAsyncAction('heist', ok, err)end)The NUI shows a spinner from click until CompleteAsyncAction resolves it.
State-Bag Refresh
Section titled โState-Bag RefreshโIf your item visibility depends on a state bag (e.g. Player(src).state.isCop), tell rs_radial to refresh when the value changes:
exports['rs_radial']:WatchStateBagKey('isCop')External Resource Expectations
Section titled โExternal Resource ExpectationsโFrameworks
Section titled โFrameworksโAuto-detected:
- QBCore
- Qbox / qbx_core
- ESX
Standalone mode is supported push job/gang data manually via SetPlayerJob / SetPlayerGang.
Inventories
Section titled โInventoriesโAuto-detected:
ox_inventoryqb-inventoryqbx_coreinventory- ESX
- Custom (via
Config.Inventory.customHasItem)
Compatibility Bridges
Section titled โCompatibility Bridgesโbridge/ox_lib.luaforwardlib.addRadialItem/lib.removeRadialItem/lib.registerRadialcalls into rs_radial. Opt-in.- No qb-radialmenu bridge ships by default the API surface is different. If you need one, the pattern is the same as
bridge/ox_lib.lua.
Development Rules Of Thumb
Section titled โDevelopment Rules Of Thumbโ- Treat
Config.Pagesas the source of truth for static items; use the runtime API for items that only make sense for some players or some sessions. - Always set
Config.Security.serverSideValidation = truefor items with real side effects. - Use predicates instead of duplicating gating logic across items.
- Register your hooks early so you donโt miss
menuOpenedon the first NUI bootstrap. - Donโt poll for state use
WatchStateBagKeyor callRefresh()from your job/state change event.
Useful Files To Start With
Section titled โUseful Files To Start Withโfxmanifest.luaload order and escrow_ignoreconfig.luaConfig.Pagesand feature togglescustom_functions.luacustomer hook surfacebridge/ox_lib.luaexample of how a bridge layer is builtdocs/configuration.mdfull config referencedocs/features.mditem schema and action types
What This File Is Not
Section titled โWhat This File Is NotโThis is not a full internal architecture spec. It is an integration-oriented guide so another engineer can find the right entrypoints quickly.