> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cc-scripts.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Every config knob across the three shared/*.lua files.

All configuration lives in `shared/`. Three files, all hot-editable — restart the resource to apply.

## `shared/config.lua` — core resource

### Currency

```lua theme={null}
config.currency = {
    name = 'VICE',
    shortName = 'VICE',
    startingBalance = 1000,
}
```

| Field                | Effect                                                                                                  |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| `name` / `shortName` | Display labels for the built-in currency. Shown everywhere VICE is referenced in the dashboard.         |
| `startingBalance`    | VICE deposited into a brand-new profile the first time it's created. Set to `0` to start players broke. |

### Marketplace generation

```lua theme={null}
config.rotation = {
    dripIntervalMs        = 3 * 60 * 1000,    -- 3 min: how often a new listing is added
    maxListings           = 9,                -- generation pauses once we hit this many
    listingExpiryMs       = 60 * 60 * 1000,   -- 60 min: listings older than this get cleared
    expirySweepIntervalMs = 10 * 60 * 1000,   -- 10 min: how often we sweep for expired listings
}
```

The marketplace fills over time instead of re-rolling in batches. Every `dripIntervalMs`, a new listing is added — picked from the registry weighted by each contract's `weight` field. Generation pauses once the marketplace hits `maxListings`. Listings older than `listingExpiryMs` are removed by a background sweep that runs every `expirySweepIntervalMs`.

| Field                   | Effect                                                                                                                       |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `dripIntervalMs`        | How often the marketplace tries to add a new listing. Skipped if we're already at `maxListings`.                             |
| `maxListings`           | Cap on simultaneous listings. Generation resumes when one is bought or expires.                                              |
| `listingExpiryMs`       | A listing is removed if it sits unbought longer than this.                                                                   |
| `expirySweepIntervalMs` | How often the expiry sweep runs. Doesn't need to match `listingExpiryMs` — a listing is at most one sweep interval over-age. |

<Note>
  On a fresh boot the marketplace starts **empty** and the first listing appears after `dripIntervalMs`. There's no initial fill — by design, so a server restart doesn't wipe and re-spawn a fresh catalog at the same time. Lower the interval temporarily if you need listings now for testing.
</Note>

#### Contract rarity (per-contract weight)

Each contract template can declare an optional `weight` field (default `1.0`) controlling how likely it is to be picked when the marketplace adds a new listing. Higher weight = more common, lower = rarer.

```lua theme={null}
exports.cc_heistcontracts:RegisterContract({
    id = 'paleto_bank',
    -- ...
    weight = 0.25,    -- about a quarter as likely as a default-weight contract
}, entryPoint)
```

Weights are relative, not probabilities. If you register three contracts with weights `1.0`, `0.5`, `0.5`, the first is picked 50% of the time and the others 25% each.

The same contract can appear as multiple listings simultaneously — each listing is its own unique instance with its own `listingId`. Buying a Fleeca doesn't stop another Fleeca listing from existing or being bought by someone else.

### Slots

```lua theme={null}
config.slots = { max = 3 }
```

How many contracts a single player can hold at once. Slots are filled by purchase and emptied on start, discard, or transfer-out.

### Invite expiry

```lua theme={null}
config.inviteExpiryMs = 60 * 1000
```

Applies to both contract transfer invites and crew invites. After this elapses, the invite auto-cleans server-side.

### XP / leveling

The XP curve is configurable two ways. Quadratic by default:

```lua theme={null}
config.xp = {
    base = 100,
    exponent = 2,
    -- requirements = nil,
}
```

Cumulative XP needed to reach level N from scratch is `base * (N - 1) ^ exponent`. With the defaults:

| Level | Cumulative XP |
| ----- | ------------- |
| 2     | 100           |
| 3     | 400           |
| 4     | 900           |
| 10    | 8 100         |
| 20    | 36 100        |

For an explicit table, set `requirements`:

```lua theme={null}
config.xp = {
    requirements = { 0, 100, 250, 600, 1200, 2200 },
}
```

Index N is the cumulative XP needed to reach level N. Index 1 must be `0`. Past the end, the last delta repeats — in the example above, level 7 needs `2200 + (2200 - 1200) = 3200`.

The dashboard's level ring is rendered server-side per profile, so curve changes flow through automatically — no client-side curve duplication.

### Debug

```lua theme={null}
config.debug = false
```

Toggles `utils.dprint` (server-side `[DEBUG]` logging). Default is off; flip to `true` when investigating drip/expiry behaviour or entry-point errors locally.

## `shared/marketplace_config.lua` — items + pickups

Two top-level tables: `items` (what's for sale) and `locations` (where the player has to go to claim a checkout).

### Items

```lua theme={null}
M.items = {
    explosives = {
        label = 'Explosives',
        items = {
            { id = 'c4',       name = 'C4',             price = 2500 },
            { id = 'thermite', name = 'Thermite',       price = 1800 },
            ...
        },
    },
    hacking = { ... },
    masks   = { ... },
    vehicles = { ... },
    tools = { ... },
}
```

| Field                             | Notes                                                                                                               |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| top-level key (e.g. `explosives`) | Internal category id; appears in the dashboard URL state but not the UI.                                            |
| `label`                           | Human-readable category header.                                                                                     |
| `items[].id`                      | **Must match your inventory system's item name.** This is what `cc_lib.Inventory.AddItem` is called with on pickup. |
| `items[].name`                    | Display name in the dashboard.                                                                                      |
| `items[].price`                   | VICE cost per unit.                                                                                                 |
| `items[].icon`                    | Optional. Maps to a key in `ui/src/icons.ts`. Falls back to the category icon.                                      |

### Locations

```lua theme={null}
M.locations = {
    { name = 'El Burro Heights',   coords = vector3(1128.21, -2034.86, 32.07), window = 600, radius = 0.6 },
    { name = 'Harmony Warehouse',  coords = vector3(614.71,   2783.87, 43.66), window = 600, radius = 0.6 },
    ...
}
```

| Field    | Notes                                                                                                                                                                       |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`   | Shown to the player and on the GPS waypoint label.                                                                                                                          |
| `coords` | World coords of the door the player has to knock on.                                                                                                                        |
| `window` | Pickup time window in seconds. After this elapses, the pickup is dropped server-side and the player loses the items. The default for shipped locations is 600 (10 minutes). |
| `radius` | ox\_target sphere radius. Defaults to `0.6` if omitted.                                                                                                                     |

On checkout, the server picks one location uniformly at random and starts a timer to drop the pickup if it's not claimed.

<Note>
  Multiple checkouts by the same player while a pickup is still active **merge** into the existing pickup instead of rolling a new location. Item quantities are summed; the original deadline is preserved.
</Note>

## `shared/training_config.lua` — training sandbox

```lua theme={null}
config.accessPrice = 5000
```

VICE fee to unlock the training sandbox at all. Charged once. Set to `0` to make access free.

### Minigames list

Each entry is one practice card in the dashboard's Training tab.

```lua theme={null}
{ id = 'drill',    label = 'Drill',    source = 'cc_minigames', minigame = 'Drill',   price = 0,    includedFree = true },
{ id = 'crack',    label = 'Crack',    source = 'cc_minigames', minigame = 'Crack',   price = 1500, levelRequired = 2 },
{
    id     = 'my_custom',
    label  = 'My Custom Game',
    source = 'custom',
    price  = 3000,
    invoke = function() return exports['my_resource']:Run({ difficulty = 'medium' }) end,
},
```

| Field           | Required                     | Notes                                                                                                                                         |
| --------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | Yes                          | Stable identifier; used as the DB key in `training_minigames` JSON. Don't rename existing ids in production — players will lose unlocks.      |
| `label`         | Yes                          | Display name on the card.                                                                                                                     |
| `source`        | Yes                          | `'cc_minigames'` or `'custom'`.                                                                                                               |
| `minigame`      | If `source = 'cc_minigames'` | Export name on `cc_minigames` (e.g. `'Drill'`, `'Crack'`). The dashboard's iframe loads this game directly.                                   |
| `invoke`        | If `source = 'custom'`       | Callable run on the **client** in the `cc_heistcontracts` resource context after the dashboard closes. Return value (success bool) is logged. |
| `price`         | Yes                          | VICE cost. `0` paired with `includedFree = true` makes it free with the access fee.                                                           |
| `includedFree`  | No                           | If `true`, the entry is auto-unlocked the moment the player pays for access.                                                                  |
| `levelRequired` | No                           | Hides name/buttons and shows a red ring with the required level until the player reaches it. Server enforces too.                             |

A lookup index `config.byId` is built once on require. Don't mutate it directly; rebuild by editing `config.minigames` and restarting.
