A handheld signal jammer for FiveM. A player takes the device out of the inventory and holds it in the right hand. Its screen is a live LCD: the owner sets the field radius, exempts players by server ID and starts the field. Every player inside the field loses radio and phone calls. The server removes them from their pma-voice radio channel and call channel, and removes them again if they tune in or call again while they are inside. Phone and radio scripts with a built-in integration are closed, locked or switched off as well. No other resource has to be patched.
It runs on qbx_core, qb-core, ESX Legacy or standalone. The device comes from ox_inventory, the framework inventory, another built-in inventory, or a chat command with an ACE permission. Version 1.0.0.
Features
Device
- A 3D model in the right hand, with an upper-body animation: the player can still walk.
- The screen is a live LCD, a 1024 x 768 page rendered in game. Players nearby see the device and its screen up to
Config.Visual.distance(35 m), read only. - Every device that is held at the same time uses one of the 8 models in
stream/, so 8 players can hold a jammer at once. A device that is put away frees its model, and its field keeps running. - LCD controls: START / STOP, range (− / + and a slider), Exempt Myself, Exempt an ID (keypad), remove an exemption, PUT AWAY.
- Hold the right mouse button for the inspect camera: it sits in front of the screen, and the mouse moves a cursor on it.
- An active device beeps every 5 s. Players within 10 m hear it, quieter the farther they are.
Field
- A sphere around its owner, 10 to 50 m (whole meters, set on the LCD). It moves with the owner. Walls do not block it.
- The server works it out every 250 ms (
Config.TickMs) from what it reads itself: the OneSync positions, health and vehicles of the players, and their routing buckets. No client request or event carries a position, and the server alone decides who is jammed. - Players inside lose their pma-voice radio channel and call channel. A player who tunes in or calls again is removed again within one server tick. Talking to players nearby (proximity voice) is not affected.
- On pma-voice builds with
addRadioDisableBit(v7.0.2-rc3,main), the radio key of a jammed player is also locked on his own client (not on servers withsv_stateBagStrictMode). - Built-in integrations end calls, close phones and leave radio channels of the phone and radio scripts listed in Integrations.
- Silent by default: xr-jammer itself shows a jammed player no message. Some phone and radio scripts give their own feedback (Known limits).
Config.NotifyJammed = trueshows "No signal." once when he enters a field. - Exemptions: the owner himself (a separate switch) and up to 16 players by server ID, bound to their character session.
- Immune jobs (
Config.ImmuneJobs) and an open hook for your own immunity rules. - A held or active device shuts down when its owner gets into a vehicle, dies, loses the item, loses access, switches character or changes routing bucket.
- Limits: 32 active fields on the server, 1 s wait after STOP, settings forgotten after 5 minutes off and put away, a per-player rate limit.
Server authority
- The item is used only through a trusted server path: ox_inventory's server-side use event, the usable item of the framework or inventory, the chat command, or the server export
UseDevice. The server checks ownership again on every use. - An LCD request carries only the action and one validated value. Owner and positions come from the server, and the server alone decides who is jammed.
- Exemptions are bound to character sessions, and fields never reach into another routing bucket.
- While the framework, the inventory or pma-voice is not ready, START is locked and active fields are switched off: the LCD never shows a field that blocks nothing.
Integration
- Server exports
IsJammed,UseDevice,ResetPlayerSession,AreIntegrationsReady, client exportIsJammed, the local client eventxr-jammer:jammedand the replicated player statesignalJammed. - Open files:
config.lua, the language files, the server and client hooks, the voice, phone and radio integrations, the item definitions ininstall/and the LCD pages inhtml/.
Languages
- 14 languages for the LCD, the notifications, the key binding names and the chat help.
Requirements
| Requirement | Needed | Notes |
|---|---|---|
| OneSync | yes | The manifest depends on it. The field is calculated with server entity natives. |
| pma-voice | yes | v6.6.2 or newer, started under the name in Config.Voice.resource. It is not a manifest dependency: while it is not running, START stays locked on every device. The optional push-to-talk lock works on builds with addRadioDisableBit (v7.0.2-rc3, or main from October 2023 on). |
| Framework | no | qbx_core, qb-core (1.2.x and 1.3.x) or ESX Legacy (es_extended 1.9.4 or newer). Without one, the script runs standalone. |
| Inventory | no | ox_inventory 2.38.0 or newer; on qb-core 2.38.0 to 2.41.0, because ox_inventory 2.42.0 and newer have no qb-core bridge and do not run on qb-core. The other inventories: Item setup and Other inventories. Without one, a chat command with an ACE permission takes the device out (Command mode). |
| Phone and radio scripts | no | Optional. The pma-voice radio and call channels are cut whatever script uses them. None of them is a start-up dependency. |
- xr-jammer loads no library and uses no database.
- No other resource has to be patched.
- The resource folder must be named
xr-jammer. The ox_inventory item calls the exportxr-jammer.useJammer, other scripts reachexports['xr-jammer'], and its events start withxr-jammer:. - Other voice systems (SaltyChat, TokoVOIP, mumble-voip, YACA) are not supported. With them START stays locked.
The resource is protected by Cfx.re Asset Escrow. Your server's license key must belong to the Cfx.re account that bought xr-jammer.
Installation
Download xr-jammer from portal.cfx.re (Granted Assets). Use the Cfx.re account you bought it with.
Extract the
xr-jammerfolder into your resources, e.g.resources/[scripts]/xr-jammer. Do not rename the folder.Add the jammer item to your inventory: Item setup. Without an inventory, use the Command mode.
Add it to
server.cfgafter the framework, the inventory and pma-voice:# your framework: qbx_core, qb-core or es_extended (none on standalone servers) ensure qbx_core # your inventory (none in command mode) ensure ox_inventory ensure pma-voice # your phone and radio scripts (optional) go here ensure xr-jammerPhone and radio scripts are optional. They are found while the server runs, so a script that starts later is picked up as well.
Restart the server. About 2 s after xr-jammer starts, the server console prints one status line:
[xr-jammer] v1.0.0 | framework: qbx | inventory: ox | voice: pma-voice (running)Check that framework and inventory are the ones you expect:
Setup Status line Qbox with ox_inventory framework: qbx | inventory: oxqb-core or ESX with ox_inventory framework: qb | inventory: oxorframework: esx | inventory: oxqb-core with qb-inventory, ps-inventory or lj-inventory framework: qb | inventory: frameworkESX with its default inventory framework: esx | inventory: frameworkqs-inventory, tgiann-inventory, origen_inventory inventory: qs,inventory: tgiann,inventory: origencore_inventory, codem-inventory inventory: customStandalone without an inventory framework: standalone | inventory: noneinventory: frameworkmeans that the item is registered through the usable-item API of the framework. It is the expected value for qb-inventory, ps-inventory, lj-inventory and the ESX default inventory.'qb-inventory'is not a value ofConfig.Inventory.The line names the framework and the inventory that xr-jammer uses at that moment. If the framework or the inventory starts after that line, for example when you start it by hand, the status line is printed again with the names xr-jammer uses now. Otherwise it is printed once.
If something is missing, a second line names it, e.g.
[xr-jammer] not ready: START stays locked until pma-voice is running. See Troubleshooting.Check in game: give yourself the item, use it, hold the right mouse button, point the cursor at START and left click.
Do not keep backup copies inside the resource, and never in locales/. The manifest loads locales/*.lua, so FiveM loads every file in locales/ whose name ends in .lua. A copy such as de - Copy.lua or de_old.lua is loaded as well, and it can overwrite the language you edited. Keep backups outside your resources folder.
Most changes need only config.lua, locales/*.lua, server/editable.lua, client/editable.lua and the files in integrations/. Every open file is listed in Open and encrypted files. After a change, restart the resource or the server.
Item setup
The item name in every definition must be the same as Config.Item (default jammer, lower case). The item is never consumed. install/jammer.png is the item image.
ox_inventory (QBCore, Qbox, ESX)
File: install/ox_inventory.lua. Needs ox_inventory 2.38.0 or newer: xr-jammer listens to the server-side use event that ox_inventory raises from that version on.
- qb-core: ox_inventory 2.38.0 to 2.41.0. ox_inventory 2.42.0 and newer have no qb-core bridge and do not run on qb-core at all.
- Qbox and ESX: ox_inventory 2.38.0 or newer.
- Copy the
['jammer']entry into the table ofox_inventory/data/items.lua, next to the other items. Do not replace youritems.luawith the file. - Copy
install/jammer.pngtoox_inventory/web/images/. - Restart the server.
['jammer'] = {
label = 'Signal Jammer',
weight = 850,
stack = false,
close = true,
consume = 0,
client = { image = 'jammer.png', export = 'xr-jammer.useJammer' },
},
consume = 0is required. An item with an export is consumed by default, and the jammer must stay in the inventory.'xr-jammer.useJammer'contains the folder name of the resource.- While the device is held or its field is active, ox_inventory refuses to move the jammer out of the owner's inventory (transfer lock). Removals through exports or admin commands are not stopped: the device then shuts down on the next server tick.
- ox_inventory ignores a second use of an item within 0.5 s (longer on some forks).
qb-inventory, ps-inventory, lj-inventory (QBCore)
File: install/qb-core.lua.
- Copy the
jammerline into the item list ofqb-core/shared/items.lua, next to the other items. Do not replace youritems.luawith the file. - Copy
install/jammer.pnginto the item image folder of your inventory. - Restart the server.
jammer = { name = 'jammer', label = 'Signal Jammer', weight = 850, type = 'item', image = 'jammer.png', unique = true, useable = true, shouldClose = true, description = 'Handheld device that blocks radio and phone signals around it' },
useable = trueis required: ps-inventory and lj-inventory do not run an item without it.- Other usable items must already work on your server: the inventory and qb-core have to be a matching pair.
- qb-inventory 1.x and lj-inventory are legacy options: the jammer works where the inventory itself runs usable items. Both read the usable-item callback in the form that official qb-core stored until May 2024 (commit 9fbab7a of 2024-05-25). With a qb-core from before that change, or a fork that still stores the callback that way, they run usable items, the jammer included. With a later official qb-core, 1.3.0 included, they run no usable item of any resource. The version number does not show the difference: official qb-core said 1.2.6 until January 2025. Check that another usable item works.
- Qbox reads no qb-core item list. Qbox with ox_inventory: use
install/ox_inventory.lua. Qbox with any other inventory: see Other inventories.
ESX default inventory
File: install/esx.sql.
- Run the file once on the database of your server.
- Restart the server.
INSERT INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES
('jammer', 'Signal Jammer', 1, 0, 1);
- The item must exist in the
itemstable. es_extended does not run the use of an item that is not in it. weightuses the unit of your other ESX items. Adjust it to your server.- es_extended reads which items are usable when a character loads. A character that was already in the game when xr-jammer started (for example after
ensure xr-jammeron a running server) has no Use option for the item: log out and back in, or restart the server. - ESX with ox_inventory: use
install/ox_inventory.luainstead.
qs-inventory, tgiann-inventory, origen_inventory and the rest
qs-inventory, tgiann-inventory, origen_inventory, core_inventory, codem-inventory and Qbox without ox_inventory: see Other inventories.
Other inventories
qs-inventory
Built in, based on the vendor's documentation. xr-jammer registers the item with qs-inventory's CreateUsableItem export and checks ownership with GetItemTotalAmount.
Add the item to the item list of qs-inventory as described in its documentation, with useable = true: item configuration. The documented fields are type = 'item', image, unique, useable = true, shouldClose = true and description. install/jammer.png is the item image.
tgiann-inventory
Built in, based on the vendor's documentation. tgiann-inventory runs the usable-item callback of the framework, which xr-jammer registers. Ownership is checked with its HasItem export.
Add an item file to the tgiann-inventory/items folder with useable = true. It must not be a roleplay item: a roleplay item skips the framework's usable-item callback.
origen_inventory
Ownership is checked with origen_inventory's HasItem export on every framework.
- ESX: built in, based on the vendor's documentation, once the vendor's ESX override files are installed as its installation guide describes. For where to define the item, see the origen_inventory documentation.
- QBCore: origen_inventory reads your existing
qb-core/shared/items.lua, so useinstall/qb-core.lua. - Qbox: for where to define the item, see the origen_inventory documentation.
On QBCore and Qbox the vendor does not document how a used item reaches the framework. xr-jammer still registers the item with the framework and prints this notice at every start:
[xr-jammer] origen_inventory does not document how a used item reaches QBCore / Qbox: if using the item does nothing, see README > Other inventories (exports['xr-jammer']:UseDevice(source))
If using the item does nothing there, call exports['xr-jammer']:UseDevice(source) from the server-side code that your origen_inventory setup runs when the item is used (see core_inventory and codem-inventory for the call). Do this only when the item does nothing: otherwise one use would take the device out and put it away again.
core_inventory and codem-inventory
Not built in: neither documents a usable-item API. With Config.Inventory = 'auto' they run in 'custom' mode, and the server console prints at every start:
[xr-jammer] core_inventory has no built-in integration: see README > Other inventories
Wire them in four steps. The same steps work for any inventory that has no built-in integration.
Create the item as the inventory's documentation describes, with the name of
Config.Item.Fill
Editable.HasIteminserver/editable.lua. For core_inventory (documented to return a boolean):function Editable.HasItem(src) return exports['core_inventory']:hasItem(src, Config.Item, 1) == true endcodem-inventory documents
exports['codem-inventory']:HasItem(source, items, amount), but not what it returns. Check its return value before you rely on it.Editable.HasItemmust returntrueorfalseat once (noWait, no database query).Call
exports['xr-jammer']:UseDevice(source)from the server-side code that runs when the item is used. If your inventory runs the usable items of qb-core, a server script of your own can do it:local QBCore = exports['qb-core']:GetCoreObject() QBCore.Functions.CreateUseableItem('jammer', function(source) exports['xr-jammer']:UseDevice(source) end)UseDevicechecks ownership throughEditable.HasItemagain, so it cannot hand out a device. Return values: Developer API.Set
Config.Inventory = 'custom'. This ends the console notice.
The shipped Editable.HasItem returns nil, which in 'custom' mode means "does not own it". Until you fill it, every use answers "You do not have a jammer.", and no console line names the unfilled hook. START stays locked (console: inventory "custom" needs Editable.HasItem in server/editable.lua) only when the function is removed from server/editable.lua or the file does not load. No transfer lock exists outside ox_inventory: when the item leaves the owner's inventory, the device shuts down on the next server tick.
Qbox without ox_inventory
Qbox reads no qb-core item list, so install/qb-core.lua does not apply. With qs-inventory, tgiann-inventory or origen_inventory, follow their sections above. With any other inventory, use the 'custom' steps of core_inventory and codem-inventory.
Command mode (no inventory)
Config.Inventory = 'none' replaces the item with a chat command and an ACE permission. 'auto' selects this mode on a standalone server without a built-in inventory.
Config.Command = {
enabled = true, -- must stay true in this mode: it is the only way to take the device out
name = 'jammer', -- chat command: takes the device out / puts it away
ace = 'jammer.use', -- ACE permission that replaces owning the item
}
# Admins may use the jammer
add_ace group.admin jammer.use allow
# Standalone: create a group and add players to it
add_ace group.jammer jammer.use allow
add_principal identifier.license:0123456789abcdef0123456789abcdef01234567 group.jammer
/jammertakes the device out, and again puts it away. The chat shows a help text for it.- A player without the ACE gets "You are not allowed to use the jammer."
- The ACE is checked on every server tick while the device is held or active: a removed ACE shuts the device down within one tick.
- A player is in
group.adminonly if yourserver.cfg(or your admin tooling) adds him withadd_principal. - With
enabled = falseSTART stays locked (console:inventory "none" needs Config.Command.enabled = true: there is no other way to take the device out).
Controls and usage
Taking the device out
- Use the item (command mode:
/jammer). The device appears in the right hand. Use the item again to put it away. - A device that is put away keeps its field running, its range and its exemptions.
- At most 8 devices can be held at the same time (one per model in
stream/). The next player gets "Too many jammers are being held right now. Try again in a moment."
Keys
| Input | Action |
|---|---|
| Left mouse button while looking at the screen | Presses the LCD button under the crosshair. Without the cursor key, the camera must be within 1.2 m of the screen (usually first-person view). |
Right mouse button (hold, Config.Keys.cursor) |
Inspect camera in front of the screen. The mouse moves a cursor on the LCD, and the left mouse button presses the button under it. |
Backspace (Config.Keys.lower) |
Puts the device away. The field stays on. |
/jammer_lower |
The same as Backspace. |
/jammer_stop |
Switches your field off, also while the device is put away. |
- While the device is in the hand, attack, aim, the weapon wheel and the melee attacks are disabled, so a click never fires a weapon or throws a punch.
- Getting into a vehicle or dying puts the device away and switches its field off.
- Players can change both keys in Settings > Key Bindings > FiveM: "Jammer: LCD cursor (hold)" and "Jammer: put the device away (the field stays on)".
- The chat shows help texts for
/jammer_stopand/jammer_lower.
The LCD
| Element | What it does |
|---|---|
| Status | CONNECTING until the server answered, then ACTIVE or STANDBY. |
| RANGE − / + and slider | Field radius in whole meters, from Config.Range.min to Config.Range.max. The slider sends its value when you release it. |
| SIGNAL | JAMMING while the field is on, IDLE otherwise. |
| Exempt Myself | Your own field leaves you alone. Without it, your own field jams you as well. |
| Exempt an ID | Opens a keypad: digits, C (clear), ⌫ (delete), ADD EXEMPTION, CANCEL. |
| ID chips | One per exempt player. Click a chip to remove the exemption. ‹ and › scroll the row. |
| START / STOP | Switches the field. START is locked while the server is not ready; STOP always works. |
| PUT AWAY | The same as Backspace. |
| Footer | WAITING FOR SERVER while the device connects, WORKING… while a request waits for the server, NO RESPONSE · TRY AGAIN after 2.5 s without an answer, SYSTEM LINK NOT READY while START is locked, YOUR SIGNAL IS BLOCKED · FIELDS: n while you are jammed, otherwise the key hint. The owner's server ID on the right. |
Other players' devices show READ ONLY, and their exemption count shows PRIVATE. The small line above the title is Config.Brand.
The field
- A sphere with the selected radius around the owner. It moves with him. Height counts, walls and interiors do not block it, and it never reaches into another routing bucket.
- A player inside loses his pma-voice radio channel and call channel, and the built-in phone and radio integrations end his call, close his phone and leave his radio (the radio about half a second later, see Radios). While he stays inside, every channel he joins again is dropped within one server tick.
- Talking to players nearby (proximity voice) is not affected. Only the pma-voice radio and call channels are cut.
- xr-jammer does not stop text messages, phone apps or dispatch commands by itself. A phone integration that closes or locks the phone keeps its apps out of reach. Any other script can ask
exports['xr-jammer']:IsJammed(source)on the server (Developer API). - Several fields can overlap. A player is jammed while at least one field reaches him.
- When he leaves the field, phone locks are released. Nothing is redialled or re-joined: he tunes in or calls again himself. On ac_radio and mm_radio, a player whose channel another script assigned also tunes in once through the radio himself before his radio key works again (see Known limits).
- By default xr-jammer tells nobody about a field (some phone and radio scripts give their own feedback, see Known limits).
Config.NotifyJammed = trueshows "No signal." once when a player becomes jammed. The owner's LCD shows YOUR SIGNAL IS BLOCKED when he is jammed himself. - The field shuts down, also while the device is put away, when the owner gets into a vehicle, dies, loses the item, loses access (Access and immunity), switches character or changes routing bucket. The owner gets "Jammer shut down: vehicle, death, lost device, lost access or character change."
- After STOP, the same device can start again after
Config.RestartCooldownMs(1 s). - A device that is switched off and put away forgets its range and exemptions after
Config.IdleTtlMs(5 minutes). - At most
Config.MaxActive(32) fields can be active on the server at the same time. - An active device beeps every
Config.Beep.intervalMs(5 s). Players closer thanConfig.Beep.distance(10 m), the owner included, hear it, quieter the farther they are.
Exemptions
- "Exempt Myself" exempts the owner from his own field. It does not count toward the limit.
- "Exempt an ID" exempts another player by server ID. The player must be online, and your own ID is refused (use Exempt Myself). Up to
Config.MaxExemptions(16) players per device. - An exemption belongs to the character session of the exempt player. It ends when either player logs out, switches character or leaves the server.
- An exemption only concerns that one device. Other fields still jam the player.
Access and immunity
The server decides who may use the device. These checks run when the device is taken out, on every LCD request except STOP and PUT AWAY (they always work), and on every server tick while the device is held or active:
- A character is loaded. Standalone: the player has a license identifier.
- The player is alive and not in a vehicle (server natives).
- The framework does not report him as dead, in last stand or handcuffed (Integrations lists the fields per framework).
- His job matches
Config.AllowedJobs, unless it isnil. Editable.CanUse(src)inserver/editable.luadoes not returnfalse.- He owns the item (command mode: he has the ACE permission).
A device that is out or active shuts down within one tick when a check fails.
Config.AllowedJobs
Config.AllowedJobs = nil -- everyone who has the item (default)
Config.AllowedJobs = { police = 0, sheriff = 2 } -- police: every grade, sheriff: grade 2 and higher
Config.AllowedJobs = { ['state-police'] = true } -- every grade (names with a dash need brackets)
Use the framework's job name (not its label), with exact case.
A number is the minimum grade:
job.grade.levelon qb-core / qbx_core,job.gradeon ESX.trueis every grade. Boss status plays no role.police = falsehas the same effect as leaving the job out.An empty table
{}lets nobody use the device.Entries in another form, such as the list
{ 'police' }or a grade written as text, match nobody. The server console names each one at start.Standalone servers have no jobs: leave it
niland useEditable.CanUse.Duty is not checked. To require it, use
Editable.CanUse(example inserver/editable.lua):function Editable.CanUse(src) local job = Bridge.GetJob(src) if not job or job.onduty == false then return false end return true end
Config.ImmuneJobs
Jobs that are never jammed: their radio and phone keep working inside every field. Their own jammer still affects everybody else.
Config.ImmuneJobs = { police = 3, ambulance = true } -- police from grade 3, ambulance every grade
- Same format as
Config.AllowedJobs: a number is the minimum grade,trueis every grade, and boss status plays no role. The default{}makes nobody immune. - The job is read on every server tick, so a job or grade change takes effect within
Config.TickMs. - More rules (an ACE, an item, duty):
Editable.IsImmune(src)inserver/editable.lua, e.g.if IsPlayerAceAllowed(src, 'jammer.immune') then return true end.
Never decide access or immunity from a replicated state bag such as Player(src).state.x: a player's client can set it. Use a server export that answers at once, or a server-side table.
Integrations
Two levels of evidence apply:
- Supported: checked against the source code of that script.
- Built in, based on the vendor's documentation: the script's source is not public. The integration uses only the calls its vendor documents.
Frameworks
Config.Framework = 'auto' uses the first framework it finds running, in this order: qbx_core, qb-core, es_extended. If none is running, it runs standalone. Start xr-jammer after your framework.
| Framework | Status | Character identity | Jobs | Blocks the device while |
|---|---|---|---|---|
| qbx_core | Supported | citizenid |
job name + job.grade.level |
metadata isdead, inlaststand or ishandcuffed, or the state isDead |
| qb-core 1.2.x and 1.3.x | Supported | citizenid |
job name + job.grade.level |
metadata isdead, inlaststand or ishandcuffed |
| ESX Legacy (es_extended 1.9.4 or newer) | Supported | identifier |
job.name + job.grade |
the state isDead or isHandcuffed (set by esx_ambulancejob and recent esx_policejob versions; without them these checks are skipped) |
| Standalone | Supported | license identifier | none | never |
ox_core, ND_Core and other frameworks run as standalone: the identity is the license identifier, and there are no jobs.
Voice
| Script | Status | What happens |
|---|---|---|
| pma-voice v6.6.2 or newer | Supported, required | The server drops a jammed player from the radio channel and the call channel and drops him again on every tick he is found back on one. On builds with addRadioDisableBit (v7.0.2-rc3, main from October 2023 on) his radio key is also locked on his client, except on servers with sv_stateBagStrictMode. |
Inventories
| Inventory | Config.Inventory |
Status | Item definition |
|---|---|---|---|
| ox_inventory 2.38.0 or newer; on qb-core 2.38.0 to 2.41.0 | 'ox' |
Supported, with transfer lock | install/ox_inventory.lua |
| qb-inventory 2.x, ps-inventory (QBCore only) | 'framework' |
Supported (item API of qb-core) | install/qb-core.lua |
| qb-inventory 1.x, lj-inventory (QBCore only) | 'framework' |
Legacy: works where the inventory itself runs usable items (official qb-core from before May 2024) | install/qb-core.lua |
| ESX default inventory | 'framework' |
Supported (item API of es_extended) | install/esx.sql |
| qs-inventory | 'qs' |
Built in, based on the vendor's documentation | Other inventories |
| tgiann-inventory | 'tgiann' |
Built in, based on the vendor's documentation | Other inventories |
| origen_inventory on ESX (with the vendor's ESX override files) | 'origen' |
Built in, based on the vendor's documentation | Other inventories |
| No inventory | 'none' |
Chat command + ACE permission | Command mode |
ox_inventory 2.42.0 and newer have no qb-core bridge and do not run on qb-core: on qb-core, use 2.38.0 to 2.41.0. On Qbox and ESX, 2.38.0 or newer. qb-inventory 1.x and lj-inventory: see qb-inventory, ps-inventory, lj-inventory.
Qbox with any inventory other than ox_inventory: see Qbox without ox_inventory.
Phones
Every phone on this list is closed when the player becomes jammed. Phones with a lock are locked as well, and unlocked when he is no longer jammed. Nothing is reopened.
| Script | When jammed | Call ended on the server | Status |
|---|---|---|---|
| lb-phone | Closes the phone and refuses to open it (open check; builds without it: the disabled flag) | yes (EndCall) |
Built in, based on the vendor's documentation |
| npwd | Ends the call, closes and disables the phone; ends a call answered later | no | Supported |
| gksphone (v2) | Ends the call, closes the phone, blocks opening with the reason "No signal." | no | Built in, based on the vendor's documentation |
| qs-smartphone (Smartphone V3) | Close only: closes the phone again whenever it is opened | yes (endCallBySource) |
Built in, based on the vendor's documentation |
| yseries | Ends the call, closes and disables the phone | no | Built in, based on the vendor's documentation |
| yboomer-phone | Closes and disables the phone | no | Built in, based on the vendor's documentation |
| roadphone (RoadPhone and RoadPhone Pro) | Ends the call, closes and blocks the phone; ends a call that comes up later | no | Built in, based on the vendor's documentation |
| okokPhone | Ends the call, closes and disables the phone; ends a call answered later | no | Built in, based on the vendor's documentation |
| high-phone | Ends the call, closes the phone, switches its usability off | no | Built in, based on the vendor's documentation |
| 17mov_Phone | Closes the phone and blocks it | yes (PhoneApp_EndCallBySrc) |
Built in, based on the vendor's documentation |
| jpr-phonesystem | Close only: closes the phone again whenever it is opened | no | Built in, based on the vendor's documentation |
- The voice of a call is cut through the pma-voice call channel for every phone whose calls run over it, with or without an integration.
- lb-phone: its call audio runs over the pma-voice call channel, which its open files show. Every lb-phone export the integration calls rests on the vendor's documentation.
- qs-smartphone:
endCallBySourceis called only for a player its documentedisPlayerInCallreports in a call (a build without that getter gets the plain call). Not verified: thatisPlayerInCallreads the phone's own call. If it read pma-voice's call channel, which the jammer clears just before, the call would be silenced but not ended. - A phone lock that another script holds (handcuffs, death) is left alone. When that script releases it while the player is still jammed, the integration takes the lock. high-phone and 17mov_Phone cannot report a lock: their lock is taken without asking, and releasing it also lifts a block another script set before or during the jam.
Radios
| Script | When jammed | While jammed | Status |
|---|---|---|---|
| qb-radio | Makes qb-radio leave through its own code | Leaves again while qb-radio still reports "on" | Supported |
| qbx_radio | Makes qbx_radio leave through its own code | Repeats it; this does nothing until the player tuned in again | Supported |
| ac_radio | Leaves the channel and closes the radio screen, only for a player who is on one or just left one | Leaves again after a re-join | Supported |
| mm_radio | Leaves the channel, only for a player who is on one or just left one | Leaves again after a re-join | Supported |
| rp-radio | Leaves the channel, closes the radio and keeps it from opening. When the player is no longer jammed, the radio can be opened again; it stays switched off. | - | Supported |
| tgiann-radio-v2 | Leaves all channels (client) | Leaves again while it reports a channel | Built in, based on the vendor's documentation |
| tgiann-radio | Leaves the radio channel (client event tgiann-radio:tk) |
- | Built in, based on the vendor's documentation |
| zerio-radio | Sets the frequency to 0 (disconnect) | - | Built in, based on the vendor's documentation |
| oxide-radio | Leaves the channel on the server (LeaveChannel) |
Again whenever the player is found on a pma-voice radio channel | Built in, based on the vendor's documentation |
- The radio channel is always dropped on the server through pma-voice, on the server tick that finds the player inside the field. The integrations bring the radio script's own state and screen in line with that about half a second later, so its screen can show the dropped channel for that long; only oxide-radio, which leaves on the server, acts at once. In that half second xr-jammer first releases a radio key the player pressed just before the field reached him (see Known limits).
- rp-radio cannot report its flag: releasing it also enables a radio another script disabled during the jam.
- ac_radio and mm_radio switch pma-voice's radio off when they leave a channel. A player whose channel another script assigned tunes in once through the radio after a field: see Known limits.
Not built in
These scripts have no built-in integration. What still works for them: the pma-voice radio and call channels they use are cut and kept cut by the server, and Config.NotifyJammed, the hooks and the exports of the Developer API are available. Their own screen is not closed.
- Voice: SaltyChat, TokoVOIP, mumble-voip, YACA. START stays locked.
- Inventories: core_inventory and codem-inventory (no documented usable-item API,
'custom'mode); the item use of origen_inventory on QBCore / Qbox (not documented by the vendor; the framework registration is attempted and a console notice points toUseDevice); ox_inventory 2.42.0 or newer on qb-core (these releases have no qb-core bridge and do not run on qb-core; use 2.38.0 to 2.41.0 there); any other inventory. See Other inventories. - Phones: qb-phone (no API), cylex_phone (only
IsOpenis documented), qs-smartphone-pro and the older Quasar phones (qs-smartphone before V3, Smartphone Lite), yflip-phone and yphone as stand-alone names, and every phone not listed above. - Radios: esx-radio and every radio not listed above. tgiann-radio-v2 is left on the client, but it is not documented whether its channels are pma-voice channels, so the server does not keep a jammed player out of them. For a hard lock, add a check to its open (editable) files:
if LocalPlayer.state.signalJammed then return false endat the top ofcanJoinChannel()in its client editable file. On the server, itscanOpen(src, data)decides whether the radio opens: a check ofexports['xr-jammer']:IsJammed(src)there is one a client cannot bypass. - Frameworks: ox_core, ND_Core and others run as standalone.
For a script of this list, see Adding a phone or radio integration and the hooks of the Developer API.
Configuration reference
config.lua is shared and sent to every client: never put secrets in it. All distances are meters and all times milliseconds.
A value of the wrong type or outside its limits is replaced by its default, and the server console names it at start (see Console messages). Two fallbacks are stricter than the default on purpose: an invalid Config.AllowedJobs lets nobody use the device, and an invalid Config.Brand leaves the brand line empty. Config.Debug is read as it is and never reported. If config.lua has a syntax error, the jammer stays switched off until it is fixed.
General
| Option | Default | Description |
|---|---|---|
Config.Debug |
false |
true: the calibration commands for the hand position (see Hand calibration) and extra console lines: failed framework calls in the server console; LCD actions, LCD page start-up and replaced config values in F8. Anything but true counts as false. |
Config.Locale |
'en' |
Language of the LCD, notifications, key binding names and chat help. See Languages. |
Framework and inventory
| Option | Default | Description |
|---|---|---|
Config.Framework |
'auto' |
'auto', 'qbx', 'qb', 'esx' or 'standalone'. Case does not matter; 'qbox', 'qbx_core', 'qbcore', 'qb-core' and 'es_extended' work too. An unknown value falls back to auto detection, with a console warning. |
Config.Inventory |
'auto' |
'auto', 'ox', 'qs', 'tgiann', 'origen', 'framework', 'custom' or 'none'. Case and spaces do not matter; the resource names ('ox_inventory', 'qs-inventory', 'tgiann-inventory', 'origen_inventory') work too. An unknown value falls back to auto detection, with a console warning. |
Config.Item |
'jammer' |
Item name, lower case. Using the item takes the device out or puts it away; it is never consumed. The device shuts down as soon as the item leaves its owner's inventory. |
Config.Command |
{ enabled = true, name = 'jammer', ace = 'jammer.use' } |
Only used in 'none' mode. See Command mode. |
Automatic inventory detection ('auto'), first running resource wins:
| Order | Resource | Mode |
|---|---|---|
| 1 | origen_inventory |
'origen' |
| 2 | core_inventory |
'custom', with a console notice at every start |
| 3 | codem-inventory |
'custom', with a console notice at every start |
| 4 | tgiann-inventory |
'tgiann' |
| 5 | qs-inventory |
'qs' |
| 6 | ox_inventory |
'ox' |
| 7 | a framework is running | 'framework' |
| 8 | otherwise | 'none' |
- ox_inventory comes last because other inventories may answer for the name
ox_inventory. A forced'ox'falls back to auto detection, with a console warning, while one of the other inventories above is running. - An inventory or framework that is installed but not started yet keeps the detection open: once it starts, it is selected.
Access
| Option | Default | Description |
|---|---|---|
Config.AllowedJobs |
nil |
nil = everyone who has the item. A table = only these jobs: job name = minimum grade, true = every grade. {} lets nobody use the device. See Access and immunity. |
Config.ImmuneJobs |
{} |
Jobs that are never jammed: job name = minimum grade, true = every grade. Read on every server tick. |
Voice, phone and radio
| Option | Default | Description |
|---|---|---|
Config.Voice |
{ resource = 'pma-voice' } |
Resource name of pma-voice. Change it only if your pma-voice folder is renamed. A running resource of that name without pma-voice's server exports keeps START locked (see START stays locked). |
Config.Phone |
'auto' |
Which phone integrations run: 'auto' = every one whose resource is running; false = none (calls are still cut through pma-voice); a list = only these, e.g. { 'lb-phone' }. A renamed folder is mapped to its integration with { ['my-phone-folder'] = 'lb-phone' }. It covers the client integrations and the server-side call ends. |
Config.Radio |
'auto' |
The same for the radio integrations. The radio channel itself is always dropped on the server through pma-voice. |
Config.NotifyJammed |
false |
true: a player who becomes jammed gets "No signal." once (text msg_no_signal). false: xr-jammer shows nothing, so the jammer stays hidden from the players it affects (some phone and radio scripts give their own feedback, see Known limits). |
- Any other value of
Config.Phone/Config.Radio(for example'lb-phone'without the braces) counts as'auto', and so does a missing line. The server console names a wrong value at start, and each list entry that is not a script name. - A script name that has no integration is skipped without a console line.
- A list can combine both forms:
Config.Radio = { 'qb-radio', ['my-radio'] = 'mm_radio' }.
Field
| Option | Default | Description |
|---|---|---|
Config.Range |
{ min = 10, max = 50, default = 10 } |
Field radius the owner can select on the LCD, in whole meters, with 1 <= min <= default <= max. default is the radius of a device that is taken out for the first time. |
Config.MaxActive |
32 |
Fields that can be active on the whole server at the same time (whole number, at least 1). |
Config.MaxExemptions |
16 |
Players one owner can exempt by server ID (whole number, at least 0). "Exempt Myself" does not count. |
Config.RestartCooldownMs |
1000 |
Waiting time after STOP before the same device can START again. |
Config.IdleTtlMs |
300000 |
A device that is switched off and put away forgets its range and exemptions after this time. |
Config.Beep |
{ intervalMs = 5000, distance = 10.0, gain = 0.08 } |
Warning beep of an active device: interval (at least 250), hearing distance (0 turns the beep off) and volume next to the device (0.0 to 1.0). |
Device and LCD
| Option | Default | Description |
|---|---|---|
Config.Brand |
'XR · FIELD SYSTEMS' |
Small line above the title on the LCD. Not translated. |
Config.Keys.cursor |
{ mapper = 'MOUSE_BUTTON', key = 'MOUSE_RIGHT', label = nil } |
Default key for the inspect camera and LCD cursor (held). |
Config.Keys.lower |
{ mapper = 'keyboard', key = 'BACK', label = nil } |
Default key that puts the device away. |
Config.Visual.distance |
35.0 |
Other players see a held device and its live LCD up to this distance. |
Config.Visual.maxDui |
4 |
Live LCDs one client renders at the same time: its own device first, then the nearest. Each one is a small browser instance; devices beyond this number show a blank screen. |
Config.Visual.loadTimeoutMs |
10000 |
How long the model and the LCD page may take to load (at least 1000). A model that does not load in time puts the device away; an LCD page that does not load is tried again while the device stays in the hand. |
Config.Visual.animation |
{ dict = 'cellphone@', clip = 'cellphone_text_read_base', flags = 49 } |
Animation of the player who holds the device (upper body, the player can still walk). dict and clip are texts, flags is a whole number (32-bit, the flags of TaskPlayAnim). |
Config.Visual.cursorSensitivity |
0.45 |
Mouse speed of the LCD cursor. |
Config.Visual.inspect |
{ enabled = true, distance = 0.25, fov = 30.0, lift = 0.0, easeMs = 220 } |
Inspect camera while the cursor key is held: distance from the screen, field of view (degrees), height offset and blend time (0 to 60000 ms). enabled = false keeps the normal camera; the cursor still works. |
Config.Device |
bone = 57005, offset = { x = 0.10, y = 0.020, z = -0.040 }, rotation = { x = 90.0, y = 180.0, z = -90.0 }, rotationOrder = 2 |
Where the device sits in the right hand (57005 = SKEL_R_Hand), calibrated for the default peds. See Hand calibration. |
Config.Models |
8 entries | One model per device that can be held at the same time. The names are baked into the files in stream/: do not rename, remove or reorder the entries. |
- Keys: every player can change them in Settings > Key Bindings > FiveM. FiveM stores a default the first time a player sees it, so changing a default here only affects players who join for the first time.
mapperandkeyare the input mapper and key name ofRegisterKeyMapping(mapper IDs). - Key labels:
labelis the text of the key in the LCD hint line, the same in every language.niluses the locale textsui_key_cursor/ui_key_lower, which name the shipped keys (RIGHT MOUSE, BACKSPACE). Set a label whenever you change mapper or key, e.g.{ mapper = 'keyboard', key = 'E', label = 'E' }. A key that a player rebinds in his own settings is not shown on the LCD. - The cursor key is held, and right mouse is the default because aiming is disabled while the device is in the hand and ALT is usually the radio key.
Advanced
| Option | Default | Description |
|---|---|---|
Config.TickMs |
250 |
Server tick (at least 50): who is inside which field, item, vehicle and death checks of the owners, voice enforcement. A jammed player who joins a channel again is removed within one tick. |
Config.SceneMs |
500 |
How often the list of held devices is compared and sent to nearby clients, only when it changed (at least 50). |
Config.Rate |
{ capacity = 8, refillPerSecond = 4 } |
Per-player limit for LCD requests and item use: a burst of capacity requests, refilled at refillPerSecond. Requests beyond it are dropped without a message. |
Hand calibration
The device is positioned for the default peds. To adjust it for your peds:
Set
Config.Debug = trueand restart the resource. The calibration commands exist only then.Take the device out and adjust it:
Command Effect /jammer_pos x y zPosition relative to the hand bone (meters, each within 1). /jammer_offset x y z rx ry rzPosition and rotation at once (rotation in degrees, each within 360). /jammer_nudge <x|y|z|rx|ry|rz> <delta>Adds to one value. /jammer_bone <bone id>Another bone, e.g. 57005 SKEL_R_Hand, 28422PH_R_Hand./jammer_next [step],/jammer_prev,/jammer_orient <n>Steps through the 64 rotations built from multiples of 90 degrees, to orient a new model or bone. /jammer_gridTest pattern on your LCD, to check the cursor mapping. /jammer_respawnBuilds the prop again. /jammer_calibPrints the current values. /jammer_calibprints a line in F8 that you can copy intoConfig.Device(bone, offset, rotation, rotationOrder). The notification shows the six numbers.Set
Config.Debug = falseagain and restart the resource.
The commands change the device only on your own client until the resource restarts.
Languages
| Code | Language | Code | Language |
|---|---|---|---|
en |
English | nl |
Dutch |
de |
German | it |
Italian |
fr |
French | tr |
Turkish |
es |
Spanish | cs |
Czech |
pt-br |
Portuguese (Brazil) | da |
Danish |
pl |
Polish | sv |
Swedish |
hu |
Hungarian | ro |
Romanian |
Switching languages: set Config.Locale = 'de' and restart the resource.
- Case, spaces and
_do not matter:'pt_BR'and'PT-BR'both selectpt-br. - A code with no file falls back to English, and the server console warns about it.
- A key missing from a translation shows its English text.
What is translated: the LCD texts, notifications, key binding names and chat help texts.
What stays as is:
Config.Brandand the key labels ofConfig.Keys(you write them yourself)- the item label, which belongs to your item definition (
Signal Jammerin the files ofinstall/) - console messages (English)
Changing a text: edit the file of your language in locales/, then restart the resource. The LCD title is ui_title, the title of ox_lib notifications is notify_title.
Adding a language:
- Copy
locales/en.luatolocales/<code>.lua, e.g.locales/fi.lua. Every.luafile inlocales/loads on its own, so there is nothing to add to the manifest. - Change
Locales['en']toLocales['<code>'], using the same code as the file name. - Translate the texts on the right. Never change the keys on the left.
- Set
Config.Locale = '<code>'.
Rules when translating (also written at the top of locales/en.lua):
- Texts are Lua strings. Write an apostrophe inside
'...'as\'. A syntax error disables the whole language file. - Keep every
%s, because a value replaces it. A text with two%sgets them in the order of the English text. msg_bad_rangegoes throughstring.format: write a literal percent sign there as%%. A translation that breaks this falls back to English, and the console names the key.ui_*texts are drawn on the small LCD in fixed-size boxes. Keep them about as long as the English ones and write them in the letter case you want to see: the LCD does not change the case. A text that is too long is cut off at the edge of its box.key_*texts are the names in Settings > Key Bindings > FiveM. FiveM stores them the first time a player sees them, so a later change only shows up for new players.cmd_*texts are the help lines the chat shows next to a command.
Developer API
Server exports
-- true while the player is jammed, and the sorted list of the server ids whose fields reach him.
-- The authoritative answer: positions, items and readiness are checked at the moment of the call.
local jammed, owners = exports['xr-jammer']:IsJammed(source)
-- The same as using the item: takes the device out, or puts it away when it is held.
local ok, code = exports['xr-jammer']:UseDevice(source)
-- Ends the player's session as a logout does: his device, its field and the exemptions bound to him are removed.
local done = exports['xr-jammer']:ResetPlayerSession(source)
-- true while START is unlocked (framework, inventory and voice integration ready)
local ready = exports['xr-jammer']:AreIntegrationsReady()
| Export | Returns |
|---|---|
IsJammed(src) |
Always two values: true / false and a list of owner ids. false, {} for an invalid id and while the system is not ready. |
UseDevice(src) |
true when the device was taken out or put away. Otherwise false and a code: 'invalid' (not a connected player id), 'rate' (rate limit), 'unavailable' (access checks), 'no_item' (ownership check), 'no_slots' (8 devices held). |
ResetPlayerSession(src) |
true for a whole number from 1 to 65535 (also for a player without a device), false otherwise. |
AreIntegrationsReady() |
true / false: the ready rule of the LCD's START button. |
UseDeviceis for inventories without a built-in integration (Other inventories). Server exports can only be called by server scripts, never by a client.- The frameworks report a logout or character switch through
QBCore:Server:OnPlayerUnload(qb-core, qbx_core),qbx_core:server:playerLoggedOut(qbx_core) andesx:playerLogout/esx:playerDropped(ESX), and xr-jammer resets the session then. CallResetPlayerSessionfrom a script that switches characters without these events.
A server-side check that is safe while xr-jammer is stopped:
local function isJammed(src)
if GetResourceState('xr-jammer') ~= 'started' then return false end
local ok, jammed = pcall(function()
return exports['xr-jammer']:IsJammed(src)
end)
return ok and jammed == true
end
-- e.g. in your phone's server code, before a call is connected
if isJammed(source) then return end
Client
-- true while this player is jammed (a hint for a HUD or a menu)
local jammed = exports['xr-jammer']:IsJammed()
-- local client event (TriggerEvent, not a net event), fired on every change
AddEventHandler('xr-jammer:jammed', function(blocked)
-- blocked = true: now jammed, false: no longer jammed
end)
State bag
Player(source).state.signalJammed is true while the player is jammed and nil otherwise. It is replicated (server and every client; LocalPlayer.state.signalJammed on the player's own client) and written only by the xr-jammer server, which puts it back on every tick while the player stays jammed. On a server without sv_stateBagStrictMode, a client can overwrite its own state, so the state bag and the client values are hints. Whatever must not be bypassed (starting a call, sending a dispatch message) asks the server export IsJammed.
Server hooks (server/editable.lua)
| Function | Default | Use |
|---|---|---|
Editable.CanUse(src) |
true |
Extra access rule after the built-in checks. false = denied: the device cannot be taken out, and a device that is out or active shuts down within one tick. On error: allowed. |
Editable.IsImmune(src) |
false |
true = never jammed, in addition to Config.ImmuneJobs. On error: not immune. |
Editable.HasItem(src) |
nil |
true / false decides ownership; nil uses the built-in check of the inventory. In 'custom' mode there is no built-in check: nil means "does not own it". On error: the built-in check. |
Editable.OnJammedChanged(src, blocked, owners) |
nothing | Called when a player becomes jammed (owners = sorted server ids whose fields reach him) and when he is no longer jammed (false, {}), also on disconnect and when xr-jammer stops. For server-side calls into phone or radio scripts without an integration. |
CanUse,IsImmuneandHasItemrun on the server tick, up to four times a second per player, and their answer is needed at once: noWait, no database query, no callback or export that is awaited. Cache anything expensive. xr-jammer looks for a waiting tick once a second and whenever a player action or a client sync reaches the server, and reports the first one it finds once in the console (see The tick guard line).OnJammedChangedruns in a thread of its own and may wait. A later change does not interrupt a call that is still waiting: after a wait, askexports['xr-jammer']:IsJammed(src)before acting on the old value ofblocked.- Every hook runs in protected mode. An error is printed once in the server console, and the default applies.
- Helpers:
Bridge.name('qbx','qb','esx'or'standalone'),Bridge.GetJob(src)({ name, grade, onduty, isboss }ornil;ondutyisnilon frameworks without a duty state) andBridge.Identity(src)(citizenid, ESX identifier or license of the loaded character;nilwhile none is loaded). - If
server/editable.luadoes not load (syntax error), START stays locked until it is fixed: the rules written there would be unknown.
Client hooks (client/editable.lua)
| Function | Default | Use |
|---|---|---|
Editable.Notify(message, kind) |
The first running notification system: qbx_core, qb-core, ESX (with esx_notify), ox_lib; otherwise the GTA feed | Route the notifications of xr-jammer to your own notify resource. kind: 'error', 'success' or 'info'. |
Editable.OnJammedChanged(blocked) |
nothing | Called when this player becomes jammed and when he is no longer jammed, also when xr-jammer stops while he is jammed. For a phone, radio or HUD without an integration. Undo on false only what you did on true; never reopen a phone or re-join a channel. |
Both run in protected mode and in their own thread. If client/editable.lua does not load, notifications use the GTA feed and the hook is skipped.
Adding a phone or radio integration
The integrations live in open files. A phone or radio script can have a client entry, a server entry, or both.
| File | Table | Side |
|---|---|---|
integrations/client/phones.lua |
PhoneAdapters |
client: end the call, close and lock the phone |
integrations/client/radios.lua |
RadioAdapters |
client: leave the channel, switch the radio off |
integrations/server/voice.lua |
Voice.Phones, Voice.Radios |
server: for scripts that can only end a call or leave a channel on the server |
A client entry (all three callbacks are optional):
PhoneAdapters['my-phone'] = {
onBlocked = function(resource) end, -- became jammed, or the phone (re)started while jammed
onUnblocked = function(resource) end, -- no longer jammed, or xr-jammer stops while jammed
whileBlocked = function(resource) end, -- once a second while jammed
}
A server entry:
Voice.Phones['my-phone'] = function(resource, src)
exports[resource]:EndCall(src) -- example: use the call your phone documents
end
Rules:
resourceis the running resource name: the key, or the folder mapped to it inConfig.Phone/Config.Radio. Call the script throughexports[resource].- Each client entry runs in a thread of its own, and its callbacks never overlap:
onUnblockedstarts only afteronBlockedhas returned. A callback may call exports that wait, but must not useWaitor loop. A callback that does not return stops only its own entry, and F8 reports it after 10 s. - Radio entries on the client get
onBlockedabout half a second after the player became jammed (phone entries at once), so that the push-to-talk lock can first release a radio key he pressed just before. When the field ends within that half second, the radio entries still getonBlocked(unless the player is on a channel again) andonUnblockedone tick later.whileBlockedruns only afteronBlocked. onBlockedin this order: end the call, close the phone, lock it. The lock comes last.- Release only a lock the entry took itself: remember it, forget it before calling the releasing export, and start every
onBlockedfrom scratch (after a restart of the phone the old lock is gone andonUnblockedwas not called). whileBlockedreacts only to what the script itself reports (open again, in a call again, "on" again). It must be cheap and must not send a server event every second. For radios that cannot report their channel,integrations/client/radios.luahas the helperswatchRejoin(resource),takeRejoin(resource)andwasTuned(resource); the comments there explain them.wasTunedcounts the channel as left, so call it right before the leave call.- Stay unnoticed by a player who does not use the script: with
Config.NotifyJammed = falsexr-jammer itself does not tell a player that he is inside a field. - Never reopen a phone or re-join a channel when the player is no longer jammed.
- Server entries run in their own thread after the pma-voice drop, on every block and again each time the player is found back on a channel of that kind. They must be safe for a player who is not in a call or on a channel. There is no server entry for "no longer jammed".
- An error is printed once and never stops the jammer or the other entries.
Which entries run is set by Config.Phone (phone entries on both sides) and Config.Radio (radio entries on both sides): 'auto' = every entry whose resource is running, a list = only these, false = none. A renamed folder: Config.Phone = { ['my-phone-folder'] = 'lb-phone' } runs the lb-phone entries for the resource my-phone-folder.
Only pma-voice is supported. integrations/server/voice.lua documents what its three functions Voice.Ready, Voice.Drop and Voice.Enforce must do: Voice.Ready may only return true when Voice.Drop really removes the player, otherwise the LCD would show a field that blocks nothing. The shipped Voice.Ready returns true only while the resource in Config.Voice.resource runs and has pma-voice's server exports setPlayerRadio and setPlayerCall.
Internal events
The net events of the resource (xr-jammer:action, xr-jammer:sync, xr-jammer:owner, xr-jammer:scene, xr-jammer:affected, xr-jammer:beep, xr-jammer:reset) are internal. Do not trigger them from other scripts. Use the exports and xr-jammer:jammed.
Open and encrypted files
The resource is delivered through Cfx.re Asset Escrow.
| Open (readable, not encrypted) | Encrypted |
|---|---|
fxmanifest.lua |
shared/locale.lua |
config.lua |
shared/util.lua |
locales/*.lua |
shared/asset.lua |
server/editable.lua |
bridge/server/framework.lua |
client/editable.lua |
bridge/server/inventory.lua |
integrations/server/voice.lua |
server/core.lua |
integrations/client/voice.lua, phones.lua, radios.lua |
server/main.lua |
install/ox_inventory.lua, install/qb-core.lua, install/esx.sql, install/jammer.png |
client/dui.lua |
html/index.html, html/ui.js, html/ui.css, html/audio.html, html/audio.js (the LCD and the beep page; escrow never encrypts NUI files) |
client/props.lua, client/main.lua |
You may edit every open file except fxmanifest.lua. The escrow_ignore list in fxmanifest.lua is config.lua, locales/*.lua, server/editable.lua, client/editable.lua, integrations/server/*.lua, integrations/client/*.lua and install/*.lua. Leave fxmanifest.lua unchanged, and do not rename the files in stream/.
Known limits
- No hard refusal of a re-join. Nothing in pma-voice is patched, so a jammed player can still join a radio channel or answer a call. The server removes him again within one tick (
Config.TickMs, 250 ms). On pma-voice builds withaddRadioDisableBit(v7.0.2-rc3,main) his radio key does nothing in that moment, except on servers withsv_stateBagStrictMode. On older builds, and withsv_stateBagStrictMode, he can transmit for that fraction of a second. Sometimes the transmission does not end with it: his push-to-talk stays pressed after he lets go of the key, until he tunes in again and then presses and lets go of the radio key. On v6.6.2 and v7.0.0 this happens when he presses the key right after tuning in again inside the field and the radio integration leaves that channel (it checks once a second) before pma-voice has reported the key press: these builds ignore the key release once a radio script has switched their radio off. Withvoice_enableRadioAnimat0, the default of these two builds, this is rare: at most a press in one frame of each second is affected. Withvoice_enableRadioAnim 1they report a press only once their radio animation has loaded, so a key pressed up to about the animation's load time before the once-a-second check can stay pressed. That is not rare, but it mostly concerns the first radio key press after joining the server, because these builds do not unload the animation. On these two builds nothing stays pressed when no radio integration runs (Config.Radio = false). Withsv_stateBagStrictModeon v7.0.2-rc3 andmain, which report the key without waiting for the animation, it happens when the key press comes in the last frame before the server drops the channel. - The push-to-talk lock is optional. It needs pma-voice's
addRadioDisableBit(v7.0.2-rc3, ormainfrom October 2023 on), and it is a client state write, so it does not work withsv_stateBagStrictMode. Without it, the server drop alone keeps the player off the channels. - Phone and radio scripts give their own feedback. qb-radio and qbx_radio leave through their own code, so they show their own "left channel" notification (qbx_radio also plays its squelch sound, qb-radio its click sound where InteractSound is installed). mm_radio plays its radio-off sound. This happens only for players who were on a channel. gksphone is blocked with the reason "No signal." (text
msg_no_signal), which the phone can show to a jammed player who tries to open it. Apart from this reason, xr-jammer shows no message whileConfig.NotifyJammed = false. - Radio key after a field on ac_radio and mm_radio. When these two radios leave a channel, they also switch pma-voice's radio off. Neither pma-voice nor xr-jammer switches it on again; tuning in through the radio does. A player whose channel was assigned by another script (dispatch, job, MDT) must tune in once through ac_radio or mm_radio after a field before his radio key works again, also when that script puts him on a channel again. qb-radio and qbx_radio leave only when the player tuned in through them, so a channel that only another script assigned is not affected.
- Radio key pressed just before a field, with pma-voice's radio animation. The radio integrations leave about half a second after a field reaches the player (see Radios), so that xr-jammer can first release a radio key he pressed just before: once a radio script has switched pma-voice's radio off, pma-voice v6.6.2 and v7.0.0 ignore the key release. When
voice_enableRadioAnimis set to1(it is0by default on these two builds), they report a pressed key only after their radio animation (random@arrests) has loaded. If it has not loaded when the radio integration leaves (for example while the game still streams it) and the player still holds the key, the release comes too late: pma-voice keeps his push-to-talk pressed and keeps sending his voice to the players who were on his channel, also after he lets go, until he tunes in again and then presses and lets go of the radio key. pma-voice does not unload the animation, so it is mostly the first radio key press after joining the server that can take that long. Forks of these builds can behave the same. v7.0.2-rc3 andmainreport the key without waiting for the animation and are not affected, nor is a server that runs no radio integration (Config.Radio = false). - Phone calls. The voice of a call is cut through pma-voice for phones whose calls run over its call channel. Video calls that run over WebRTC end only where the phone's call is ended (the phone integrations and the server-side call ends). A phone without an integration keeps showing its call screen until a player hangs up.
- Locks of other scripts. A lock taken by xr-jammer that another script lifts during the jam is not taken again; the voice stays cut by the server. high-phone, 17mov_Phone and rp-radio cannot report their lock: releasing it also lifts a block another script set before or during the jam.
- State bags are hints.
signalJammedand the client export can be faked by a modified client on a server withoutsv_stateBagStrictMode. The server exportIsJammedis the authority. - Immunity: a number in
Config.ImmuneJobs/Config.AllowedJobsis the minimum grade,trueis every grade. Boss status plays no role. - 8 held devices. One model per held device: 8 can be held at once. Devices that are put away keep their fields and use no model.
- Live LCDs per client. Each client renders at most
Config.Visual.maxDui(4) live screens. Further devices show a blank screen. - Texture size. The two texture dictionaries use 24 MiB (
xrp_jammer.ytd) and 32 MiB (xrp_jammer_instances.ytd) of graphics memory. FXServer reports every streamed asset above 16 MiB, so the start of xr-jammer shows two warnings (see FXServer warns about the texture size). A client loads the textures only while a device is near. - The field is a sphere. Height counts, walls and interiors do not block it.
- Transfer lock only with ox_inventory, and best effort there: removals through exports or admin commands are not stopped. With any inventory, a device whose item is gone shuts down within one tick.
- Character switches without framework events. A switch that fires no unload or load event of the framework is seen within 5 s. Call
ResetPlayerSessionfrom such a script. - Chat help after a chat restart. A restart of the chat resource empties its help texts: the commands are listed without help until xr-jammer restarts or the player rejoins.
Troubleshooting
The resource does not start
No [xr-jammer] status line appears in the server console.
| Cause | Fix |
|---|---|
FXServer prints Could not find dependency /onesync for resource xr-jammer |
OneSync is off. Enable it in the txAdmin FXServer settings, or start FXServer with +set onesync on. |
| An Asset Escrow error for xr-jammer | Your server's license key must belong to the Cfx.re account that received xr-jammer. Generate the key on that account at portal.cfx.re. |
| No line about xr-jammer at all | The folder must be named xr-jammer and sit inside your resources folder, and server.cfg must contain ensure xr-jammer. A renamed folder does not work: the ox_inventory item, the exports and the events use the name xr-jammer. |
If the server console shows Lua errors for xr-jammer files right after the start, scroll up to the first one: it names the file and the line to fix.
FXServer warns about the texture size
At every start FXServer prints:
Asset xr-jammer/xrp_jammer.ytd uses 24.0 MiB of physical memory.
Asset xr-jammer/xrp_jammer_instances.ytd uses 32.0 MiB of physical memory.
Started resource xr-jammer (2 warnings)
These two warnings are expected. FXServer reports every streamed asset above 16 MiB of graphics memory; the device model has high-resolution textures and one screen texture per model that can be held at the same time (see Known limits). The resource works normally. Do not rename, convert or remove the files in stream/: the model names are baked into them.
START stays locked (SYSTEM LINK NOT READY)
The server console explains it. About 2 s after the start, right after the status line, it prints the not ready line when something is missing. From then on it prints one line whenever the state changes:
[xr-jammer] not ready: START stays locked until <list>
[xr-jammer] ready: START is unlocked
Part of <list> |
Fix |
|---|---|
config.lua loads |
config.lua has a syntax error. The Lua error above the line names the file and line. Fix it and restart. |
server/editable.lua loads |
server/editable.lua has a syntax error. Fix it and restart. |
integrations/server/voice.lua loads |
The file has a syntax error or lost Voice.Ready, Voice.Drop or Voice.Enforce. Restore it from the download. |
the framework (<name>) is running |
The framework is not started, the wrong one is selected, or its version is older than the ones in Requirements. When the framework runs but xr-jammer cannot get its object, a separate line names the call that failed (see Console messages). Check the start order, Config.Framework and the framework version. |
the inventory integration (<name>) is ready |
The inventory is not started, the item registration failed, Editable.HasItem is missing from server/editable.lua ('custom'), or Config.Command.enabled is not true ('none'). A separate inventory line in the console says which (see Console messages). |
pma-voice is running |
pma-voice is not started, or its folder name differs from Config.Voice.resource. |
integrations/server/voice.lua can use <name> |
The resource named in Config.Voice.resource runs, but integrations/server/voice.lua does not accept it. With the shipped file, the resource is not pma-voice: it lacks one or both of pma-voice's server exports setPlayerRadio and setPlayerCall (for example SaltyChat, TokoVOIP, YACA, or a name that points to another resource). The line Config.Voice.resource = '<name>' is running but has no pma-voice exports (setPlayerRadio / setPlayerCall): START stays locked (only pma-voice is supported) comes before it, and the status line says voice: <name> (not usable). Set Config.Voice.resource to the folder name of pma-voice. Other voice systems are not supported. |
pma-voice is restarted |
A voice drop failed (see the voice line below). Restart pma-voice. |
While the system is not ready, every active field is switched off and its owner gets "The jammer is not ready on this server. Please tell the server staff."
Using the item does nothing
- ox_inventory: the item needs
consume = 0andexport = 'xr-jammer.useJammer', and the folder must be namedxr-jammer. The item key must equalConfig.Item, in lower case: with an upper-case letter, ox_inventory's count search returns no number and the item is never counted. ox_inventory 2.38.0 or newer is needed: the server console names an older version (see Console messages). On qb-core it must be 2.38.0 to 2.41.0: ox_inventory 2.42.0 and newer have no qb-core bridge and do not run on qb-core. A second use within 0.5 s is ignored. - qb-inventory, ps-inventory, lj-inventory:
useable = trueis required. Check that other usable items work: the inventory and qb-core must be a matching pair. qb-inventory 1.x and lj-inventory run no usable item with an official qb-core from May 2024 on, 1.3.0 included (see qb-inventory, ps-inventory, lj-inventory). - ESX default inventory: the item must be in the
itemstable. A character that was already in the game when xr-jammer started has no Use option for it: log out and back in, or restart the server. - tgiann-inventory: the item must have
useable = trueand must not be aroleplayitem. - origen_inventory on QBCore / Qbox, core_inventory, codem-inventory, others: see Other inventories.
- Command mode: the player needs the ACE of
Config.Command.ace. - A notification appears: see the table below.
Notifications
| Notification | Cause |
|---|---|
| You cannot use the jammer right now. | The player is dead or in a vehicle, the framework reports him dead, in last stand or handcuffed, his job does not match Config.AllowedJobs, Editable.CanUse returned false, or no character is loaded (standalone: no license identifier). |
| You do not have a jammer. | The ownership check failed: no item, an item name that differs from Config.Item, Editable.HasItem returned false, or 'custom' mode without an answer from Editable.HasItem. |
| You are not allowed to use the jammer. | Command mode: the player lacks the ACE permission. |
| Take the jammer out first. | /jammer_stop or an LCD request without a device, e.g. after the device forgot its settings (Config.IdleTtlMs). |
| Take the device in your hand to do this. | An LCD request for a device that is put away. |
| Too many jammers are being held right now. Try again in a moment. | 8 devices are held (one per entry of Config.Models). |
| The jammer is not ready on this server. Please tell the server staff. | START while the system is not ready, or an active field was switched off for that reason. See START stays locked. |
| Too many jammers are active right now. | Config.MaxActive fields are active. |
| Wait a moment before starting the jammer again. | Config.RestartCooldownMs after STOP. |
| The range must be a whole number from 10 to 50 meters. | An invalid range request (the numbers follow Config.Range). |
| Enter the server ID of another player who is online. | The ID is the owner's own, not online, or not a valid ID. |
| The exemption list is full. | Config.MaxExemptions is reached. |
| The device rejected this request. | A malformed request. |
| Jammer shut down: vehicle, death, lost device, lost access or character change. | The server stopped a held or active device: the owner got into a vehicle, died, lost the item, lost access, switched character or changed routing bucket. |
| The jammer model could not be loaded. | The model is missing or did not load within Config.Visual.loadTimeoutMs. Check that the files in stream/ are complete and unchanged, and that Config.Models is as shipped. The device is put away. |
| The jammer screen could not be loaded. Trying again. | The LCD page did not load within Config.Visual.loadTimeoutMs. It is tried again while the device stays in the hand; the player does not have to do anything. Shown once each time the device is taken out, not on every new try. |
| No signal. | Config.NotifyJammed = true: the player became jammed. |
A jammed player can still talk
- Talking to players nearby (proximity voice) is never blocked. Only the pma-voice radio channel and call channel are cut.
- pma-voice is the only supported voice system. Check the status line:
voice: pma-voice (running). - The player is immune (
Config.ImmuneJobs,Editable.IsImmune), exempt on that device, in another routing bucket, or outside the sphere (height counts). - The owner is jammed by his own field unless "Exempt Myself" is on.
- A phone whose calls do not run over the pma-voice call channel is cut only where its integration ends the call. tgiann-radio-v2 channels: see Not built in.
- A player who joins a channel again is on it for up to one tick: see Known limits.
A phone or radio stays open
- The script is not in Integrations, its folder is renamed (map it in
Config.Phone/Config.Radio), orConfig.Phone/Config.Radioisfalseor a list without it. - A radio that shows the channel for about half a second after the field reached the player is expected: the radio integrations wait that long (see Radios).
- An integration error is printed once in F8:
phone adapter for <resource>: onBlocked failed: <error>. - The voice is still cut by the server.
The LCD is blank or does not load
- More devices are near the player than
Config.Visual.maxDui(4): the farthest ones show a blank screen. - The page did not load within
Config.Visual.loadTimeoutMs(10 s): the owner gets "The jammer screen could not be loaded. Trying again." once each time he takes the device out, and the page is tried again while the device stays in the hand. On slow clients, raiseConfig.Visual.loadTimeoutMs. Config.Debug = trueprintsLCD ready: slot <n>in F8 when a page has started.
Standalone players without a license identifier
In standalone mode a player is identified by his license: identifier. A player without one has no identity and cannot use the device ("You cannot use the jammer right now.").
pma-voice client error createCallThread
On pma-voice v7.0.0-era builds, every server-side setPlayerCall prints one pma-voice script error in the player's F8 console that names createCallThread: that build calls a function it does not define. The error is harmless: the call channel is already set to 0 and the player is out of the call. xr-jammer only removes a player from a call when he is in one, so the line appears only then. pma-voice v6.6.2 and the current main do not make that call.
The tick guard line
[xr-jammer] a framework, inventory or Editable call waited inside the server tick; ownership and access checks must answer immediately (see README > Troubleshooting)
An Editable.CanUse, Editable.IsImmune or Editable.HasItem hook, or an inventory ownership export, did not answer at once (a Wait, a database query, an awaited callback). A pma-voice fork or an edited integrations/server/voice.lua whose call waits causes the same line. The jammer keeps working, but its tick was delayed. Make the hook return immediately: cache the value in a table that your own events update. Printed once, the first time xr-jammer finds the tick waiting. It looks once a second, and whenever an item use, an LCD request, a logout, a player leaving the server or the sync that every client sends every 15 s reaches the server. The longer and the more often a call waits, the sooner the line appears. A short wait, or one that happens rarely, can fall between two of these moments and go unreported: a missing line does not prove that every hook answers at once.
[xr-jammer] a framework, inventory, Editable or voice call has kept the server tick waiting for 5 s (did its resource stop during the call?): a new tick took over (see README > Troubleshooting)
A call inside the server tick has not answered for the number of seconds in the line. The limit is 20 times Config.TickMs, at least 5 s. The tick guard line above comes first, within about a second after the call started to wait, unless it was printed before. Usually the resource that had to answer was stopped or restarted during the call, so the answer never comes. That resource can be the framework, the inventory, pma-voice, or a resource that one of your Editable hooks calls. A hook that waits longer than the limit causes the line as well. While the tick waits, the fields are not updated: a player who walks out of a field, or whose field is switched off, stays jammed until the new tick.
xr-jammer then gives up the waiting tick and starts a new one, so the jammer keeps working. The new tick sees a resource that is still stopped: a stopped framework, inventory or pma-voice locks START and switches the fields off (the not ready line names it), and a hook whose call fails is reported once and its default applies (see the Editable lines in Console messages). The fix is the same as for the line above: hooks and exports must answer at once. Printed once. If the calls keep hanging, each further takeover waits twice as long as the one before (at most 5 minutes) and prints no new line.
Console messages
Lines start with [xr-jammer]. Server console, unless marked F8. Lines marked (debug) appear only with Config.Debug = true. FXServer's own Could not find dependency /onesync for resource xr-jammer line is explained in The resource does not start.
| Message | Meaning / fix |
|---|---|
v1.0.0 | framework: X | inventory: Y | voice: pma-voice (running) |
Normal start. The voice part says not running when the resource in Config.Voice.resource is stopped, not usable when it runs but integrations/server/voice.lua does not accept it (with the shipped file: it has no pma-voice exports, see the line below), and integration file not loaded when integrations/server/voice.lua did not load. Printed again with the new names when the framework or the inventory starts later. |
not ready: START stays locked until ... / ready: START is unlocked |
See START stays locked. |
integrations/server/voice.lua Voice.Drop failed (...): fields are switched off and START stays locked until pma-voice is restarted (also Voice.Enforce) |
A pma-voice export raised an error while pma-voice was running. A line such as pma-voice: setPlayerRadio failed: ... or pma-voice: setPlayerCall failed: ... names it. Check the pma-voice version (v6.6.2 or newer) and restart pma-voice. |
Config.Voice.resource = '<name>' is running but has no pma-voice exports (setPlayerRadio / setPlayerCall): START stays locked (only pma-voice is supported) |
Config.Voice.resource names a resource that runs but is not pma-voice. The status line says not usable, and the not ready line lists integrations/server/voice.lua can use <name>. Set the option to the folder name of pma-voice; other voice systems are not supported. Printed once per resource name. |
integrations/server/voice.lua Voice.Ready failed: ... |
Your change to Voice.Ready raised an error. Restore the file. |
integrations/server/voice.lua: the entry "X" failed: ... |
A server-side phone or radio entry raised an error. It does not stop the jammer. |
config.lua did not load (syntax error? see the Lua error above): the jammer stays disabled until it is fixed |
Fix the syntax error named in the Lua error above. |
server/editable.lua did not load (syntax error? see the Lua error above): START stays locked until it is fixed |
Fix the syntax error in server/editable.lua. |
integrations/server/voice.lua did not load or lacks Voice.Ready / Voice.Drop / Voice.Enforce: START stays locked |
Fix the file or restore it from the download. |
Config.X is invalid (need ...): using ... |
A config value was replaced by its default, e.g. Config.Range is invalid (need whole numbers with 1 <= min <= default <= max): using 10 / 50 / 10. |
Config.Device.X is invalid (need ...): using ... |
A hand position value was replaced by its default. |
Config.AllowedJobs[...] = ... is ignored: write job_name = 0 (minimum grade) or job_name = true (also Config.ImmuneJobs) |
Wrong entry format. A list such as { 'police' } matches nobody. |
Config.Phone = ... is invalid (need 'auto', false or a list such as { ... }): using 'auto' (also Config.Radio) |
The value is not 'auto', false or a table, e.g. 'lb-phone' without the braces. Every integration runs. |
Config.Phone[...] = ... is ignored: write { 'script-name' }, or { ['folder-name'] = 'script-name' } for a renamed folder (also Config.Radio) |
A list entry that is not a script name. It selects nothing. |
unknown Config.Framework "x", detecting automatically |
Use 'auto', 'qbx', 'qb', 'esx' or 'standalone'. |
framework "qb" is selected but qb-core is not running |
The selected framework resource is not started. Fix the start order. |
qb-core is running but exports['qb-core']:GetCoreObject() did not return the QBCore object: qb-core 1.2.x or 1.3.x is required (see README > Troubleshooting) (ESX: es_extended is running but exports.es_extended:getSharedObject() did not return the ESX object: ESX Legacy 1.9.4 or newer is required ...) |
The framework runs, but the call returned no framework object. START stays locked. Use a framework version listed in Requirements. |
unknown Config.Inventory "x", detecting automatically |
Use one of the values in Framework and inventory. |
Config.Inventory is "ox" but X is running (it may answer for the name ox_inventory): detecting automatically |
Another inventory is running. Remove the forced 'ox', or stop the other inventory. |
core_inventory has no built-in integration: see README > Other inventories (also codem-inventory) |
See core_inventory and codem-inventory. Config.Inventory = 'custom' ends it. |
origen_inventory does not document how a used item reaches QBCore / Qbox: ... |
See origen_inventory. |
inventory "X" is selected but Y is not running |
The inventory resource is not started. |
ox_inventory X is older than 2.38.0 and has no server event ox_inventory:usedItem: using the item does nothing until ox_inventory is updated (see README > Troubleshooting) |
Update ox_inventory to 2.38.0 or newer, on qb-core to a version from 2.38.0 to 2.41.0 (ox_inventory). START is not locked, but an ox_inventory older than 2.38.0 never reports the item use, so the item does nothing. The version is read from the version field of ox_inventory's fxmanifest.lua. A fork that numbers its versions differently and still sends ox_inventory:usedItem (the item works) can ignore this line. |
could not register the usable item "jammer" in Y |
The framework or qs-inventory refused the registration. Check that the item exists and the start order. |
inventory "X" needs the usable items of qbx_core, qb-core or es_extended: set Config.Inventory to "custom" or "none" |
The inventory mode runs on a standalone server. |
inventory "custom" needs Editable.HasItem in server/editable.lua |
The function Editable.HasItem is missing from server/editable.lua, or the file did not load (see the line about server/editable.lua). Restore the function from the download and fill it (core_inventory and codem-inventory). The shipped function does not cause this line: it answers "does not own it" until you fill it. |
inventory "none" needs Config.Command.enabled = true: there is no other way to take the device out |
Set Config.Command.enabled = true. |
Editable.HasItem failed (server/editable.lua): ... / server/editable.lua Editable.X failed: ... |
Your hook raised an error. Its default applies. Printed once. |
a framework, inventory or Editable call waited inside the server tick; ... |
See The tick guard line. |
a framework, inventory, Editable or voice call has kept the server tick waiting for <n> s (did its resource stop during the call?): a new tick took over ... |
A call inside the tick did not answer for <n> seconds, usually because its resource was stopped or restarted during the call. A new tick took over, and the jammer keeps working. See The tick guard line. |
item use failed: ... / character unload handler failed: ... / the server tick failed: ... |
An internal error. Send the full line to support. |
Config.Locale "xx": locales/xx.lua is missing, did not load (syntax error? see the Lua error above) or does not define Locales['xx'], using English |
Missing file, syntax error in it, or a wrong Locales['xx'] name. See Languages. |
locales/en.lua did not load (syntax error?): missing texts show their keys |
Restore the original locales/en.lua. |
Server or F8: Locales['xx'].key does not fit its values (keep every %s, write a literal percent sign as %%): ... |
A translation broke a %s / %% rule. The English text is shown until you fix it. |
(debug) Bridge.X failed: ... |
A framework call raised an error and counts as "no data". Check Config.Framework and the start order. |
F8: phone adapter for X: onBlocked failed: ... / radio adapter for X: ... |
A client integration raised an error. Printed once; the other integrations keep working. |
F8: phone adapter for X: a callback has not returned for 10 s, its next callbacks wait for it |
A client integration waits inside the script it calls. Only that integration is held up. |
F8: Editable.Notify failed (client/editable.lua), using the game feed: ... |
Your Editable.Notify raised an error. |
F8: Editable.OnJammedChanged failed: ... / Voice.OnBlocked failed: ... |
An open client function raised an error. |
(debug) F8: LCD: <action> <value>, LCD ready: slot <n>, calibration: ... |
LCD buttons, page start-up and hand calibration. |
If client/editable.lua or a file of integrations/client/ does not load, F8 shows the Lua error of FiveM. The jammer keeps working: notifications use the GTA feed, and the server still drops the voice channels.
Other questions
Anticheat. While the device is in the hand, the client disables attack, aim, the weapon wheel and melee, plays an animation on the player's own ped, and uses a script camera for the inspect view (CreateCam, RenderScriptCams). The device props are local objects: nothing is networked. Whitelist xr-jammer for these checks if your anticheat flags them.
Performance.
- The server tick runs every
Config.TickMs(250 ms). Access hooks and job lookups run only for device owners and players inside a field; the character identity of every other player is kept for up to 5 s, and each player's identity is read again at its own moment, so the lookups do not all fall into the same tick. - Per-frame client work runs only while the player holds his own device.
- Each live LCD is a small browser instance on the client.
Config.Visual.maxDuilimits how many one client renders. - The beep page is created on the first beep and closed after a minute without one.
The escrow says I do not own the resource. See The resource does not start.
Updating
- Download the new version from portal.cfx.re (Granted Assets) and read
CHANGELOG.md. - Back up your
config.lua, your changedlocales/*.lua,server/editable.lua,client/editable.lua, the files inintegrations/, and the files inhtml/if you changed the LCD pages, outside the resources folder. - Replace the whole
xr-jammerfolder. Never mix files of two versions: the encrypted files belong together. - Re-apply your changes. Compare them with the new files rather than overwriting them, because new versions can add options, texts or integrations. Texts missing from an older language file fall back to English.
- Restart the server and check the status line.
Support
Contact the seller through the support channel named on the store page where you bought xr-jammer. Include:
- the status line from the server console (version, framework, inventory, voice)
- every
[xr-jammer]andSCRIPT ERRORline from the server console and the F8 console, in full - your framework, inventory and pma-voice versions, and the phone and radio scripts you use
- what you changed in
config.lua,locales/*.lua, the editable files,integrations/andhtml/
Most problems after a change come from an open file. Check Troubleshooting first.
License
See LICENSE.txt. The license covers use on servers of the purchasing Cfx.re account. Redistribution of any part is not allowed, including the open files, the LCD pages and the 3D model.
Changelog
1.0.0 - first release
- Handheld signal jammer item:
- 3D model in the right hand with a live LCD that nearby players see as well
- 8 devices can be held at once; a device that is put away keeps its field running
- LCD controls: START / STOP, range, Exempt Myself, exemptions by server ID (keypad), PUT AWAY
- inspect camera and LCD cursor on the right mouse button, put away with Backspace (both rebindable)
- commands
/jammer_stopand/jammer_lowerwith chat help - warning beep of an active device
- Server-side field:
- sphere of 10 to 50 m around the owner, calculated on the server every 250 ms
- jammed players are removed from their pma-voice radio and call channels and removed again on every tick they rejoin; no patch to pma-voice or any other resource
- optional push-to-talk lock on pma-voice builds with
addRadioDisableBit(not withsv_stateBagStrictMode) - START unlocks only while the resource named in
Config.Voice.resourceruns and has pma-voice's server exports; a resource without them keeps START locked, and the server console says why - shuts down when the owner gets into a vehicle, dies, loses the item or access, switches character or changes routing bucket
- limits for active fields, exemptions, restart cooldown, idle time and request rate
- a server tick that waits for a call that never answers (for example when its resource is stopped during the call) is replaced by a new tick after 20 times
Config.TickMs, at least 5 s; the console says so once - silent by default: xr-jammer itself shows jammed players no message; optional "No signal." notification (
Config.NotifyJammed)
- Frameworks: qbx_core, qb-core 1.2.x and 1.3.x, ESX Legacy (es_extended 1.9.4 or newer) and standalone.
- Inventories (see the README for the evidence each one rests on):
- checked against their source code: ox_inventory 2.38.0 or newer (with transfer lock; on qb-core 2.38.0 to 2.41.0, because ox_inventory 2.42.0 and newer have no qb-core bridge); qb-inventory 2.x and ps-inventory on qb-core, and the ESX default inventory, through the framework's item API
- qb-inventory 1.x and lj-inventory as legacy options: the jammer works where the inventory itself runs usable items (official qb-core from before May 2024)
- built in, based on the vendor's documentation: qs-inventory, tgiann-inventory, and origen_inventory on ESX with the vendor's ESX override files
- a
'custom'mode for other inventories, and a chat command with an ACE permission when there is no inventory
- Phone and radio integrations that end calls, close phones and leave radio channels; see the README for the list and the evidence each one rests on.
- phone integrations act as soon as the player becomes jammed; the client-side radio integrations about half a second later, after a radio key he pressed just before has been released (the server has cut the voice already)
- ac_radio and mm_radio leave only for a player who is, or just was, on a channel, and only once per dropped channel
- Access and immunity:
Config.AllowedJobs,Config.ImmuneJobs(minimum grade or every grade) and server hooks. - Integration API: server exports
IsJammed,UseDevice,ResetPlayerSession,AreIntegrationsReady; client exportIsJammed; local eventxr-jammer:jammed; replicated player statesignalJammed. - Open files:
config.lua,locales/*.lua,server/editable.lua,client/editable.lua,integrations/, the item definitions ininstall/and the LCD pages inhtml/. - Server console status line (printed again when the framework or the inventory starts later) and plain explanations when START is locked.
config.luais checked at start: a value of the wrong type or outside its limits is replaced by its default and named in the server console.- 14 languages.
