Skip to content

Graces: telling Bastrel about a legitimate state

Some things your scripts do look like cheating to a server-side check: teleporting a player to the hospital, making them invincible during a cutscene, hiding them while a character is created. A grace is how your own server scripts tell Bastrel “I did this, it is expected”. Only server scripts can grant one: the client SDK never can (a cheat would use it to excuse itself).

The state checks (see state.md) and the movement checks (see movement.md) read these graces. The exports and the grace table are in place so your scripts can call them now.

Call them from a server script of yours (bastrel must be started):

-- the player is moved to these coordinates; expected for 5 seconds
exports.bastrel:allowTeleport(src, vector3(298.6, -584.4, 43.3), 5000)
exports.bastrel:allowState(src, 'invincible', 10000) -- cutscene, character creation
exports.bastrel:allowState(src, 'invisible', 0) -- 0 = until you revoke it
exports.bastrel:revokeState(src, 'invisible')

Each export answers true when it did something and false otherwise; it never raises an error. The reason of a refusal is in the log (below).

State Duration Notes
teleport (allowTeleport) ttlMs from 1 coords is the expected destination and must be finite numbers. 0 is refused.
invincible, invisible ttlMs from 1, or 0 = until revokeState
weapon, weaponModifier, health, camera ttlMs from 1 only Outside the base spec, see below. weapon needs the exact hash: exports.bastrel:allowState(src, 'weapon', 3000, { hash = GetHashKey('WEAPON_PISTOL') }). Never a wildcard.
  • src is the server id of a player Bastrel tracks; an unknown id is refused.
  • A duration above grants.maxTtlMs (10 minutes by default) is cut to it, and the log says so.
  • Each resource has its own grace per player and state, with its own duration and data (destination, weapon hash). The player is covered as long as one of them is. Granting again never shortens your own grace, and never replaces another resource’s. Revoking removes all of your own entries for that state (every weapon hash you granted). A resource may hold at most grants.maxWeaponHashes (16) weapon hashes per player.
  • Graces are forgotten when the player leaves and when the resource restarts.

Limiting who can grant: grants.allowedResources

Section titled “Limiting who can grant: grants.allowedResources”

config/modules.lua, block grants:

grants = {
allowedResources = { 'qbx_ambulance', 'my_cutscenes' },
maxTtlMs = 600000,
logMs = 60000,
},
  • Empty list (default): any server resource may grant teleport, invincible and invisible. The states weapon, weaponModifier, health and camera are refused.
  • With names: only these resources may grant or revoke, and only then can the four extra states be granted. A compromised resource that is not in the list cannot excuse a cheater.

Bastrel’s own inventory adapter is the one exception: it may grant a short weapon grace itself while the list is empty (finite duration, logged). A script of yours can only revoke a grace it granted itself.

We recommend filling the list as soon as you use graces.

Every grace granted, refused or revoked is written to the Bastrel log with the calling resource, the state and the player (grants.granted, grants.refused, grants.revoked). The first of each (resource, state) in a minute is written at once; the repeats are summed in one grants.summary line when the minute ends, so a script that calls the export in a loop cannot flood the log.

Refusal reasons: bad_src, unknown_player, unknown_kind, no_caller, resource_refused, not_owner (revoking a grace another resource granted), cap_reached (too many weapon hashes), bad_ttl, bad_coords, bad_hash.

Setting Default Meaning
grants.allowedResources {} Resources allowed to grant or revoke.
grants.maxTtlMs 600000 Longest grace, in ms (1 to 3600000).
grants.maxWeaponHashes 16 Weapon hashes one resource may hold at once per player (1 to 64).
grants.maxTeleports 4 Teleport destinations one resource may have declared at once per player (1 to 64); each is used up by one teleport. Past it, the oldest one of that resource is dropped.
grants.logMs 60000 Window over which repeated log lines are summed (1000 to 3600000).
sampler.tickMs 500 Period of the scheduler tick, in ms (500 to 2000).
sampler.intervalMs 1500 Every player is read once per interval (1000 to 2000, a multiple of tickMs).
sampler.maxPerTick 64 Players read in one tick at most (1 to 1024).

An invalid value never stops Bastrel: the invalid field takes its default and a warning is logged. If allowedResources itself is invalid, every external resource is refused until you fix it (Bastrel’s own grace for weapon still works).

All the periodic reads of the state of players share one scheduler (sampler). Players are read in equal slices spread over the interval, so the cost has no peak, and a player who joins is read from the next interval. Nothing to configure unless you want a slower rhythm.