Installation Guide

Step-by-step setup for oxide-baitcar — dependencies, SQL, per-framework item registration, and verification.

Step-by-step setup for oxide-baitcar.

Prerequisites

Required resources

ResourcePurpose
ox_libCallbacks, timers, UI helpers
oxmysqlDatabase driver for the baitcar_events audit log
o-linkFramework and system abstraction used by this resource

Optional resources

ResourcePurpose
oxide-policeDashcam clip recording uploads. Clips are stored in police_bodycam_recordings and surfaced in the MDT Video Storage tab. Without oxide-police running, the recorder mount is skipped and the F8 hotkey is a no-op.
o-link dispatch adapter (ps-dispatch, cd_dispatch, oxide-dispatch, etc.)Broadcasts the Bait Car Triggered alert when a suspect drives off. Without an adapter that satisfies dispatch.CreateAlert, the audit row still logs but no alert fires.

This resource depends on your server's o-link setup for:

  • player identity and character lookup
  • job and rank checks (AllowedJobs)
  • notifications (officer and suspect toasts)
  • inventory reads, item add / remove, and usable-item registration
  • framework.RegisterUsableItem for the chip, the remote controller, and the removal tool (screwdriver)
  • progressbar.Open for the install and search animations
  • radial.Register / radial.AddItem for the radial entry (dashboard mode only)
  • dispatch.CreateAlert for theft alerts
  • menu.Open and input.Open for the dashcam tuner
  • logger for the structured audit / debug log

Installation

Place the resource

Place oxide-baitcar inside your server's resources folder.

Add startup order

Start dependencies before this resource:

server.cfg
ensure ox_lib
ensure oxmysql
ensure o-link
ensure oxide-police   # optional, but required for clip recording
ensure oxide-baitcar

If you use a different dispatch resource, start its o-link adapter before oxide-baitcar as well.

Install the SQL table

Run sql/install.sql against your database. This creates the single audit table the resource writes to:

sql/install.sql
CREATE TABLE IF NOT EXISTS `baitcar_events` (
    `id`              INT AUTO_INCREMENT PRIMARY KEY,
    `officer_charid`  VARCHAR(60) NOT NULL,
    `plate`           VARCHAR(8) NOT NULL,
    `model`           VARCHAR(50) NULL,
    `event_type`      ENUM(...) NOT NULL,
    `suspect_charid`  VARCHAR(60) NULL,
    `coords`          JSON NULL,
    `extra`           JSON NULL,
    `created_at`      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_officer` (`officer_charid`),
    INDEX `idx_plate` (`plate`),
    INDEX `idx_event_type` (`event_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

The event_type enum covers every action the resource records: deployed, lock, unlock, engine_off, engine_on, triggered, arrest, chip_removed, decommissioned, steering_off, steering_lock, steering_left, steering_right, speed_limit_on, speed_limit_off, strobe_on, strobe_off, alarm_on, alarm_off, horn.

Register the inventory items

oxide-baitcar uses three items by default. The item names below match the defaults in shared/config.lua (Config.ChipItem, Config.RemovalToolItem, Config.RemoteControllerItem) — if you change those values, register your custom names instead.

Item namePurposeImage
baitcar_chipThe chip an officer installs into a target vehicle. Consumed on install, returned to inventory if a suspect successfully removes it.items/baitcar_chip.png
screwdriverCivilian removal tool (config default). Must be registered as usable. The civilian uses it from inventory while sitting in a vehicle to roll for chip removal; used outside a vehicle it silently no-ops. Most servers already ship a screwdriver — leave Config.RemovalToolItem = 'screwdriver' to reuse it. A screwdriver.png image ships in items/ if your inventory framework does not already provide one.items/screwdriver.png
baitcar_remoteHandheld remote controller. Required entry point for all bait-car controls when Config.UseRemoteController = true (the default).items/baitcar_remote.png

Add to qb-core/shared/items.lua:

qb-core/shared/items.lua
['baitcar_chip']   = { name = 'baitcar_chip',   label = 'Bait Chip',         weight = 50,  type = 'item', image = 'baitcar_chip.png',   unique = false, useable = true,  shouldClose = true,  description = 'Police-issue chip. Install in a target vehicle to enable remote control.' },
['baitcar_remote'] = { name = 'baitcar_remote', label = 'Bait Car Remote',   weight = 100, type = 'item', image = 'baitcar_remote.png', unique = true,  useable = true,  shouldClose = true,  description = 'Handheld controller for police bait cars.' },
-- Most servers already define screwdriver; if not (note: useable = true so oxide-baitcar can bind its use handler):
['screwdriver']    = { name = 'screwdriver',    label = 'Screwdriver',       weight = 200, type = 'item', image = 'screwdriver.png',    unique = false, useable = true,  shouldClose = true,  description = 'A flat-head screwdriver.' },

Insert into the items table:

INSERT INTO `items` (`name`, `label`, `weight`) VALUES
    ('baitcar_chip',   'Bait Chip', 1),
    ('baitcar_remote', 'Bait Car Remote', 1),
    ('screwdriver',    'Screwdriver', 1);

Register usable items on the server (for example in es_extended/server/main.lua or an esx_addoninventory config). oxide-baitcar registers its own usable handlers through o-link.framework.RegisterUsableItem, so no extra usable wiring is required as long as your ESX o-link adapter implements it.

Add to ox_inventory/data/items.lua:

ox_inventory/data/items.lua
['baitcar_chip']   = { label = 'Bait Chip',       weight = 50,  client = { image = 'baitcar_chip.png'   } },
['baitcar_remote'] = { label = 'Bait Car Remote', weight = 100, client = { image = 'baitcar_remote.png' }, stack = false },
-- ox_inventory ships a screwdriver out of the box; reuse it.

Copy item images

Copy the .png files from items/ into the image directory used by your inventory resource.

Common locations:

  • qb-inventory/html/images/
  • ox_inventory/web/images/

The packaged image filenames are baitcar_chip.png, baitcar_remote.png, and screwdriver.png (only copy the latter if your inventory framework does not already ship a screwdriver image).

User interface (prebuilt)

The dashboard, remote controller, and dashcam recorder are prebuilt and already included in the resource. There is nothing to build or install — they work as soon as the resource starts.

Set up media uploads (FiveManage)

This only matters if you want dashcam clips to be saved. Bait car dashcam clips do not upload on their own — they go through oxide-police's recording pipeline. So two things must be true for clips to save:

  1. oxide-police is installed and running (without it, the recorder is skipped and the F8 hotkey does nothing).
  2. The FIVEMANAGE_MEDIA_API_KEY convar is set in your server.cfg (the text file that holds your server's settings). This is the same key oxide-police uses for mugshots and bodycam — set it once and all three work.

The short version:

server.cfg
set FIVEMANAGE_MEDIA_API_KEY "your_key_here"

For the full walkthrough (creating a free FiveManage account, making a Media token, and where to paste it), see the oxide-police guide: Setting up FiveManage media uploads.

If you set Config.Recording.provider = 'discord' instead, clips post to oxide-police's bodycam Discord webhook and no FiveManage key is needed.

Review configuration

Adjust shared/config.lua to fit your server. The most common keys to set first:

  • Config.AllowedJobs — map of jobName -> { minGrade = N }. Defaults to police and LSPD. Officers must hold one of these jobs (at or above the listed grade) to install chips, see deployments, open the dashcam, and operate the remote.
  • Config.UseRemoteController — when true (default), the only entry point is the baitcar_remote item; the /baitcar command and the radial menu are disabled. When false, /baitcar opens the dashboard and the radial submenu is registered with your radial provider.
  • Config.Recording.provider'fivemanage' (default) or 'discord'. Dashcam clips upload through oxide-police. With 'fivemanage', the credential is the FIVEMANAGE_MEDIA_API_KEY convar in your server.cfg (the same key oxide-police uses for mugshots and bodycam). With 'discord', clips post to oxide-police's bodycam Discord webhook. See Set up media uploads above.
  • Config.Dispatch — alert code, title, icon, priority, and blip. Consumed by olink.dispatch.CreateAlert so the exact behavior depends on your dispatch adapter.

Full per-key reference: Configuration.

Tune dashcams for your fleet (optional)

The packaged camera profiles in Config.Dashcam.ByClass cover every stock GTA vehicle class. If you use a bait fleet of specific models (especially add-ons), tune them in-game with /baitcardashcamtune and paste the resulting snippet into Config.Dashcam.ByModel. See Admin for the full tuner workflow.

Verification

Start the server and confirm ox_lib, oxmysql, o-link, and oxide-baitcar all load without dependency errors.
Join the server on an account whose job matches Config.AllowedJobs. Give yourself a baitcar_chip and, if UseRemoteController = true, a baitcar_remote.
Get into any car, use the chip item, and confirm the install progressbar runs and the chat / notify message confirms the install.
Open the remote controller item (or /baitcar with UseRemoteController = false). The deployment should appear in the list. Lock and unlock the doors and confirm the icons reflect the state.
Open the dashcam and confirm the camera attaches to the bait car.

Switch to a non-allowed character, drive the bait car off above Config.TriggerSpeedMps for at least Config.TriggerDurationMs. Confirm:

  • The dispatch alert fires (depends on your dispatch adapter).
  • All allowed-job clients receive a Bait car triggered: <plate> toast.
  • A triggered row appears in baitcar_events.
  • If Config.FakeWantedLevel > 0, the suspect picks up that wanted level.
While still on the dashcam, press F8 and confirm the recorder prompt opens. Stopping the recording should upload through oxide-police (Fivemanage or Discord) and surface in the MDT Video Storage tab.

Optional Setup

Admin permission for the dashcam tuner

/baitcardashcamtune is available to any admin — anyone in your server's admin group. To make someone an admin, add them to the admin group and grant that group the admin ace in your server.cfg (the text file that tells your server which resources to start and holds your permission settings):

server.cfg
add_principal identifier.license:<their-license> group.admin
add_ace group.admin admin allow

Replace <their-license> with the player's license identifier. If you already run QBCore, QBX, or ESX admins, no extra setup is needed — your existing framework admins pass automatically. See Admin for the full tuner workflow and key bindings.

Translations

All player-facing strings are loaded from locales/<lang>.json via lib.locale() (set SetConvar('ox:locale', '<lang>')). The shipped pack is locales/en.json; copy it to your locale code (e.g. locales/de.json) and translate the values to add another language.

Next Steps