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

# Open Source

> Open source files available for customization in the TStudio Ammunation Robbery resource

Most of the core logic in this resource is handled by the bridge. The open source files are provided so you can customize minigames, add hooks for custom systems, and extend the script without touching escrow.

## opensource/client/client.lua

This file handles zone creation, drawtext, evidence, stress, and most importantly, the minigame callback. If you want to change the minigames used for each step, this is the only file you need to edit.

### Minigame Callback

Each robbery step triggers the `projectx-ammunationrobbery-tstudio:client:Minigames` callback with the step name. You can swap any minigame by replacing the export call inside the matching `elseif` block.

```lua theme={null}
lib.callback.register('projectx-ammunationrobbery-tstudio:client:Minigames', function(Step)
    local p = promise.new()
    if Step == "Fusebox" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:WaveMatch(1, {duration = 30000})
        p:resolve(success)
    elseif Step == "Register" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:KeySpam(3, 60)
        p:resolve(success)
    elseif Step == "Computer" then
        if not exports['projectx-bridge']:MinigameCheck("SN-Hacking") then p:resolve(false) return Citizen.Await(p) end
        local success = exports['SN-Hacking']:MemoryCards('medium')
        p:resolve(success)
    elseif Step == "GunCrate" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:RapidLines(2, 50, 4)
        p:resolve(success)
    elseif Step == "ExplosivesCrate" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:MineSweeper(3, {grid = 7, duration = 10000, target = 10, previewDuration = 2000})
        p:resolve(success)
    elseif Step == "ArmorCrate" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:CircleProgress(3, 75)
        p:resolve(success)
    elseif Step == "Gun" then
        local success = lib.skillCheck({'easy', 'easy', 'medium', 'hard'}, {'w', 'a', 's', 'd'})
        p:resolve(success)
    elseif Step == "Ammo" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:KeySpam(2, 50)
        p:resolve(success)
    elseif Step == "ElectricBox1" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:KeySpam(3, 50)
        p:resolve(success)
    elseif Step == "ElectricBox2" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:PathFind(1, {numberOfNodes = 10, duration = 7500})
        p:resolve(success)
    elseif Step == "Fingerprint" then
        if not exports['projectx-bridge']:MinigameCheck("bl_ui") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["bl_ui"]:PrintLock(1, {grid = 5, duration = 15000, target = 5 })
        p:resolve(success)
    elseif Step == "FingerprintKeypad1" then
        if not exports['projectx-bridge']:MinigameCheck("SN-Hacking") then p:resolve(false) return Citizen.Await(p) end
        local success = exports['SN-Hacking']:Thermite(7, 5, 10000, 1, 2, 3000)
        p:resolve(success)
    elseif Step == "FingerprintKeypad2" then
        if not exports['projectx-bridge']:MinigameCheck("SN-Hacking") then p:resolve(false) return Citizen.Await(p) end
        local success = exports['SN-Hacking']:Thermite(7, 5, 10000, 1, 2, 3000)
        p:resolve(success)
    elseif Step == "Safe" then
        if not exports['projectx-bridge']:MinigameCheck("pure-minigames") then p:resolve(false) return Citizen.Await(p) end
        local gameData = {totalNumbers = 20, seconds = 20, timesToChangeNumbers = 4, amountOfGames = 1, incrementByAmount = 5}
        local success = exports['pure-minigames']:numberCounter(gameData)
        p:resolve(success)
    elseif Step == "MiniSafe" then
        if not exports['projectx-bridge']:MinigameCheck("pd-safe") then p:resolve(false) return Citizen.Await(p) end
        local success = exports["pd-safe"]:createSafe({math.random(0,99)})
        p:resolve(success)
    elseif Step == "Keypad" then
        if not exports['projectx-bridge']:MinigameCheck("pure-minigames") then p:resolve(false) return Citizen.Await(p) end
        local gameData = {totalNumbers = 20, seconds = 20, timesToChangeNumbers = 4, amountOfGames = 1, incrementByAmount = 5}
        local success = exports['pure-minigames']:numberCounter(gameData)
        p:resolve(success)
    end
    return Citizen.Await(p)
end)
```

<Info>
  You can return `true` unconditionally inside any step block to skip that minigame entirely.
</Info>

The default minigames per step are:

| Step                 | Minigame Resource | Function         |
| -------------------- | ----------------- | ---------------- |
| `Fusebox`            | `bl_ui`           | `WaveMatch`      |
| `Register`           | `bl_ui`           | `KeySpam`        |
| `Computer`           | `SN-Hacking`      | `MemoryCards`    |
| `GunCrate`           | `bl_ui`           | `RapidLines`     |
| `ExplosivesCrate`    | `bl_ui`           | `MineSweeper`    |
| `ArmorCrate`         | `bl_ui`           | `CircleProgress` |
| `Gun`                | `ox_lib`          | `skillCheck`     |
| `Ammo`               | `bl_ui`           | `KeySpam`        |
| `ElectricBox1`       | `bl_ui`           | `KeySpam`        |
| `ElectricBox2`       | `bl_ui`           | `PathFind`       |
| `Fingerprint`        | `bl_ui`           | `PrintLock`      |
| `FingerprintKeypad1` | `SN-Hacking`      | `Thermite`       |
| `FingerprintKeypad2` | `SN-Hacking`      | `Thermite`       |
| `Safe`               | `pure-minigames`  | `numberCounter`  |
| `MiniSafe`           | `pd-safe`         | `createSafe`     |
| `Keypad`             | `pure-minigames`  | `numberCounter`  |

***

## opensource/server/server.lua

This file handles dispatch, item/money functions, logging, and exposes several hooks you can use to run custom logic at specific points in the robbery.

### Hooks

<AccordionGroup>
  <Accordion title="SuccessfulStep(source, location, step)" icon="circle-check">
    Called whenever a player successfully completes a robbery step.

    ```lua theme={null}
    function SuccessfulStep(source, location, step)
        if step == "Fusebox" then
            -- exports['projectx-bridge']:AddExperience(source, 'criminal', 10)
        end
    end
    ```
  </Accordion>

  <Accordion title="FailedStep(source, location, step)" icon="circle-xmark">
    Called whenever a player fails a robbery step.
  </Accordion>

  <Accordion title="InitialCheck(src, Location, Interaction)" icon="shield-check">
    Called before a player can start a robbery at a location. Return `false` to block them.
  </Accordion>

  <Accordion title="RobberyResetHook(location)" icon="rotate">
    Called whenever a location resets.
  </Accordion>
</AccordionGroup>
