NullSect
Systems nominal

Docs / Locksmith / Installation

Installation

This guide is written for Qbox (qbx_core + ox_*). Differences for QBCore and ESX are noted in the relevant steps.

  1. Files
  2. Database
  3. server.cfg
  4. Job definition
  5. Items (optional)
  6. Admin permissions
  7. HQ location
  8. Recording job spots
  9. Integrations
  10. Installation checklist
  11. Troubleshooting

1. Files

Copy the folder somewhere like resources/[nullsect]/ns-locksmith. Layout:

ns-locksmith/
├── fxmanifest.lua
├── config.lua            ← all settings (CONFIGURATION.md)
├── bridge/               ← framework / inventory / target / dispatch / UI bridges
├── shared/               ← locale, constants, customer pool
├── server/               ← authoritative game logic
├── client/               ← scene, NPCs, interaction, NUI bridge
├── nui/                  ← tablet + minigame UI (offline fonts included)
├── sql/locksmith.sql     ← schema
└── data/locations.json   ← job spots recorded in-game (created on the first recording if missing)

Escrow: the resource is protected with Cfx.re asset escrow. config.lua, shared/locale.lua and shared/customers.lua are open for editing; all other Lua files are encrypted. The asset is tied to the Cfx.re account that bought it (Keymaster), so run it on a server whose license key belongs to that account.

data/locations.json ships with 110 ready job spots (52 houses, 40 vehicles, 18 businesses) and holds your own recordings too. Do not overwrite this file when updating. To start from zero, set its contents to {"locations":[],"disabledDefaults":[]}.


2. Database

Automatic (recommended): Config.Database.AutoMigrate = true is enabled by default. sql/locksmith.sql runs on every start (CREATE TABLE IF NOT EXISTS, safe to repeat) and any missing indexes are added. The added index ... line only appears when upgrading from an older version.

Manual: If you set AutoMigrate = false, import sql/locksmith.sql once with HeidiSQL or phpMyAdmin.

Tables created:

TableContents
locksmith_playerslevel, XP, reputation, career path, licence, settings (JSON)
locksmith_statsjob counts, earnings, times
locksmith_equipmenttool durability and upgrade level
locksmith_skills · locksmith_achievementsskill ranks, unlocked achievements
locksmith_job_historyevery completed / cancelled job (deleted after HistoryRetentionDays)
locksmith_contracts · locksmith_business_repdaily / business / master contracts, customer relationships
locksmith_audit_logadmin actions, purchases, security warnings (deleted after AuditRetentionDays)

Tables are keyed to the character ID (citizenid / ESX identifier). When a profile is deleted, its linked rows are deleted too (FK cascade); the audit log is the exception and its records are kept.


3. server.cfg

Dependencies must start before the locksmith resource:

ensure oxmysql
ensure ox_lib
ensure qbx_core          # or qb-core / es_extended
ensure ox_target         # or qb-target
ensure ox_inventory      # optional
ensure ox_doorlock       # optional
ensure ps-dispatch       # optional (or cd_dispatch)

ensure ns-locksmith

add_ace group.admin locksmith.admin allow

Framework, inventory, target and dispatch are detected automatically. If detection gets it wrong, set Config.Framework / Inventory / Target at the top of config.lua and Config.Police.Dispatch manually.


4. Job definition (optional)

By default (Config.RequireJob = false) no job is needed: the tablet item is the access key (see §5). Skip this section unless you set Config.RequireJob = true.

The script identifies the job by Config.JobName (default locksmith). The job grade is not the locksmith level. Level progresses with XP; grade only affects salary and boss access.

Qbox — qbx_core/shared/jobs.lua

['locksmith'] = {
    label = 'Locksmith',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        [0] = { name = 'Apprentice', payment = 50 },
        [1] = { name = 'Locksmith',  payment = 75 },
        [2] = { name = 'Senior',     payment = 100 },
        [3] = { name = 'Supervisor', payment = 125 },
        [4] = { name = 'Manager',    payment = 150 },
        [5] = { name = 'Owner',      payment = 200, isboss = true, bankAuth = true },
    },
},

QBCore — qb-core/shared/jobs.lua

['locksmith'] = {
    label = 'Locksmith',
    defaultDuty = true,
    offDutyPay = false,
    grades = {
        ['0'] = { name = 'Apprentice', payment = 50 },
        ['1'] = { name = 'Locksmith',  payment = 75 },
        ['2'] = { name = 'Senior',     payment = 100 },
        ['3'] = { name = 'Supervisor', payment = 125 },
        ['4'] = { name = 'Manager',    payment = 150 },
        ['5'] = { name = 'Owner',      payment = 200, isboss = true },
    },
},

ESX — SQL

INSERT INTO `jobs` (`name`, `label`) VALUES ('locksmith', 'Locksmith');
INSERT INTO `job_grades` (`job_name`, `grade`, `name`, `label`, `salary`, `skin_male`, `skin_female`) VALUES
('locksmith', 0, 'apprentice', 'Apprentice', 50,  '{}', '{}'),
('locksmith', 1, 'employee',   'Locksmith',  75,  '{}', '{}'),
('locksmith', 2, 'senior',     'Senior',     100, '{}', '{}'),
('locksmith', 3, 'supervisor', 'Supervisor', 125, '{}', '{}'),
('locksmith', 4, 'manager',    'Manager',    150, '{}', '{}'),
('locksmith', 5, 'boss',       'Owner',      200, '{}', '{}');

Other options:

  • With Config.RequireOnDuty = true, an off-duty locksmith cannot take jobs. This setting uses the qb / qbx duty flag.
  • With Config.RequireJob = true, only players with the job can work. Combined with Config.Items.RequireTablet = true, they need both the job and the tablet.

5. Items

The tablet item is required with the default settings (Config.Items.RequireTablet = true): players buy it at any HQ with features.shop (target option Buy locksmith tablet, price Config.Items.TabletPrice). While they carry it they are a locksmith: they get automatic calls, can use the job board and do lock work. Without it they are not. Using the item (or /lstablet) opens the tablet. The toolkit item is optional.

ItemUse
locksmith_tabletRequired (default). Makes the carrier a locksmith; opens the tablet when used
locksmith_toolkitRequired to work if Config.Items.RequireToolkitItem = true. Sold in the HQ shop (Config.Shop.Items)

Tools (lockpick set, drill, etc.) are not items. Their durability is stored in the locksmith database, so they behave the same with every inventory.

ox_inventory — ox_inventory/data/items.lua

['locksmith_tablet'] = {
    label = 'Locksmith Tablet',
    weight = 600,
    stack = false,
    close = true,
    description = 'Work orders, workshop and career terminal',
},
['locksmith_toolkit'] = {
    label = 'Locksmith Toolkit',
    weight = 2500,
    stack = false,
    close = true,
    description = 'Basic toolkit for lock jobs',
},

Add images as ox_inventory/web/images/locksmith_tablet.png and locksmith_toolkit.png. The package does not include images.

QBCore — qb-core/shared/items.lua

locksmith_tablet  = { name = 'locksmith_tablet',  label = 'Locksmith Tablet',  weight = 600,  type = 'item', image = 'locksmith_tablet.png',  unique = true, useable = true,  shouldClose = true, description = 'Work orders and career terminal' },
locksmith_toolkit = { name = 'locksmith_toolkit', label = 'Locksmith Toolkit', weight = 2500, type = 'item', image = 'locksmith_toolkit.png', unique = true, useable = false, shouldClose = true, description = 'Toolkit for lock jobs' },

The script registers the tablet's "use" behaviour itself (CreateUseableItem / RegisterUsableItem); you don't need to do anything else.


6. Admin permissions

Any one of the following is enough for the admin commands and /lslocations:

  • ACE: add_ace group.admin locksmith.admin allow (ACE name is Config.Admin.Ace)
  • Framework admin: when Config.Admin.AllowFrameworkAdmins = true:
    • on Qbox, the ACEs listed in QBPermissions (admin, god)
    • on QBCore, the permissions with the same names
    • on ESX, ESXGroups

7. HQ location

The first entry in Config.Locations is the HQ. Skill resets, career path selection, licence renewal and licence reinstatement can only be done here (within 10 m). Even when started from the tablet, you need to be at the HQ.

Config.Locations = {
    {
        id       = 'hq',
        name     = 'Locksmith HQ',
        coords   = vector3(170.73, -1799.37, 29.32),   -- change to suit your map
        size     = vector3(1.6, 1.6, 2.0),             -- target box
        heading  = 0.0,
        blip     = true,
        jobOnly  = true,                                -- target options only for locksmiths
        features = { terminal = true, board = true, license = true, shop = true, repair = true },
        van      = vector4(160.52, -1807.43, 28.61, 50.0),   -- service van bay (optional)
        tutorial = vector4(165.20, -1793.10, 28.32, 320.0),  -- apprentice training door (optional)
        ped      = { model = 's_m_m_autoshop_01', coords = vector4(170.19, -1799.48, 27.32, 140.0), scenario = 'WORLD_HUMAN_CLIPBOARD' },  -- HQ clerk (optional)
    },
}

van is the service van bay: the rented van spawns there and must be parked there to be returned. Stand on an empty parking spot, type /lscoords and paste the value. Remove the line to disable van rental at that HQ.

tutorial is the apprentice training door: stand in front of a real door, facing it, and use /lscoords. Without it no training is offered.

ped is the HQ clerk: every HQ option is on this NPC. Stand where the clerk should stand, facing the customer side, and use /lscoords. Without it a plain target box at coords is used.

Other spots to set: the black market fence Config.BlackMarket.Broker.coords (with /lscoords). Black market drop points and spots are recorded in /lslocations (Black market tab); until then the example drops in config.lua are used.

To add another shop or workshop point, just add a new entry. For example, for a repair-only point, use features = { repair = true }.

To find coordinates, stand at the spot, type /lscoords and paste the value from your clipboard.


8. Recording job spots

The package ships 110 ready job spots in data/locations.json; Config.JobLocations in config.lua adds approximate default spots. To add, move or remove spots, use /lslocations (admin) in-game. It opens the Location Tool panel:

  • Left: spot counts per kind, door type counts, and warnings for what is missing (for example no business spot, so business and safe jobs are off).
  • Middle: every active spot with its distance to you, filters per kind, and a name search.
  • Right: a small map with all spots and your position, and the details of the selected spot.

See where things spawn: turn on Show spots in the world (or type /lslocations view). Near spots you see a ghost of the customer's car and the customer NPC, the door area and a label. Spots within Config.LocationTool.ViewRange get a marker. Map blips adds a blip for every spot. Spots with a job running right now get no ghost.

New spot: pick Vehicle, House door or Business door. While you walk, a live ghost shows where the customer will stand and where the door is assumed. Press E to open the form, X to cancel.

  • Vehicle: park where the customer's car should be and stay inside the vehicle.
  • House / business door: stand in front of the door, back to the door, facing the street. The door is assumed to be Config.Houses.DoorOffset behind you. Fill in the door type, the business type (business only), and optionally the ox_doorlock door name or ID.

Black market tab: record the black market spots the same way: Car to steal (sit in a car), Break-in door and Safe spot (on foot, back to the door) and Drop point (on foot or in a car; the circle shows the delivery area). Nobody waits at these spots. Until a kind has its own spots, the normal job spots are used (Config.BlackMarket.Spots); recorded drops replace the example drops in config.lua.

Selected spot actions: teleport, show in the world (with a waypoint), send a test call (accept with Y), edit the name and types, move it to where you stand now, delete. Spots with a running job cannot be edited or moved.

Recordings take effect immediately; no restart needed. Once you have recorded enough spots, set Config.Jobs.UseDefaultLocations = false to use only your own spots. Without ox_lib the panel still works; the old ox_lib menu is only a fallback when the NUI cannot open.


9. Integrations

ResourceWhat it doesSetting
ox_doorlockWhen a job at a door-linked spot succeeds, the door unlocks and relocks after RelockAfter secondsConfig.Houses.DoorlockIntegration, RelockAfter
ps-dispatch / cd_dispatchSends a call to police for alarms, suspicious attempts, fake owners, restricted zones and similarConfig.Police.Dispatch = 'auto' (ps > cd > built-in)
Built-in dispatchNotification and temporary blip for on-duty police'builtin'; 'none' to disable
ox_libMenus, input, progress bars, confirmation dialogs; notifications with Config.Notify = 'ox_lib'—

Police jobs are read from Config.Police.Jobs (default police, bcso, sasp).


10. Installation checklist

StepExpected
Server start[locksmith] ready (framework=qbx, inventory=ox). There should be no Modules not loaded error
Buy the tablet at HQ (target Buy locksmith tablet)Money is taken, the item is added, profile loaded <citizenid>; players, stats and 3 equipment rows are created in the DB
Character without the tabletProfile does not load and no calls arrive (normal)
Target the HQ clerkTerminal, buy tablet, tool locker, van and training options appear (job board, workshop, career and licence are in the terminal)
/lstabletTablet opens; home page shows level, licence and daily contracts
/locksmithjob <id> vehicleCall arrives, accept with Y, GPS, customer NPC
Finish the jobReceipt, money, XP; a locksmith_job_history row is added
restart ns-locksmithProfiles are saved and reloaded ~2 s later. Active jobs in progress are cancelled (jobs are kept in memory only)
Target a locksmith as a police characterVerify locksmith licence and Ask for work order options appear
New character with the tabletApprentice training? dialog; the checklist appears on the left and the training call comes in after opening the tablet
Rent the van at HQ (level 2)Van spawns in the bay; Take tools opens the tool cabinet with the rear doors open
Tablet → Company (level 5, at HQ)A company can be founded; the licence card shows its name
Fence at night (level 4)Talk → a Channel section appears in the tablet

11. Troubleshooting

Modules not loaded: ... / new feature didn't show up FXServer caches fxmanifest.lua. After updates that add new files, type refresh in the console, then restart ns-locksmith.

No options at HQ

  • Check that the target resource starts before the locksmith resource.
  • With jobOnly = true, only players with the locksmith job see them.
  • If Config.RequireOnDuty is enabled, you need to go on duty.

profile_not_loaded / tablet won't open

  • Check that the job name matches Config.JobName exactly.
  • Check the oxmysql connection and any SQL errors in the console.

No calls coming in

  • Enable automatic calls with /lsdispatch.
  • Your licence must be valid. Check the licence card on the tablet:
    • if expired, renew it at the HQ;
    • if suspended, it lifts when the suspension ends (if an admin set it with /locksmithlicense … suspended, it has no end date and the admin must set it to valid);
    • if revoked, get it reinstated at the HQ.
  • You must not have an active job.

Minigame immediately returns "invalid"

  • If the console shows a minigame_too_fast warning, the result arrived under the minimum time (bot / macro).
  • If legitimate players are hitting it, lower the Config.Minigames.MinTime values.

Menus won't open, "ox_lib not found" warning Start ox_lib before the locksmith resource. While the NUI is open, the tablet and minigames work without ox_lib, but the fallback menus require ox_lib.

"invalid_request" on a vehicle job The client spawns the job vehicle. Attempts to register a different vehicle (a player vehicle or another job's vehicle) are deliberately rejected. If a vehicle of the same model is parked near the spot, move the spot slightly.

Worried about data loss

  • The profile is saved when a job finishes, on level-up, and every AutosaveInterval (300 s).
  • It is also saved when the player disconnects and when the resource stops.

Cart

Your cart is empty.