Skip to main content
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-sandybankrobbery-prompt:client:Minigames callback with the step name. You can swap any minigame by replacing the export call inside the matching elseif block.
lib.callback.register('projectx-sandybankrobbery-prompt:client:Minigames', function(Step)
    local p = promise.new()

    if Step == "BackdoorKeypad" then
        local success = exports["bl_ui"]:PrintLock(1, {grid = 5, duration = 15000, target = 5})
        p:resolve(success)

    elseif Step == "PlantBreachFuse" then
        local success = exports["bl_ui"]:CircleProgress(3, 50)
        p:resolve(success)

    elseif Step == "ManagerKeypad" then
        local gameData = {totalNumbers = 20, seconds = 20, timesToChangeNumbers = 4, amountOfGames = 1, incrementByAmount = 5}
        local success = exports['pure-minigames']:numberCounter(gameData)
        p:resolve(success)

    elseif Step == "OfficeComputer" then
        local success = exports["bl_ui"]:RapidLines(3, 50, 5)
        p:resolve(success)

    elseif Step == "GeneralComputer" then
        local success = exports["bl_ui"]:MineSweeper(3, {grid = 7, duration = 10000, target = 10, previewDuration = 2000})
        p:resolve(success)

    elseif Step == "CodeComputer" then
        local success = exports["bl_ui"]:Untangle(1, {numberOfNodes = 10, duration = 15000})
        p:resolve(success)

    elseif Step == "SecurityDoor" then
        local success = exports["bl_ui"]:KeySpam(2, 50)
        p:resolve(success)

    elseif Step == "BreachServer" then
        local success = exports["bl_ui"]:WaveMatch(1, {duration = 30000})
        p:resolve(success)

    elseif Step == "JamServers" then
        local success = exports["bl_ui"]:KeySpam(2, 50)
        p:resolve(success)

    elseif Step == "LaptopVault" then
        local success = exports["bl_ui"]:RapidLines(3, 50, 5)
        p:resolve(success)

    elseif Step == "SecurityPC" then
        exports['xdecrypto']:StartHack(function(success)
            p:resolve(success)
        end)

    elseif Step == "PickupLaptop" then
        local success = exports["bl_ui"]:CircleProgress(4, 50)
        p:resolve(success)

    elseif Step == "LaptopPutDown" then
        p:resolve(true)

    elseif Step == "LaptopLasers" then
        local success = exports["bl_ui"]:PathFind(1, {numberOfNodes = 10, duration = 7500})
        p:resolve(success)

    elseif Step == "Drill" then
        local success = exports["bl_ui"]:CircleShake(1, 50, 2)
        p:resolve(success)
    end

    return Citizen.Await(p)
end)
You can return true unconditionally inside any step block to skip that minigame entirely. This can be useful for testing or for steps you want to make automatic.
The default minigames per step are:
StepMinigame ResourceFunction
BackdoorKeypadbl_uiPrintLock
PlantBreachFusebl_uiCircleProgress
ManagerKeypadpure-minigamesnumberCounter
OfficeComputerbl_uiRapidLines
GeneralComputerbl_uiMineSweeper
CodeComputerbl_uiUntangle
SecurityDoorbl_uiKeySpam
BreachServerbl_uiWaveMatch
JamServersbl_uiKeySpam
LaptopVaultbl_uiRapidLines
SecurityPCxdecryptoStartHack
PickupLaptopbl_uiCircleProgress
LaptopLasersbl_uiPathFind
Drillbl_uiCircleShake

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

Called whenever a player successfully completes a robbery step. Use this to award XP, trigger events, or log individual step completions.
function SuccessfulStep(source, step)
    if step == "BackdoorKeypad" then
        -- exports['projectx-bridge']:AddExperience(source, 'criminal', 10)
    elseif step == "ManagerKeypad" then
        -- exports['projectx-bridge']:AddExperience(source, 'criminal', 10)
    end
end
All available step names can be found in Config.SandyBankSteps inside config/config.lua.
Called whenever a player fails a robbery step. Use this to remove XP, items, or apply penalties.
function FailedStep(source, step)
    if step == "BackdoorKeypad" then
        -- RemoveItem(source, 'hackcard', 1)
    end
end
Called before a player can start the robbery. Return false to block them.
function InitialCheck(Interaction)
    return true
    -- Add a group check, tablet requirement, or any other gate here
end
Called whenever the heist resets. Use this to update a scoreboard, trigger an external event, or notify players.
function RobberyResetHook()
    -- TriggerEvent('my-scoreboard:update')
end