The Training tab on the dashboard lets players practice cc_minigames games (or any custom minigame you wire up) without rewards or penalties. It’s a separate progression layer — completely independent of contracts, slots, and crew.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.
Three gates
A given minigame card is reachable only when all three are satisfied:- Access — the player paid the one-time
accessPricefee (default5000VICE). Until they pay, the entire Training tab is a single locked card showing the access price. - Level — the entry’s
levelRequired(default1). The card is rendered as a red ring with the required level until the player reaches it; name and buttons stay hidden. - Unlock — the entry’s
price(orincludedFree = truefor the freebies). After access is paid, free entries are auto-unlocked; paid entries need a separate unlock click.
What the default config looks like
Out of the box (shared/training_config.lua):
| Tier | Entries | Cost | Level |
|---|---|---|---|
| Free with access | Drill, Circuit, Sequence, Lockpick | 0 (included) | 1 |
| Tier 1 | Crack, Pattern, Hotkey, Cadence, Memory, Aim, Path, Flick | 1500 each | 2–7 |
| Tier 2 | Verbal, Balance, Masher, Sniffer, Tracer, Untangle, Cut, Outrun, Color Count | 2000 each | 8–14 |
| Tier 3 | Shell | 3000 | 15 |
Server-side flow
Player pays for access
Dashboard →
cc_heistcontracts:trainingUnlock → Training.UnlockAccess(src). Validates VICE balance, debits, sets training_unlocked = 1 in the profile.Free entries auto-unlock at access time
Each entry with
includedFree = true (or price = 0) is added to training_minigames JSON the first time the player tries to start it (via Training.UnlockMinigame, called from the start path).Paid unlock
Click unlock on a card →
cc_heistcontracts:trainingUnlockMinigame → Training.UnlockMinigame(src, id). Re-validates access, level requirement, and balance. Debits the entry’s price. Sets training_minigames[id] = true.Client-side dispatch
When the server authorises a start, the client behaves differently persource:
source = 'cc_minigames'
The minigame runs inside the dashboard’s iframe, not by calling cc_minigames’s Lua exports. The dashboard relays start and result messages over postMessage. This means:
- The dashboard stays open during the practice run.
- NUI focus stays with the dashboard the whole time.
- Cancellation works via the dashboard’s
embedCancelpostMessage path — the iframe sends ESC into the embedded game; no Lua action is needed, andclient/main.luaonly acknowledges thetrainingCancelcallback.
source = 'custom'
The dashboard closes, then your invoke function runs in a CreateThread on the client:
invokeis called inside apcall. Throwing is treated as failure.- Return value
true= pass, anything else = fail. - The result is sent to the dashboard NUI as a
trainingFinishedevent so the dashboard can show pass/fail feedback when the player reopens it. - The dashboard does not auto-reopen after a custom run finishes. The player has to press F6 /
/heistagain.
Failure modes
Training.UnlockAccess errors:
| Code | Cause |
|---|---|
already | Player already has access. |
funds | Insufficient VICE. |
no_profile | Couldn’t resolve the player’s profile. |
Training.UnlockMinigame errors:
| Code | Cause |
|---|---|
no_profile | Couldn’t resolve. |
access_locked | Player hasn’t paid the access fee yet. |
invalid | id doesn’t appear in config.byId. |
level | Player’s level is below levelRequired. |
already | Already unlocked. |
funds | Insufficient VICE for the entry’s price. |
cc_heistcontracts:trainingResult net event with the kind tag (access or minigame) and the id (for minigame errors) so the UI can surface them at the right card.
Adding a new training entry
- Edit
shared/training_config.lua. Append toconfig.minigames. Pick a stableid— don’t reuse it for anything else, and don’t rename it after release (players will lose unlocks because the JSON column references the id). - Decide
source. If it’s already an export oncc_minigames, usesource = 'cc_minigames', minigame = '<ExportName>'. If it’s anything else, usesource = 'custom'with aninvokefunction. - Set
price.0paired withincludedFree = truemakes it free at access-purchase time. - Set
levelRequired(default1). - Restart
cc_heistcontracts. Theconfig.byIdindex rebuilds on require.