Features

Feature reference for oxide-baitcar — deployment, dashboard, remote, dashcam, remote actions, trigger detection, civilian search, recording, audit log, and officer blips.

Reference for every system oxide-baitcar ships. Each section explains the in-game behavior and the configuration keys that govern it.

Overview

A bait car is a regular civilian vehicle that an officer has chipped. To civilians it looks identical to any other car in the world. To officers in Config.AllowedJobs, every active deployment is visible as a map blip, in the dashboard or remote, and through a live event feed.

The system is asymmetric on purpose: civilians cannot tell a chipped car from a clean one. Even the screwdriver-use removal flow (triggered from inside any vehicle) returns identical "found nothing" messaging on a clean vehicle, a failed roll, and a rate-limited request. This keeps the item from being weaponized as a free bait-car detector.

Deployment

A deployment is created when an officer uses the chip item (Config.ChipItem, default baitcar_chip) while sitting in a target vehicle.

The install flow:

Officer uses the chip item from their inventory.

Client checks the officer is in a vehicle. The server re-verifies, requires the officer to be physically inside the vehicle (any seat), and rejects vehicle types other than automobile (bikes, boats, helis, planes, etc. are blocked).

A progress bar runs for Config.InstallDurationMs (default 4000 ms) with the mini@repair / fixing_a_ped animation. The bar is cancellable.

On completion, the server consumes one baitcar_chip from the officer's inventory and registers the deployment.

The oxide:baitcar entity statebag is set on the vehicle, replicating a public view of the deployment (plate plus the state booleans) only to clients with the entity in scope.

Deployment guard rails:

  • Plate uniqueness. A plate that is already an active deployment is rejected. Servers using auto-generated plates inherit that uniqueness; servers reusing plates need to wait for the first deployment to be decommissioned before re-chipping.
  • Per-officer cap. Officers can hold at most Config.MaxDeploymentsPerOfficer (default 3) active deployments simultaneously. The cap is per character id, not per source, so disconnecting does not free a slot.
  • Inventory check. The chip is only consumed after every other gate passes. A failed inventory remove cancels the deployment.

Operating Modes

Config.UseRemoteController picks between two operating modes. The two modes are mutually exclusive.

ModeUseRemoteControllerEntry points
Remote controller (default)trueThe baitcar_remote item is the only entry point. The /baitcar command is not registered, and the radial submenu is not added.
DashboardfalseThe /baitcar command opens the dashboard. The o-link radial provider receives a Bait Car root entry with Open Manager and Watch Nearest Dashcam actions.

In both modes the dashcam, all remote actions, and the dashcam recording hotkey work identically. The remote-controller mode is appropriate when you want the officer to physically carry the device (and risk losing it on death / search). Dashboard mode is appropriate when the controls should be available any time the officer is on duty.

Dashboard

The dashboard is the Vue 3 view that the /baitcar command opens. It is also reachable from the radial entry Bait Car -> Open Manager. The dashboard shows:

  • Deployment list — every active deployment with plate, vehicle model, install timestamp, and current state (locked / engine / steering / speed limit / strobe / alarm / triggered flag).
  • Stat tiles — count of active deployments, triggered count, and other rollups derived from the live deployment list.
  • Event feed — the live officer-only feed (see Audit Log and Live Event Feed).
  • Action buttons — lock / unlock, engine kill / restore, watch dashcam, set GPS waypoint, decommission.

Remote Controller

The remote is a Vue view styled as a handheld device. It is opened by using the baitcar_remote item (with Config.UseRemoteController = true). It shows:

  • Signal strength bars derived from the officer's distance to the bait car.
  • Live telemetry — speed, heading, current street and zone, fuel level, engine and body health, occupant list (when the entity is in scope).
  • Full action surface — lock / unlock, engine kill / restore, steering modes, speed limiter, strobe, alarm, horn pulse, dashcam viewer, waypoint, and decommission.

Because the remote is an inventory item it is subject to your inventory's loss-on-death and search behaviors.

Dashcam

The dashcam attaches a scripted camera to the bait car and plays it back through a Vue HUD overlay. Officers can open the dashcam from the dashboard, the remote, or the radial Watch Nearest Dashcam shortcut.

Camera profile resolution is layered. For each field (fov, offset, rotation), the value is taken from the first layer that defines it:

Config.Dashcam.ByModel[<spawnName>] — exact model override.

Config.Dashcam.ByClass[<className>] — vehicle-class default. Class names match GTA's vehicle class integer mapping: compact, sedan, suv, coupe, muscle, sportsclassic, sports, super, offroad, industrial, utility, van, service, emergency, military, commercial, openwheel.

Config.Dashcam.Default — universal fallback.

Behavior:

  • The camera is re-rotated every frame to track the vehicle's heading plus the configured yawBias.
  • A slow poll (every Config.Dashcam.pollIntervalMs, default 2500 ms) confirms the vehicle still exists, is in network scope, and still carries the oxide:baitcar statebag. If any check fails the dashcam closes itself with a reason (feed_lost, chip_removed).
  • The exit keys default to Esc and Backspace (controls 200 and 177). Configure with Config.Dashcam.exitKeys.
  • An optional timecycle modifier (Config.Dashcam.timecycleEffect) and strength produce a stylized CCTV look. Empty string disables.
  • During playback the officer is invisible to themselves and core driving / combat controls are disabled to prevent input bleed.

The admin command /baitcardashcamtune lets you adjust offsets, pitch, yaw bias, and FOV live and produces a paste-ready Lua snippet. See Admin.

Remote Actions

All actions are server-authoritative. The client only fires the intent event; the server validates job permissions, looks up the deployment by plate, writes the new state into the entity statebag, logs an audit row, and broadcasts the deployment change to all officer clients.

ActionBehavior
Lock / unlockSets SetVehicleDoorsLocked = 4 (locked-for-non-owners) and SetVehicleDoorsLockedForAllPlayers = true while locked; releases both on unlock. Enforced by a 250 ms client tick on every client that has the entity in scope; only the network owner's writes stick.
Engine kill / restoreToggles SetVehicleUndriveable and forces SetVehicleEngineOn(false) while killed.
Steering: off / lock / left / rightUses SetVehicleSteerBias (the physics-aware override). lock centers the wheel, left pushes bias to +Config.SteeringBias (default 1.0 = full left), right to -Config.SteeringBias. Analog steer input (controls 59 / 60) is disabled while any non-off mode is active.
Speed limiterCaps the vehicle at Config.SpeedLimitMph (default 30 mph) via SetVehicleMaxSpeed. Releases by setting a deliberately-high cap (SetVehicleMaxSpeed(0) would clamp to zero).
StrobeToggles SetVehicleLights between 2 (force-on) and 1 (force-off) at Config.StrobeIntervalMs cadence (default 150 ms).
AlarmSets the alarm capability flag and restarts StartVehicleAlarm when the in-game alarm naturally expires (~30 s). Toggling off both clears the flag and zeroes any remaining alarm timer.
HornFires a one-shot StartVehicleHorn for Config.HornDurationMs (default 1500 ms). Implemented via a separate oxide:baitcar:horn statebag pulse with a nonce, so only clients with the entity in scope receive the pulse and the bait-car netId is never broadcast globally.

The lock, engineDisabled, steering, speedLimit, strobe, and alarm fields all live on the entity statebag (oxide:baitcar) so every officer's dashboard or remote stays in sync without polling the server.

Drive-Off Trigger

Trigger detection runs entirely on the client of any non-LEO civilian who is in the driver's seat of a vehicle with the oxide:baitcar statebag.

The detector:

Confirms the player is the driver, that LEO immunity does not apply (BaitCar.IsAllowed()), and that the plate has not already been triggered this session.

Watches GetEntitySpeed against Config.TriggerSpeedMps (default 8.0 m/s, ~29 km/h).

Accumulates time while above the threshold; when accumulated time reaches Config.TriggerDurationMs (default 1500 ms) and the engine is running, fires oxide:baitcar:server:trigger with the vehicle's network id.

Re-arms after the statebag is cleared (so re-deployed plates can trigger again on the same client).

Server-side trigger handling:

  • Validates the source is actually the driver of the vehicle (anti-spoof).
  • Rejects LEO sources (officers can't trigger their own deployments).
  • One-shot per deployment: a triggered = true flag is set, so subsequent fires from the same or other clients are ignored.
  • Calls the configured dispatch adapter through olink.dispatch.CreateAlert with the Config.Dispatch payload, vehicle plate, model, and coords.
  • Optionally applies Config.FakeWantedLevel (set to 0 to disable).
  • Notifies every authorized officer with a Bait car triggered: <plate> toast.
  • Writes a triggered row to baitcar_events with the suspect char id and coords.

A second dispatch alert fires the first time a non-LEO sits in the bait car, before they drive off and trip the trigger threshold. This uses Config.EntryDispatch (separate code, title, priority, and blip from the trigger alert) and routes through the same job list as the trigger — so every responder is notified, not just the deploying officer. Entry alerts are gated by Config.NotifyOfficerOnEntry, rate-limited per plate (one alert every 30 s regardless of source), and skipped for LEOs.

Civilians remove a chip by using Config.RemovalToolItem (default screwdriver) from their inventory while sitting inside a vehicle. The item must be registered as a usable item in the customer's inventory framework; oxide-baitcar binds the use callback through olink.framework.RegisterUsableItem.

The flow:

The civilian uses the screwdriver from their inventory. If they are not in a vehicle, the use is a silent no-op — no toast, no animation, nothing. This keeps the item generic so it can co-exist with other resources that may also consume it.

If the civilian is in any seat of a vehicle, a progress bar runs for Config.RemovalDurationMs (default 12000 ms) with movement / combat disabled. The bar is cancellable.

The server re-verifies the source is still seated in the same vehicle (trust boundary against modded clients spoofing arbitrary netIds), validates the source still holds the removal tool, rate-limits the call (one search per source every 3 s), and rolls math.random() against Config.RemovalSuccessChance (default 0.5).

Crucially, the same You search the vehicle but find nothing of value. message is returned for a non-bait vehicle, a failed roll, a rate-limited call, a missing entity, and a not-in-vehicle reject. The civilian cannot distinguish between them, so the screwdriver cannot be used as a bait-car detector.

On a successful roll against an actual bait car: the statebag clears, the deployment is removed, the chip is returned to the suspect's inventory, an audit row (chip_removed) is written, and every authorized officer is notified.

The entry dispatch alert still fires the first time a non-LEO sits in a bait car, so officers see the suspect on the map even if the suspect immediately uses a screwdriver and gets lucky on the roll. A future inventory item will introduce a way to bypass the entry alarm; the screwdriver intentionally cannot.

Dashcam Recording

The recording pipeline routes through oxide-police so clips appear in the MDT Video Storage tab alongside bodycam clips. The pipeline is gated on three conditions at all times:

  • Config.Recording.enabled = true
  • GetResourceState('oxide-police') == 'started' (rechecked on every call; safe to start / stop oxide-police at runtime)
  • The officer's job is in Config.AllowedJobs

Workflow:

Officer opens the dashcam. The client mounts the recorder NUI overlay and pre-fetches the officer's recorder name (first + last from olink.character.GetName).

Officer presses F8 (configurable via Config.Recording.hotkey) to start recording. A second press stops it.

The recorder auto-stops at Config.Recording.maxDurationS (default 45 seconds) regardless of user input.

On stop, the clip is uploaded:

  • Config.Recording.provider = 'fivemanage' (default) — the upload URL is fetched from oxide-police's GetVideoUploadUrls export and the clip is sent to Fivemanage. This requires the FIVEMANAGE_MEDIA_API_KEY convar in your server.cfg (the same key oxide-police uses for mugshots and bodycam).
  • Config.Recording.provider = 'discord' — the clip is forwarded to oxide-police's UploadVideoToDiscord export. Clips above Config.Recording.discordMaxBytes (default 25 MiB) are rejected.

A thumbnail is captured at Config.Recording.thumbnail.captureSeconds (default 1.0 s into the clip) at Config.Recording.thumbnail.quality (default 0.7) if Config.Recording.thumbnail.enabled = true.

The metadata (plate, vehicle model label, recorder character id, deploying officer character id) is forwarded to oxide-police's SaveVideoRecording export. The plate is validated server-side against the active deployments before being written to metadata.deployedBy.

Closing the dashcam (or losing the feed) automatically flushes and unmounts the recorder.

Audit Log and Live Event Feed

Every state change writes a row into baitcar_events. The full set of event_type values:

EventLogged when
deployedChip installed on a vehicle
lock / unlockDoors toggled
engine_off / engine_onEngine kill toggled
steering_off / steering_lock / steering_left / steering_rightSteering mode set
speed_limit_on / speed_limit_offLimiter toggled
strobe_on / strobe_offStrobe toggled
alarm_on / alarm_offAlarm toggled
hornOne-shot horn fired
triggeredDrive-off detected (one-shot per deployment)
chip_removedCivilian successfully rolled the search
decommissionedOfficer cleared the deployment
arrestReserved (not currently emitted by any code path)

Rows include officer_charid, plate, model, the optional suspect_charid, an optional JSON coords object, and an optional JSON extra object. Indexes on officer_charid, plate, and event_type keep audit queries fast.

In addition to the database row, every Events.Log call broadcasts a oxide:baitcar:client:event event to every authorized-job client. The Vue dashboard subscribes to this and renders the event feed in real time. Non-officers never receive the broadcast.

Officer Blips

When an officer's job becomes authorized (on login or olink:client:jobChanged), the client fetches the active deployment list and attaches a map blip to each bait-car entity. The blip uses Config.OfficerBlip:

  • sprite (default 225)
  • color (default 5)
  • scale (default 0.9)
  • label — combined with the plate as <label> [<plate>]

Blips are non-short-range so they're visible at any zoom level. They auto-clear when the deployment is removed or the resource stops.