Skip to content

Configuration

Configuration is split across the configs/ directory:

  • configs/config.lua — global settings: locale, the contact ped, the black-market shop, statistics, and per-crime level thresholds.
  • configs/<crime>.lua — one file per crime (20 of them), each holding that crime's cooldown, tools, rewards, police alert, and minigame.

Every value can also be edited live from the in-game admin panel — those edits are stored as overrides and take precedence over the file defaults.

Global Config (config.lua)

Locale

lua
Locale = 'en', -- Loads locales/<Locale>.json. Falls back to 'en' if missing.

Logging & Menus

lua
ShopLogging = true, -- Log shop buy/sell + ped actions to the history ledger.
IconColors  = false, -- Allow per-item icon colors in the menus.

Contact Ped

The black-market contact is a ped that spawns at one random location from the list each restart.

lua
Ped = {
    Location = {
        {x = 366.44, y = -1250.83, z = 31.51, w = 321.98},
        -- Add more {x,y,z,w} tables; one is chosen at random each start.
    },
    Model = "a_m_m_mlcrisis_01",
    Interaction = { Icon = "fas fa-circle", Distance = 3.0 },
    Scenario = "WORLD_HUMAN_STAND_IMPATIENT", -- Ambient animation. Scenario list: https://pastebin.com/6mrYTdQv
}

Black-Market Shop

The contact buys and sells. Each BuyItems / SellItems row is a self-contained card.

lua
Shop = {
    Enable = { Buying = true, Selling = true },
    BuyItems = {
        {
            Product = "lockpick",    -- inventory item name
            Price = 50,              -- cost per unit
            Label = "Lockpick",      -- display title
            Description = "Essential tool for breaking into mailboxes...",
            IconName = "fas fa-key", -- FontAwesome icon
            IconColor = "#FFD700"    -- optional; omit for no color (needs IconColors = true)
        },
        -- screwdriver, crowbar, gloves, ...
    },
    SellItems = {
        { Product = "stolen_goods", Price = 100, Label = "Stolen Goods", Description = "...", IconName = "fas fa-box", IconColor = "#e56969" },
        -- jewelry, electronics, ...
    },
}

TIP

This is where you sell the tool items from the installation page. Add screwdriver, multitool, powersaw, etc. as BuyItems rows so players can purchase the gear each crime needs (the vanilla WEAPON_* tools don't need selling).

Statistics

lua
Stats = { Enable = true }, -- Track per-crime attempts, successes, cash, items.

Shared Package Item

Mailbox break-ins, parcel theft, and smash & grab all hand out one shared, openable package item. Configure the item key and what opening it yields here — every source behaves identically.

lua
Package = {
    Item = 'package',                 -- Inventory item the crimes give. Must exist in your inventory.
    RewardChance = 80,                -- % chance an opened package isn't empty.
    LootCount = { min = 1, max = 3 }, -- Distinct items rolled per successful open.
    Rewards = {                       -- One flat weighted pool (chance = relative weight).
        { item = 'lockpick',  chance = 25, min = 1, max = 2 },
        { item = 'phone',     chance = 20, min = 1, max = 1 },
        { item = 'goldchain', chance = 20, min = 1, max = 2 },
        -- ...
    },
}
FieldMeaning
ItemThe shared item key. Crimes award it; using it opens it
RewardChance% chance an opened package contains loot (else empty)
LootCountHow many distinct items are rolled per open
RewardsFlat weighted pool — chance is a relative weight, sampled without replacement

While a player holds a package they carry it on their shoulder (controls disabled); the carry visuals and the open progress bar / skill-check live in configs/parceltheft.lua. Opening grants loot only — the crimes themselves award the XP for the rob/pickup.

Level Thresholds

Every crime has its own 3-level curve. xpRequired is the cumulative XP needed to reach that level. Higher levels unlock the richer reward tiers defined in each crime's config.

lua
Levels = {
    mailbox = {
        [1] = { xpRequired = 0 },   -- starting level
        [2] = { xpRequired = 150 },
        [3] = { xpRequired = 300 },
    },
    pickpocket = {
        [1] = { xpRequired = 0 },
        [2] = { xpRequired = 100 },
        [3] = { xpRequired = 200 },
    },
    -- ...one block per crime (payphone, parkingmeter, robaped, shoplift,
    -- parceltheft, vending, catalytic, smashgrab, tiretheft, acstrip,
    -- newsrack, atmskimmer, tireslash, brakecut, wheelloose, fuelsabotage,
    -- signrob, brickgas)
}

Per-Crime Config Anatomy

All crime configs share a common shape. Below is the prop-crime layout (mailbox, payphone, parking meter, news rack, vending, shoplift) — the others add a few fields on top, covered after.

lua
return {
    Cooldown = 2,                 -- Minutes before the same target is robbable again.
    Items = { 'WEAPON_HAMMER' },  -- Tools — ANY ONE is enough. (see installation tool mapping)
    Time = 15,                    -- Progress-bar duration in seconds.
    BaseXP = 15,                  -- XP for a successful attempt.
    GiveXPForItems = true,        -- Add each rolled item's own xp on top of BaseXP.
    Models = {                    -- World props the target option attaches to.
        'prop_letterbox_01', 'prop_letterbox_02', 'prop_letterbox_03', 'prop_letterbox_04',
    },
    Logging = true,               -- Write success/items/cash to the history ledger.

    Minigame  = { ... },          -- Skill check (see Minigames below).
    PoliceAlert = { ... },        -- Dispatch hook (see Police Alerts below).
    Rewards   = { ... },          -- Weighted loot, keyed by player level.
}

Rewards & Weighted Chance

Reward tables are keyed by player level ([1], [2], [3]). Each row's chance is a weight, not a percentage — think of it as the number of raffle tickets in the hat.

lua
Rewards = {
    [1] = { -- Level 1
        { item = "cash",         chance = 15, min = 3, max = 8, xp = 0 },
        { item = "package",      chance = 30, min = 1, max = 1, xp = 5 },
        { item = "metalscrap",   chance = 25, min = 1, max = 2, xp = 2 },
    },
    [2] = { ... }, -- richer pool, unlocked at level 2
    [3] = { ... },
}
FieldMeaning
itemInventory item name (cash pays out money)
chanceRelative weight — higher = more likely within its level bucket
min / maxQuantity range awarded
xpBonus XP if this item is rolled (added when GiveXPForItems = true)

Openable Packages

Mailbox break-ins and smash & grab include a package in their loot tables, and parcel theft hands one out on pickup. The package is a single shared, openable item — its loot pool lives in Shared Package Item above. The per-crime configs just list package as a reward; there's no per-crime package table.

Person Crimes (pickpocket, armed robbery)

Pickpocketing and armed ped robbery roll loot by map zone tier instead of a flat per-level table:

lua
Cooldown = { Enable = false, Time = 30 }, -- Personal cooldown (seconds) between attempts.
Chance   = 80,                            -- Overall % the target has anything on them.

Tiers = {
    low    = { cash = { min = 8, max = 25 }, [1] = { ... }, [2] = { ... }, [3] = { ... } },
    medium = { cash = { min = 20, max = 50 }, ... },
    high   = { cash = { min = 40, max = 100 }, ... },
},
ZoneTiers = {
    high   = { 'AIRP', 'PBOX', 'DOWNT', ... }, -- GetNameOfZone() short codes
    medium = { 'CHIL', 'ROCKF', 'BEACH', ... },
    -- anything not listed falls through to the `low` tier
},

Armed robbery (robaped.lua) adds a few control fields:

FieldDefaultPurpose
RunawayChance10Per-second % a held-up ped breaks free if you stop aiming
Key38Control input that commits the rob (38 = E)
IgnoreWeaponstableWeapons/groups that won't trigger the hold-up (unarmed, fire extinguisher, …)
IgnoreModelstablePed models the option won't attach to (cops, medics, gang bosses, military)

Vehicle Crimes

Vehicle crimes (catalytic, tiretheft, wheelloose, tireslash, brakecut, fuelsabotage, brickgas, smashgrab) follow the same skeleton with a few extras:

FieldPurpose
CooldownPer-plate or per-coords cooldown (minutes) before the same target can be hit again
IgnoreClassesVehicle classes the option refuses (bikes, boats, aircraft, emergency)
Locations / VehicleModelsFor spawn-based crimes (e.g. smash & grab parks cars at fixed points)
Multi-stage timesSome crimes have a scope/setup stage and a commit stage with separate durations

ATM skimming (atmskimmer.lua) is a two-phase, delayed-payoff crime:

lua
HarvestDelay = 30,            -- Minutes between installing a skimmer and the earliest harvest.
MaxPerPlayer = 5,             -- Max simultaneously-installed skimmers per character.
InstallItem  = 'skimmer',     -- Consumed at install.
HarvestItem  = 'card_data',   -- Received at harvest.

Parcel theft (parceltheft.lua) defines porch spawn Locations — append a row and it's picked up on next restart:

lua
{ coords = vector3(123.45, 678.90, 12.34), heading = 90.0, distance = 25.0, prop = 'hei_prop_heist_box' },

Minigames

TIP

For the complete reference — every minigame's parameters with defaults, how to swap them per crime, and using external minigame resources — see the dedicated Minigames page.

Every crime runs a skill-check before it commits. The minigame lives in a Minigame block in the crime's config:

lua
Minigame = {
    Enable = true,                       -- false = skip the minigame (auto-success).
    Start = function()
        -- Calls a built-in minigame and returns its pass/fail boolean.
        return require('client.minigame').mash({
            fillPerTap   = 0.08, -- bar gained per E tap (0..1)
            decayPerSec  = 0.4,  -- bar lost per second (0..1)
            timeLimitSec = 6,    -- seconds to fill it; 0 = no limit
        }).success
    end,
}

Start can be any function that returns true/false — swap the built-in for a different one, change its tuning, or drop in ps-ui / lib.skillCheck / your own NUI minigame without touching the crime's code.

Built-In Library

Call any of these via require('client.minigame').<name>({ ...params }):

MinigameWhat it isKey params
pickpocketMoving marker over item "safe zones"; grab eachgreenArcDeg, rotationSpeedDegSec, speedUpMultiplier, mysteryChance, timeLimitSec
lockpickRotating ring; press as the orb crosses each nodenodeCount, hitWindowDeg, rotationSpeedDegSec, speedUpMultiplier, timeLimitSec
lockpickbarBar variant of lockpicksame as lockpick
holdsteadyHold E to keep a marker inside a drifting bandbandSize, holdDurationSec, gravity, thrust, timeLimitSec
mashMash E to fill a bar before it decaysfillPerTap, decayPerSec, timeLimitSec
stealthPress E only while the NPC looks awaysteps, safeDurationSec, watchDurationSec, jitterSec, timeLimitSec
dialPress E at each notch of a sweeping pointernotches, hitWindowDeg, rotationSpeedDegSec, timeLimitSec
wiresCut the correct coloured wire(s)wireCount, cutsNeeded, timeLimitSec
sequenceGrowing Simon — repeat the lengthening patternpadCount, startLength, maxLength, growBy, timeLimitSec
reactionHit key prompts in a row, window tighteningrounds, startWindowSec, windowShrink
rhythmHit falling notes on the hit line (A S D F)lanes, noteCount, fallSec, maxMisses, hitWindow
trackingKeep the cursor on a wandering targetcatchRadius, holdDurationSec, targetSpeed, timeLimitSec
tumblerSet each pin as its marker crosses the sweet spotpins, bandSize, speedSec, timeLimitSec
traceTrace a path without straying off ittolerance, segments, wiggle, timeLimitSec
codeType the shown code in timelength, timeLimitSec
safedialRotate a safe dial onto each mark in sequencenumbers, steps, toleranceDeg, timeLimitSec
tuningDrag sliders onto their target markssliders, tolerance, timeLimitSec
spotFind the matching icon in a gridgridCount, rounds, timeLimitSec
whackClick targets before they expirehits, maxMisses, targetLifeSec, spawnEverySec
gaugeHold to fill, release inside the green bandrounds, bandSize, fillSpeed, timeLimitSec
pipesRotate pipe tiles to connect the flowcols, rows, timeLimitSec

Test any minigame

With the command.pettycrime_admin ACE, run /testminigame <name> to preview any minigame, /testminigame all to cycle through every one, or /testminigame with no argument to list them.

Police Alerts

Every crime has a PoliceAlert block. The default Send is a console print — replace it with your dispatch system.

lua
PoliceAlert = {
    Enable = true,
    Chance = 25, -- 0-100 chance an alert fires on success.
    Send = function()
        local data = exports['cd_dispatch']:GetPlayerInfo()
        TriggerServerEvent('cd_dispatch:AddNotification', {
            job_table = { 'police' },
            coords = data.coords,
            title = '10-15 - Mail Theft',
            message = ('A %s tampering with a mailbox at %s'):format(data.sex, data.street),
            -- ...your dispatch fields...
        })
    end,
}

Localisation

locales/en.json ships by default. To add a language:

  1. Copy locales/en.json to locales/<code>.json (e.g. de.json).
  2. Translate the values, keep the keys.
  3. Set Locale = '<code>' in configs/config.lua.

Missing keys fall back to the raw key name in the UI, so untranslated entries are immediately visible.

Admin Panel

Players with the command.pettycrime_admin ACE can run /pettycrimeadmin (alias /pcadmin) to open the React NUI admin panel.

TabWhat it does
PlayersPaged, searchable list of every record. Inspect per-crime XP/level/attempts/cash/items; adjust XP inline; reset a single crime or wipe a record
HistoryAppend-only ledger of every shop buy/sell, crime success (one row per item awarded), and admin override
ConfigurationEvery value in configs/*.lua as an editable field. Save persists an override; reset reverts to the file default

How overrides work

Saved edits are stored in sd_pettycrime_admin as override rows. At boot, the admin module patches the live config tables before the crime modules load, so every consumer sees the merged values. Fields you never touch keep using their config defaults; reset removes the override and restores the boot-time default.

Grant the ACE in server.cfg:

cfg
add_ace group.admin command.pettycrime_admin allow

Full Config Files

Prefer reading the raw files? Every config is mirrored under Full Config Files — the global config.lua plus one page per crime.