Skip to content

Game events (world and weapons)

With OneSync, a client can ask the server to create entities, explosions, particle effects and fires that every nearby player then sees. Bastrel listens to these requests on the server, checks them, and can cancel them before they reach the other players. This page covers the world events, then the weapon events (below).

Code Event What is flagged Needs a list
BAS-GE-001 entityCreating A model in entities.blacklist yes (entities)
BAS-GE-002 entityCreating More than entitiesPer10s entities created by one player in 10 s no
BAS-GE-010 explosionEvent An explosion type outside explosions.allowed yes (explosions)
BAS-GE-011 explosionEvent An invisible explosion, or damageScale above maxExplosionDamageScale (rule mode observe, see below) no
BAS-GE-012 explosionEvent More than explosionsPer10s explosions by one player in 10 s no
BAS-GE-050 ptFxEvent An effect or asset in ptfx.blacklist, or more than ptfxPer10s effects in 10 s yes (ptfx)
BAS-GE-070 fireEvent More than firesPer10s fires received from one player in 10 s no

Bastrel applies the same rules as for every detection (see startup.md).

  • enforce: a deterministic code (001, 010, 011) cancels the event and applies the sanction of its severity (a ban for critical, a kick for high); 011 is shipped with the rule mode observe (sanctions.ruleModes) and so cancels without sanctioning until you remove that entry (see below). A heuristic code (002, 012, 050, 070) cancels the event and adds to the player’s score; the score alerts the staff and can kick, it never bans.
  • observe: nothing is sanctioned. The detection is logged with would_have (what enforce would have done). A deterministic code cancels the event in observe only when its list is marked calibrated = true, or when it needs no list; a heuristic is never cancelled in observe.
  • A list that is not calibrated keeps its codes (001, 010, 050) in observe, even under mode = 'enforce', until you set calibrated = true for it in config/whitelists.lua.

All three lists are shipped empty, and an empty list switches its check off. Bastrel does not guess which models or effects you want to refuse.

entities = { blacklist = { 'adder', 1234567 }, calibrated = false },
explosions = { allowed = { 0, 2 }, calibrated = false },
ptfx = { blacklist = { 'some_effect' }, calibrated = false },
  • entities.blacklist and ptfx.blacklist: model or effect names (turned into hashes with GetHashKey) or integer hashes. For particles, the effect name and the asset name are both compared.
  • explosions.allowed: explosion type numbers (the list of explosion tags of the AddExplosion native in the FiveM documentation). An empty list does not forbid every explosion, it turns the check off. Run in observe first and fill the list from the types your server really uses. Build the list from the observe logs of your own server. An explosion can come with collateral types: once, 240 ms after a car exploded (type 7), a second explosion of type 13 (DIR_WATER_HYDRANT in the AddExplosion list) was attributed to the player who caused it (captured on 18 October 2026, FXServer artifact 37474; the other car explosions of that day produced none), and BAS-GE-010 flags every type that is not on your list. Hydrants, and probably gas pumps and other props near a vehicle, can do this: check your observe logs before you calibrate.
  • Review each list on your server, then set calibrated = true.

The thresholds (config/modules.lua, gameevents)

Section titled “The thresholds (config/modules.lua, gameevents)”
Setting Default Meaning
entitiesPer10s 30 Entities one player’s client may create per 10 s
explosionsPer10s 10 Explosions one player may trigger per 10 s (measured peak: 2, margin x5)
ptfxPer10s 100 Networked particle effects per 10 s
firesPer10s 30 Fires received per 10 s, every fire counts (measured peak: 10, one Molotov, margin x3)
maxExplosionDamageScale 1 damageScale above this is flagged by BAS-GE-011 once explosionDamageScaleCheck is on
ignoredPopulationTypes { 1, 2, 3, 4, 5 } Entities of the ambient population made by the game are not counted by BAS-GE-002 (0 unknown, 6 permanent, 7 mission, 8 replay, 9 cache, 10 tool are checked)

Each limit is a burst allowance that refills over 10 s. These are starting values: tune them with the observe logs of your own server. The two rate limits of the explosion and fire codes were checked against a timestamped session of two real clients (18 October 2026): at most 2 explosions and 10 fires in any 10 s (a Molotov is 10 fires within 2 s; Molotovs thrown 15.3 s then 12.0 s apart never exceeded 10 in a window; the fires of the first one were not captured). The defaults keep a margin of x5 and x3 over that peak. What the session did not measure: throwing Molotovs faster than one every few seconds, a burning vehicle or vegetation, and chained explosions. If your server does that legitimately, raise the limit. An invalid value is replaced by its default, with a warning in the console.

  • Cancelling a Player entity does nothing. Cancelling entityCreating has no effect on the entity of a player (the character of someone who is connecting). Bastrel never relies on it and ignores the ped of a player, including the new ped created when a player changes model. This is a FiveM behaviour, not a setting. A hostile ped spawned by a cheat menu is not a player ped and is still judged.
  • The owner of an entity is read from the network owner. Entities made by the game (population) or by the server have no player owner and are not judged. An entity created by a server script but handed to a nearby client may be charged to that client. For BAS-GE-002 that is a heuristic (score and alert, never a ban on its own). For BAS-GE-001 it is not: do not put in entities.blacklist a model that your own scripts create, or the player who receives it can be banned (not verified in game).
  • One detection per burst. A rate overrun (BAS-GE-002, 012, 050, 070) produces one detection per player, per code and per 10 s window, not one per event over the limit. The following events over the limit in the same window stay cancelled (according to the mode and the bypass at that moment) and are counted in one burst.cancelled line per player, code and window. An event is cancelled once, however many codes ask for it, but the repeats are counted per code: a forbidden type inside the window of BAS-GE-012 is counted under BAS-GE-012 even when BAS-GE-010 cancelled the same event. An explosion that is both over the rate and invisible (or over maxExplosionDamageScale) produces both BAS-GE-012 and BAS-GE-011, in that order: the burst never hides BAS-GE-011. The three explosion checks are independent: every explosion takes its rate token, and BAS-GE-010 (forbidden type), BAS-GE-012 (rate) and BAS-GE-011 are each judged, in that order. BAS-GE-012 therefore also cancels, in enforce, the explosions over the limit whose type is forbidden, even when explosions.allowed is not calibrated and BAS-GE-010 itself is only observed. Detections of one code are capped per player by the pipeline budget (10 per 10 s by default), the rest being counted in the budget.exceeded line.
  • The population type does not exempt a model from the blacklist. The ambient population types are ignored by the burst count (BAS-GE-002) only: the client declares the type, so BAS-GE-001 checks the model first.
  • Fires: every fire counts. The server gives a script at most five fires per fireEvent and BAS-GE-070 takes one token per fire received. No field of the fire is used to tell a legitimate fire from another (a script chooses maxChildren itself), so this code is a clue (score 10), never a proof.
  • Explosions and fires are not documented by FiveM. explosionEvent and fireEvent are missing from the official event list. Their payloads were captured on real clients (FXServer artifact 35245, default game build, 17 and 18 October 2026). BAS-GE-010, BAS-GE-012 and BAS-GE-070 follow the normal rules since then. BAS-GE-010 uses a single field, explosionType, which is confirmed (a whole float such as 7.0 is read as 7), and acts only once you calibrated explosions.allowed. The captures also show that the position of an explosion is relative to an entity (not a world position) when the undocumented field f208 is not 0, (it read 106 for one car on the morning of 18 October 2026 and 65534 and 65533 for two others that evening, so what it holds is not established: it is not the vehicle’s network id in general); Bastrel uses no explosion position and never reads that field. A payload format belongs to a FXServer artifact: the captures name theirs, and a new capture is made when the development server moves to a newer one.
  • BAS-GE-011 is checked, and shipped in the rule mode observe. An invisible explosion (isInvisible = true, type 0, damageScale 1) was captured on a real client on 18 October 2026. A damageScale above 1 was still never observed: that branch is behind its own switch, explosionDamageScaleCheck, off by default until a capture exists. A burst of invisible explosions produces one detection per player and window (explosionReportMs, 10 s by default); the repeats are still cancelled and counted in burst.cancelled, per code (an event that asks for its cancellation through two codes is cancelled once, and counted once under each). A legitimate script of yours can ask for an invisible explosion, so config/modules.lua ships sanctions.ruleModes['BAS-GE-011'] = 'observe' (as for BAS-GE-023). In that mode the code needs no list, so the event is cancelled (the invisible explosion does not reach the other players), but nothing is sanctioned and no score is added; the log shows would_have. Once you have checked that none of your scripts asks for an invisible explosion, remove the entry to get the normal sanction of its severity (a ban, critical). If your scripts legitimately use invisible explosions, set invisibleExplosionCheck = false (see the switches below): invisible explosions then raise nothing and are not cancelled (the damageScale branch has its own switch, off by default).
  • BAS-GE-002 is a smoothed limit, not a sliding count. The limit is a token bucket: entitiesPer10s entities of capacity, refilled at entitiesPer10s per 10 s (3 a second for 30). A burst of 40 entities created within about 3 seconds trips it; the same 40 created over more than about 3.3 seconds (30 + 3 × seconds ≥ 40) never do, so a slow burst can go undetected, and the same holds for the other rate limits (explosionsPer10s, firesPer10s). Seen in game on 19 October 2026 (an intermittent miss on 40 entities). In the worst case the bucket lets through twice its capacity in one 10 s window (a full bucket plus a full refill: 60 entities for 30). This is a known limit; a sliding window or a smaller bucket are being considered.
  • Collateral explosion types of a destroyed vehicle. Destroying a vehicle can set off the explosions of what is next to it, and every one of them is attributed to the player who fired. Seen in game: type 7 (a car, the vehicle itself), type 10 (probably a motorbike next to a destroyed car, 19 October 2026: BAS-GE-010 kicked the player in a test profile that only allowed 0, 3, 7 and 13), and type 13 (DIR_WATER_HYDRANT, once, 18 October 2026). The names of 10 and 13 in the AddExplosion list are not all confirmed in game. Add the types you see to explosions.allowed from your own observe logs before you set calibrated = true; do not copy this list blindly.
  • Evidence of two codes. BAS-GE-070 carries fires (the fires of the event that tripped the limit) and windowFires (the fires counted for the player over the last 10 seconds, a sliding count, so a burst across the edge of a fixed window is still shown); BAS-ST-030 carries the value read (visible).

Weapons (weaponDamageEvent, giveWeaponEvent, removeWeaponEvent, removeAllWeaponsEvent, clearPedTasksEvent)

Section titled “Weapons (weaponDamageEvent, giveWeaponEvent, removeWeaponEvent, removeAllWeaponsEvent, clearPedTasksEvent)”
Code Event What is flagged Needs a list Status
BAS-GE-020 weaponDamageEvent A hit on the ped of a player that declares more damage than weapons.maxDamage (body) or weapons.maxHeadDamage (head) allows for its weapon yes (weapons) unverified
BAS-GE-021 weaponDamageEvent The declared weapon is not the weapon the shooter holds (GetSelectedPedWeapon) no unverified, a clue only (scoreOnly)
BAS-GE-022 weaponDamageEvent A target farther than weapons.maxRange for its weapon yes (weapons) unverified
BAS-GE-023 weaponDamageEvent Shooter and target in different routing buckets no unverified
BAS-GE-030 giveWeaponEvent A weapon given to a ped that is not the sender’s no unverified (logged only for now), can be switched off
BAS-GE-031 removeWeaponEvent, removeAllWeaponsEvent A weapon removed from a ped that is not the sender’s no unverified (removeAllWeaponsEvent never received), can be switched off
BAS-GE-040 clearPedTasksEvent The tasks of a ped that is not the sender’s cleared no unverified, can be switched off

weaponDamageEvent is documented by FiveM and was captured on two real clients (18 October 2026, FXServer artifact 35245, default game build): pistol and pump shotgun gunshots and punches, at players, at NPCs and at a vehicle, at the body and at the head, with and without armor. giveWeaponEvent was received with { pedId, weaponType, ammo, givenAsPickup, unk1 } when one client gave a weapon to the ped of another (18 October 2026, FXServer artifact 37474), and BAS-GE-030 is unverified again: the rule had never been seen firing on a real client before the correction of how the server reads the answer of a native that returns a yes/no value (19 October 2026), so it only logs until that test is done. Seven codes are unverified: BAS-GE-030 (above), BAS-GE-020 (the cost per shot on the server is not measured; a shot fired from a vehicle cannot be told apart in the payload, hasVehicleData is always false), BAS-GE-021 (what GetSelectedPedWeapon returns for a player’s ped was never observed), BAS-GE-022 (no distance between two entities was observed on the server), BAS-GE-023 (changing the routing bucket during shots produced no event, so no routing bucket was captured), BAS-GE-031 (removeWeaponEvent was received with { pedId, weaponType }, but removeAllWeaponsEvent, which the code also covers, never arrived after RemoveAllPedWeapons on another player’s ped) and BAS-GE-040 (clearPedTasksEvent never arrived after ClearPedTasksImmediately on another player’s ped; the game probably does not send it for a remote ped).

The weapon limits (config/whitelists.lua, weapons)

Section titled “The weapon limits (config/whitelists.lua, weapons)”
weapons = {
maxDamage = { WEAPON_PISTOL = 26 }, -- the `weaponDamage` a client may declare for one hit at the body of a player
maxHeadDamage = { WEAPON_PISTOL = 500 }, -- the same for a hit at a head component (`headComponents`)
maxRange = { WEAPON_PISTOL = 120 }, -- metres between the shooter and the target
calibrated = false,
},

The three tables are shipped empty: no FiveM native gives the damage or the range of a weapon, and a wrong number would flag honest players, so Bastrel invents none. A weapon without an entry is not checked, and a weapon without a maxHeadDamage entry is not judged at the head. Fill both for each weapon: the client declares the hit component, so a weapon with a maxDamage and no maxHeadDamage lets a cheater declare every hit as a head hit and escape BAS-GE-020. At startup Bastrel prints a [bastrel] WARNING gameevents: line that lists such weapons (keys compared as written: a name and its hash count as two keys). Only a hit on the ped of a player is judged for damage (the same weapon does different damage to a player, an NPC and a vehicle: a pump shotgun did 54 to a player and 162 to an NPC), so calibrate from hits on players. Keys are weapon names (turned into hashes with GetHashKey) or integer hashes; values are numbers above 0. Fill them from what you observe in observe, then set calibrated = true. The range is the straight distance between the shooter’s ped and the target at the moment the server receives the event, with no margin: put your margin (latency, a moving target) in the number. A malformed entry is reported at start-up and skipped.

  • BAS-GE-020 (deterministic, severity high) is unverified for now (see the bullet on the unverified codes below). Once lifted it will cancel the hit and kick the sender in enforce once weapons.calibrated = true (and cancel it in observe when calibrated too, like the other deterministic codes that depend on a list). With weapons.calibrated = false it will stay in observe; with empty weapons tables it checks nothing.
  • BAS-GE-030 is unverified for now (see the bullet on the unverified codes below): deterministic, severity high, it will cancel the event and kick the sender in enforce, and cancel it in observe, once lifted; its payload is { pedId, weaponType, ammo, givenAsPickup, unk1 } and the code reads pedId (compared with the sender’s own ped), givenAsPickup and weaponType. It judges only the captured path: givenAsPickup false and the target a player’s ped; pickups and NPC targets get no verdict until they have been captured. If a script of yours gives a weapon to the ped of another player, set giveWeaponCheck = false (below) or the sender will be kicked once the code is lifted.
  • The seven weapon codes are unverified (BAS-GE-020, 021, 022, 023, 030, 031, 040): held in observe by the code itself, whatever you write in the config or in weapons.calibrated, never cancelled, never sanctioned, no score, evidence marked payloadUnverified = true. They only log what they would see, so you can calibrate weapons from the log. A later Bastrel release lifts the restriction code by code once what they need has been checked on a real client.
  • Once lifted: BAS-GE-022 (heuristic, medium) will cancel and score in enforce once calibrated; BAS-GE-021 (heuristic, scoreOnly, 5 points) will add to the score and do nothing else, ever (the server cannot see the weapon wheel and a thrown weapon is no longer held when it hits, so false positives are expected; six windows are needed to reach the alert threshold).
  • BAS-GE-023 is also held in observe by the shipped rule mode (sanctions.ruleModes) for after the lift, because a player can change routing bucket or instance while shots are in flight. In the meantime an instance change during a fight can log BAS-GE-023, and that is all: nothing is cancelled or sanctioned.
  • One detection per player, per code and per window (weaponReportMs, 10 s by default); the repeats of a code that can be cancelled (none while the weapon codes are unverified) are cancelled too, in the same window, and counted in one burst.cancelled line; a repeat of a code that can never be cancelled costs nothing and is not logged.
Setting Default Meaning
invisibleExplosionCheck true Turn the invisible-explosion part of BAS-GE-011 off if a script of yours legitimately asks for invisible explosions (the damageScale branch has its own switch, off by default)
explosionReportMs 10000 The window of the one-detection-per-player rule of BAS-GE-011, in ms (1000 to 60000)
explosionDamageScaleCheck false Judge a damageScale above maxExplosionDamageScale in BAS-GE-011: off until such an explosion has been captured on a real client
giveWeaponCheck true Turn BAS-GE-030 off if a script of yours gives a weapon to a ped that is not the sender’s (a gift to another player’s ped; gifts to NPCs and pickups are not judged)
removeWeaponCheck true Turn BAS-GE-031 off if a script of yours removes weapons from peds that are not the sender’s (NPCs included)
projectileCheck true Turn BAS-GE-060 off altogether. To keep it on while a script of yours creates projectiles for a player without an inventory weapon, grant that player a weapon grace instead (see the bullet on scripts of yours that fire a projectile, above)
clearTasksCheck true Turn BAS-GE-040 off if a script of yours clears the tasks of peds that are not the sender’s
maxHitTargets 8 How many distinct entities hit by one shot are judged (1 to 64)
weaponReportMs 10000 The window of the one-detection-per-code rule for weapons, in ms (1000 to 60000); it also sets how fast BAS-GE-021 would build a score
headComponents { 20 } Hit components that count as a head hit: BAS-GE-020 compares them to weapons.maxHeadDamage (a weapon without an entry is not judged there); an empty list {} means no head hit. A list with some invalid entries keeps the valid ones ({ 19, 'neck' } gives { 19 }) and logs an error; a non-empty list with no valid entry falls back to { 20 } and logs an error
  • Who shoots. The damage, the held weapon, the position and the routing bucket are those of the sender’s own ped, so BAS-GE-020 to 023 judge a hit only when it is the sender’s own shot. The captures show how a client declares it: a gunshot at a player carries parentGlobalId = 0 (no declared attacker), and a shot at an NPC or a vehicle carries the network id of the shooter’s own ped. Bastrel judges both: 0 is judged, so declaring it is no way out. A hit made by an NPC the sender owns (legitimate) is judged by none of them: such a shot carries the network id of the NPC as parentGlobalId (captured on 18 October 2026, never 0), so it is never charged to the sender’s own ped. A shot at an NPC owned by the other client does arrive, with the shooter’s own ped as parentGlobalId. The limits: a client escapes BAS-GE-020 by declaring no damage or another attacker, and a weapon listed in weapons.maxDamage but not in weapons.maxHeadDamage (shipped empty) escapes it by declaring a head component. Fill the two tables together, weapon by weapon. Checking that the declared attacker belongs to the sender is planned and not implemented (it needs a native that has not been verified yet).
  • The head. A pistol shot at the head did 491 to 543 damage in the evening captures (435 to 485 in the morning ones), against 25 at the body, and killed; a pump shotgun shot at the head did 2740 and 3836. The head ceiling depends on the weapon, so calibrate weapons.maxHeadDamage weapon by weapon. BAS-GE-020 therefore compares the hit components listed in headComponents to a separate table, weapons.maxHeadDamage, and does not judge the head of a weapon without an entry (component 20 in the captures; FiveM does not document the component numbers, so the value is read from the captures and you can change it). No head multiplier is guessed: the pistol ratio varied (17 to 22) and the shotgun’s is far higher. Set your weapons.maxDamage from body hits on players and your weapons.maxHeadDamage from head hits on players.
  • Who is judged for damage. Only a target that is the ped of a player. A shot at an NPC or a vehicle is not judged by BAS-GE-020: the damage of a weapon depends on the type of target.
  • Who is hit. Any entity (a player, an NPC, a vehicle). The targets are the entity of hitGlobalId and the entities of hitGlobalIds, at most maxHitTargets distinct ones (duplicates and invalid entries are not counted); 022 and 023 judge each one. A target that cannot be found is ignored.
  • Events received, and not. giveWeaponEvent and removeWeaponEvent are sent when a client acts on the ped of another player (18 October 2026); removeAllWeaponsEvent and clearPedTasksEvent were never received on the test server, and the game probably does not send them for a remote ped. A weapon event on the sender’s own ped is never flagged. BAS-GE-031 and BAS-GE-040 cancel and sanction nothing until a later release has captured them. Player-versus-player shots were captured on 18 October 2026; BAS-GE-023 waits for a captured routing bucket value.
  • The aim hook. The subscribers of onWeaponDamage receive (src, event, attackerIsSender, cancelled, victims): cancelled is true when Bastrel cancelled the event or when another resource’s handler cancelled it before Bastrel’s ran (the order between resources is not guaranteed, so a later cancel is not seen), and victims is the list of entities already resolved for the checks (only valid during the call). They run after the event has been cancelled, in a protected call, and must not call Wait (a subscriber that yields would delay the handler of the next event).
  • The weapon wheel. See BAS-GE-021 above.
Code Event What is flagged Needs a list Status
BAS-GE-060 startProjectileEvent A projectile started by the sender’s own ped for a weapon that is not the one ox_inventory says is equipped, and not covered by a weapon grace no unverified (logged only), can be switched off

startProjectileEvent is documented by FiveM (ownerId, weaponHash, projectileHash, commandFireSingleBullet, targetEntity) and was captured on 2026-10-20 (ownerId is the network id of the sender’s ped), but nobody has tried whether cancelling it stops the projectile for the other players, and the page does not say. The code therefore ships unverified: it is logged with payloadUnverified = true, never cancels, never sanctions and adds nothing to the score, whatever your configuration says.

  • Only a projectile whose ownerId is the network id of the sender’s own ped is judged. A projectile of an NPC the sender owns is not.
  • A weapon in state.weaponIgnore (fists, and for example a snowball you add) is never judged, and neither is a player already reported in the window (no native or export is read for him).
  • commandFireSingleBullet excludes nothing: it is true for a rocket launcher held and fired by the player as well as for a script bullet, and false for a thrown grenade or Molotov. A rocket a script of yours fires in the name of a player without an inventory weapon is therefore logged. A sender in a vehicle (vehicle weapons) is not judged, nor when the server cannot tell.
  • An unknown inventory status gives no verdict (“unknown” is never “no weapon”), and a weapon grace for the projectile weapon silences it (the same graces as BAS-ST-001, see state.md).
  • Known race. When the last grenade or Molotov is thrown, ox_inventory removes the item. Bastrel sees that disappearance, and grants the grace, at its next sample (up to 1.5 s later), so the projectile event can arrive first and be logged once. Another reason this code stays unverified.
  • One detection per player and window (weaponReportMs).
  • Scripts of yours that fire a projectile in the name of a player (an airstrike or flare item, a turret) are logged when the player has no matching inventory weapon. Tell Bastrel they are legitimate with a weapon grace for the hash of the projectile weapon: exports.bastrel:allowState(src, 'weapon', ttlMs, { hash = <weapon hash> }) (src is the player’s server id, ttlMs how long the grace lasts; the hash may be signed or unsigned; your resource must be listed in grants.allowedResources). BAS-GE-060 honours it like BAS-ST-001 does. Do not switch the rule off with projectileCheck = false for this: that removes the protection for everybody. The code stays unverified until this path has been shown in game.

With the development test resource (not shipped), the scenario list of bastest_report has one scenario per code and per profile, plus false positive checks: a vehicle that blows up normally (it must raise nothing: type 7, and the collateral type 13 if it appears, are allowed by both profiles), and the weapon cases (a weapon change through the inventory, a death and respawn, an instance change during a fight) must write no BAS-GE detection other than the unverified weapon clues in either profile, and must cancel or sanction nothing; real pistol, shotgun and unarmed hits (head included) must write no BAS-GE-020.