Pacific Bank Heist

header_lq

:bank: Pacific Bank Heist

Looking for a unique addition to your server? This multiple stage heist will require your players to carefully plan ahead the heist of the century. With immersive guard interactions, user-interfaces, mini-games, lasers and more, it will truly enhance the player experience. The script is highly configurable, allowing you to add your own twists.

:movie_camera: Detailed Preview: Youtube

:shopping_cart: Get it here: gamzkystore.com

:question: Support: Discord

Key Features include:

  • A hobo, randomly placed around the Pacific Bank has important information about the on-duty guard.
  • Interact with the on-duty guard to obtain information about the employees and the vault.
  • The correct vault code can be found by interacting with computer screens from employees.
  • Dynamic reactions from the hobo and guard, based on your interaction, with voice lines to give a more immersive experience.
  • Synchronized particles and sound effects when using thermite, opening the vault and drilling lootboxes.
  • Interactive and configurable mini-games can be completed before opening a door.
  • A changing laser pattern in the vault introduces an extra obstacle for robbers.
  • Walking through the laser locks the final door, requiring more thermite to open it.
  • An alarm is played after starting the heist, which is disabled when the vault is opened (added after an update, here is an extra preview highlighting this feature: streamable link).

Other features

  • All actions and states are synced between clients, also when a player connects during an active heist.
  • Many configurable options, like required items, interact locations, animations, obtained loot etc (see Client config below).
  • Accessible bridge code, to modify important parts of the script to your own liking is included in the encrypted version (see Bridge Functions below).
  • Loot is configurable in a server-sided config and based on the weapon-tier used during the heist.
  • Possible to adapt the configuration to integrate custom map-edits.
  • Players with the police-job can stop and reset the heist.
  • Optimized to only run code if a player is nearby, and secure against event injections.

Core functions of the script are located in bridge files, which are accessible also in the normal encrypted version and shown in the code snippets below. This includes functions like sending notifications, giving and receiving items or job-checks. This is by default configured to the ESX-framework and is therefore plug and play in case of ESX. For other frameworks, you will have to modify the functions according to your framework, we can support you with this via our Discord.

Accessible code below:

Client Config (server-sided config not shown for security reasons)
Config = {}

-- Change the startlocation of the heist if you whish, this is also the spawn-location of the guard, the heading changes the heading of the guard
Config.Start = { Location = vector3(238.97, 221.24, 105.29), Heading = 271.0 }

-- The minimum cooldown in between starting heists in seconds
Config.Cooldown = 3 * 3600 -- This is equal to 3 hours

-- The required amount of cops before a heist can be started, set to 0 if you want to remove the check
Config.CopsNeeded = 0

-- Below you can configure the blip shown at the bank. You can also choose to disable it. See https://docs.fivem.net/docs/game-references/blips/ for the possible sprites and colors.
Config.Blip = {
    EnableBlip = true,
    Sprite = 642,
    Scale = 1.1,
    Color = 5 -- Yellow
}

-- Below you can change the model and static scenario of both the hobo and guard NPC. Make sure to always use valid ped-models and scenarios if you wish to change these.
-- There are also configuration options to configure the item you can trade with the hobo, and the amount of money required to bribe the guard.
Config.NPCs = {
    ['hobo'] = {
        Model = 's_m_y_dealer_01',                       -- The ped model for the hobo
        StaticScenario = 'WORLD_HUMAN_DRUG_DEALER_HARD', -- The static scenario for the hobo
        Name = 'Hobo',                                   -- The name of the hobo displayed to the players
        Item = 'cigarettes',                             -- The required item-name to interact with the hobo
        Label = 'Cigarettes',                            -- The item label which will be shown to the player
    },
    ['guard'] = {
        Model = 'mp_m_securoguard_01',                       -- The ped model for the guard
        StaticScenario = 'WORLD_HUMAN_SECURITY_SHINE_TORCH', -- The static scenario for the guard
        Name = 'Guard',                                      -- The name of the guard displayed to the players
        BribePrice = 5000,                                   -- If you bribe the guard to give up information, this value is the amount of money required to bribe the guard
    }
}

-- The text displayed to the player when interacting with the hobo is editable below.
Config.HoboInteractionText = {
    [1] = { Reply = 'Do you have plans for this bank? Give me something of value and I will tell you useful information.' },         -- This is the first interaction shown to a player
    [2] = { Reply = 'Give me ' .. Config.NPCs['hobo'].Label .. '. But be wary, don\'t wait to long with your plans if you agree.' }, -- The player can interact again, this time the hobo will let the player know what item he needs
    [3] = { Reply = 'The weak point of the security guard currently working a shift is ~b~%s~s~, don\'t forget!' },                  -- If the player gives the required item to the hobo, he will give valueble information about the guard. This information is inserted at %s
    [4] = { Reply = 'Fuck off, let a man enjoy his life in peace...' }                                                               -- This is the interaction shown to a player if he has the police job
}

-- The displayed text for each interrogation method, do not add or remove any entries, only edit the existing text. Generally only requires editing if you are changing the language or keybinds
Config.InterogationText = {
    ['violence'] = { text = 'Violence', icon = 'fas fa-person-burst', reply = 'Alright, alright... easy! %s has the vault code somewhere.' },               -- Make sure the reply contains %s, it will be replaced by a name
    ['angry'] = { text = 'Intimidate', icon = 'fas fa-face-angry', reply = 'Dont hurt me, please! %s has the vault code somewhere.' },                      -- Make sure the reply contains %s, it will be replaced by a name
    ['caring'] = { text = 'Caring', icon = 'fas fa-hand-holding-hand', reply = 'I just want to go back to my family, please... You have to look for %s.' }, -- Make sure the reply contains %s, it will be replaced by a name
    ['bribe'] = { text = 'Bribe', icon = 'fas fa-hand-holding-dollar', reply = 'It is %s you are looking for, he has the code for the vault.' },            -- Make sure the reply contains %s, it will be replaced by a name
}

-- Configure the required lockpick item, the item label, if the item should be removed when failing the minigame, and the lockpick animation
Config.Lockpick = {
    Item = 'lockpick',
    Label = 'Lockpick',
    RemoveItemOnFail = true,
    AnimationDict = 'mp_arresting',
    AnimationName = 'a_uncuff',
}

-- Configure the required hacking item, the item label, if the item should be removed when failing the minigame, and the hacking animation
Config.Hack = {
    Item = 'hackdevice',
    Label = 'Hacking device',
    RemoveItemOnFail = true,
    AnimationDict = 'mp_prison_break',
    AnimationName = 'hack_loop',
}

-- Configure the required thermite item, the item label and the thermite animation.
-- You can also change the thermite prop and the burn duration before it will open the door
Config.Thermite = {
    Item = 'thermite',
    Label = 'Thermite',
    AnimationDict = 'weapons@projectile@sticky_bomb',
    AnimationName = 'plant_vertical',
    PropName = 'hei_prop_heist_thermite_flash',
    Duration = 15000, -- The time it takes for the thermite to burn in milliseconds
}

-- All the configurable options for opening the vault door
Config.MainVault = {
    PincodeAttempts = 2,                          -- The amount of attempts you will have in a single heist, to enter the correct vault code. If all attempts are used, it is impossible to open the vault and the heist has failed
    AnimationDict = 'mp_prison_break',            -- The animation dict when entering a pincode
    AnimationName = 'hack_loop',                  -- The animation name when entering a pincode
    Location = vector3(252.92, 228.53, 102.12),   -- The location where a pincode can be entered (Might only need changing when using a map edit)
    Heading = 75.0,                               -- The heading of the player when entering the pincode (Might only need changing when using a map edit)
    ObjLocation = vector3(254.29, 224.0, 101.88), -- The object location of the vault door (Might only need changing when using a map edit)
    ObjClosedHeading = 160.0,                     -- The object heading of the vault door, when in closed position (Might only need changing when using a map edit)
    ObjOpenHeading = 0.0,                         -- The object heading of the vault door, when in open position (Might only need changing when using a map edit)
    ModelHash = `v_ilev_bk_vaultdoor`,            -- The model hash of the vault door (Might only need changing when using a map edit)
}

-- All the configurable options opening loot boxes
Config.Drill = {
    Item = 'drill',                                                -- The required item
    Label = 'Drill',                                               -- The required item label shown to the player
    AnimationDict = 'anim@heists@fleeca_bank@drilling',            -- The animation dict when drilling
    AnimationName = 'drill_straight_idle',                         -- The animation name when drilling
    PropName = 'hei_prop_heist_drill',                             -- The prop used for the drill
    Duration = 6000,                                               -- The time in milliseconds it takes to drill open a box
    CancelKey = 73,                                                -- The key which can be used to cancel drilling (see https://docs.fivem.net/docs/game-references/controls/ if you are changing this)
    DisabledKeys = { 22, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36 }, -- The keys which are temporarily disabled when drilling (see https://docs.fivem.net/docs/game-references/controls/ if you are changing this)
    EnableDrillSound = true,                                       -- Indicate if you want the drill to make a sound when drilling
}

Config.LootBox = {
    AnimationDict = 'random@mugging4',                             -- The animation dict when looting a box
    AnimationName = 'struggle_loop_b_thief',                       -- The animation name when looting a box
    Duration = 3000,                                               -- The time in milliseconds it takes to loot a box
    CancelKey = 73,                                                -- The key which can be used to cancel looting (see https://docs.fivem.net/docs/game-references/controls/ if you are changing this)
    DisabledKeys = { 22, 23, 24, 25, 30, 31, 32, 33, 34, 35, 36 }, -- The keys which are temporarily disabled when looting (see https://docs.fivem.net/docs/game-references/controls/ if you are changing this)
}

-- If you wish to change any displayed text or messages, edit the corresponding field below.
Config.Locales = {
    ['blip_label'] = 'Pacific Bank',
    ['hobo_denied'] = 'You don\'t have what I am looking for, piss off.',
    ['guard_bribed'] = 'You bribed the guard with ~b~€%d~s~.', -- Leave the %d in there, it displays amount of bribe money.
    -- More lines but excluded to minimize lines
}

-- Weapons that can be used to start the heist, you can choose the type of loot-table for each weapon. So stronger, more valuable weapons will yield more loot. The loot tables are defined in server/sv_config.lua/SVConfig.Loot
-- If a weapon is not present in the list below, it cannot be used to start the heist.
Config.Weapons = {
    ['WEAPON_SNSPISTOL'] = 'lightweapon',
    ['WEAPON_PISTOL'] = 'mediumweapon',
    ['WEAPON_MACHINEPISTOL'] = 'heavyweapon',
    -- More lines but excluded to minimize lines
}

-- The locations where a player can interact with a computer, and the user-name for that computer. There is no need to make changes here, unless you are using a map-edit with different locations for computers.
-- Feel free to change the names on the other hand if you wish
Config.Computers = {
    { Location = vector3(250.2, 227.41, 106.29),  Name = 'Davy Jackson',    Code = '' },
    { Location = vector3(248.99, 228.51, 106.29), Name = 'Olaf Borman',     Code = '' },
    { Location = vector3(245.52, 229.37, 106.29), Name = 'Marnix Okars',    Code = '' },
    -- More lines but excluded to minimize lines
}

-- The time interval at which the lasers change pattern.
Config.LaserInterval = 2500; -- In milliseconds, you should not make it lower then 250 ms.

-- The coordinates which are used to determine if the player is close enough to the lasers to draw them on the screen for optimization purposes. No need to change these unless you are changing laser locations.
Config.LaserDistanceCheckCoords = vector3(255.24, 217.26, 101.68)

-- The laser will start with pattern 1, after Config.LaserInterval [ms] it will move to patter 2 etc. This might only need changing if you are using a mapedit, or if you know what you are doing, otherwise you should leave this as it is.
Config.Lasers = {
    -- More lines but excluded to minimize lines
}

-- The doors in the pacific bank will be locked by default. You only need to make changes here if you are using a mapedit with different door models and positions, otherwise you should probably leave this alone.
Config.Doors = {
    ['laserDoor'] = {
        Location = vector3(261.54, 215.18, 101.68),
        InteractCoords = vector3(-1.195, -0.06, -0.02),
        ClosedDoorHeading = 250.0,
        Name = `hei_v_ilev_bk_safegate_pris`,
        OpeningMethod = 'thermite',
        ObjRotation = vector3(90.0, 90.0, -110.0)
    },
    -- More lines but excluded to minimize lines
}

-- The location where mainDoor2 (see Config.Doors) can be hacked. You should leave the location and heading alone unless using a map-edit.
-- You can change the amount of hack minigames the player needs to complete, in order to open the door.
Config.DoorHack = {
    Location = vector3(262.36, 223.02, 106.54),
    Heading = 250.0,
    Door = 'mainDoor2',
    AmountOfHackGames = 6
}

Client Bridge Functions
local ESX = exports['es_extended']:getSharedObject()
-- local QBCore = exports['qb-core']:GetCoreObject()

local playerJob = 'unemployed'
RegisterNetEvent('esx:setJob', function(job, lastJob)
    playerJob = job.name
end)

Functions = {}

-- A general notification using ESX. Change to your own framework.
Functions.Notify = function(message)
    ESX.ShowNotification(message)
end

-- An advanced notification using ESX. Change to your own framework.
Functions.AdvancedNotify = function(message, name)
    ESX.ShowAdvancedNotification(name, '', message, 'CHAR_BLANK_ENTRY', 0)

    -- !! If you wish for a standard notification, or an advanced notification is not available in your framework, remove the line above and uncomment the line below
    -- Functions.Notify(message)
end

-- Check if the player has a police job using ESX. Change to your own framework.
Functions.IsPlayerPolice = function()
    if (playerJob == 'police') then
        return true
    end

    return false
end

-- Checks if the player can interact with the rest of the script
Functions.CanInteract = function()
    local playerPed = PlayerPedId()

    if IsPedDeadOrDying(playerPed, true) then
        return false
    end

    if IsPedInAnyVehicle(playerPed, false) then
        return false
    end

    return true
end

-- This function is called for lockpicking doors, feel free to make changes to your liking but make sure either true or false is returned by the function (indicating a success or fail)
Functions.LockPickDoor = function(AmountOfLockpickGames)
    local instantlyRemoveItem = false;
    local hasItem = lib.callback.await('gs_pacificbank:useItem', false, Config.Lockpick.Item, instantlyRemoveItem)
    local success = false

    -- If the player has the correct item, a mini-game is started to unlock the door
    if (hasItem) then
        -- You can increase or decrease the area size in which the user has the press E to change difficulty
        local areaSize = 40

        -- You can increase or decrease the speed at which the user has the press E to change difficulty
        local speedMultiplier = 1.5

        -- Lockpicking requires AmountOfLockpickGames correct skillchecks, based on the configuration in Config.Doors.AmountOfLockpickGames
        for i = 1, AmountOfLockpickGames do
            success = lib.skillCheck({ areaSize = areaSize, speedMultiplier = speedMultiplier }, { 'E' })
            if not success then
                break
            end

            -- Decrease the size as and increase the speed as the player nears the end
            areaSize -= 2
            speedMultiplier += 0.05
            Wait(300)
        end

        -- If the player failed, remove the item and notify
        if (not success) then
            instantlyRemoveItem = Config.Lockpick.RemoveItemOnFail
            lib.callback.await('gs_pacificbank:useItem', false, Config.Lockpick.Item, instantlyRemoveItem)
            Functions.Notify((Config.Locales['door_open_fail']):format(Config.Lockpick.Label))
        else
            Functions.Notify(Config.Locales['door_open_succes'])
        end
    else
        Functions.Notify((Config.Locales['door_open_missing_item']):format(Config.Lockpick.Label))
    end

    -- Ensure true is returned if the lockpick was succesfull and false otherwise
    if success then
        return true
    else
        return false
    end
end

-- This function is called for hacking the main door, feel free to make changes to your liking but make sure either true or false is returned by the function (indicating a success or fail)
Functions.HackDoor = function()
    local instantlyRemoveItem = false;
    local hasItem = lib.callback.await('gs_pacificbank:useItem', false, Config.Hack.Item, instantlyRemoveItem)
    local success = false

    -- If the player has the correct item, a mini-game is started to unlock the door
    if (hasItem) then
        -- You can increase or decrease the area size in which the user has the press E to change difficulty
        local areaSize = 40

        -- You can increase or decrease the speed at which the user has the press E to change difficulty
        local speedMultiplier = 1.5

        -- Hacking requires Config.DoorHack.AmountOfHackGames correct skillchecks
        for i = 1, Config.DoorHack.AmountOfHackGames do
            success = lib.skillCheck({ areaSize = areaSize, speedMultiplier = speedMultiplier }, { 'E' })
            if not success then
                break
            end

            -- Decrease the size as and increase the speed as the player nears the end
            areaSize -= 2
            speedMultiplier += 0.05
            Wait(300)
        end

        -- If the player failed, remove the item and notify
        if (not success) then
            instantlyRemoveItem = Config.Hack.RemoveItemOnFail
            lib.callback.await('gs_pacificbank:useItem', false, Config.Hack.Item, instantlyRemoveItem)
            Functions.Notify((Config.Locales['door_open_fail']):format(Config.Hack.Label))
        else
            Functions.Notify(Config.Locales['door_open_succes'])
        end
    else
        Functions.Notify((Config.Locales['door_open_missing_item']):format(Config.Hack.Label))
    end

    -- Ensure true is returned if the hack was succesfull and false otherwise
    if success then
        return true
    else
        return false
    end
end

-- The function which is called before placing thermite on a door. Feel free to edit a mini-game if you want, but I didnt think it was suitable here. The thermite prop and the effects are taken care of in the script itself.
-- If you make any chances, again ensure to either return true or false (indicating a success or fail)
Functions.ThermiteDoor = function()
    local instantlyRemoveItem = true;
    local usedItem = lib.callback.await('gs_pacificbank:useItem', false, Config.Thermite.Item, instantlyRemoveItem)
    if (usedItem) then
        Functions.Notify((Config.Locales['thermite_placed']):format(Config.Thermite.Label))
        return true
    else
        Functions.Notify((Config.Locales['no_thermite']):format(Config.Thermite.Label))
        return false
    end
end

-- If you want to execute any code before looting a box, do it here
Functions.LootBox = function(location, heading)
    local success = true
    local boxCoordinates = location
    local boxHeading = heading

    return success
end

Server Bridge Functions
local ESX = exports['es_extended']:getSharedObject()
-- local QBCore = exports['qb-core']:GetCoreObject()

Functions = {}

-- This function checks if the player has a specific item, and also removes the item based on the boolean removeItem
-- Make sure either true or false is returned by the function (indicating a success or fail)
Functions.HasItem = function(source, itemName, amount, removeItem)
    local xPlayer = ESX.GetPlayerFromId(source)
    local item = xPlayer.getInventoryItem(itemName)
    if (item == nil) then
        print('gs_pacificbank: [ERROR] The item (' .. itemName .. ') which is configured does not exist.')
        return false
    end

    -- The player does not have the required amount
    if (item.count < amount) then
        return false
    end

    -- Ensure the item is actually removed if the boolean removeItem is set to true
    if (removeItem) then
        xPlayer.removeInventoryItem(itemName, amount)
    end

    -- Indicate a success by returning true
    return true
end

-- This function should return the amount of cops currently active
Functions.GetActivePoliceCount = function(source)
    return ESX.GetNumPlayers('job', 'police')
end

-- Used to pay the guard bribe, you can rewrite it to either use a cash item or bank account money
-- Make sure either true or false is returned by the function (indicating a success or fail)
Functions.RemoveMoney = function(source, amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    if (xPlayer.getMoney() >= amount) then
        xPlayer.removeMoney(amount)
        return true
    else
        return false
    end
end

-- Function to check if the source is a police officer
-- Make sure either true or false is returned by the function
Functions.IsPlayerPoliceServer = function(source)
    local xPlayer = ESX.GetPlayerFromId(source)
    if (xPlayer.job.name == 'police') then
        return true
    else
        return false
    end
end

-- Function to give the player loot after a box has been looted.
-- Safety checks and anti-cheat is already encorporated in the script itself. This event can only be triggered if the heist is active, if the box is opened by a drill and if the box still has loot inside it.
Functions.GivePlayerBoxLoot = function(source, itemName, amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    local item = xPlayer.getInventoryItem(itemName)
    if (item == nil) then
        print('gs_pacificbank: [ERROR] The item (' .. itemName .. ') which is configured does not exist.')
        return false
    end

    if (not xPlayer.canCarryItem(itemName, amount)) then
        return false
    end

    xPlayer.addInventoryItem(itemName, amount)

    -- This triggers the LogAction function below
    local data = {}
    data.item = itemName
    data.amount = amount
    Functions.LogAction(source, 'loot', data)

    return true, item.label
end

Functions.OnHeistStart = function(source, pinCode, guardMethod)
    -- Add any code you want executed on the heist start, like a message to available police units
    -- If you need the location of the heist, use the variable Config.Start.Location
    -- pinCode is the correct pinCode to open the vault if you wish to share this with the police
    print(('gs:pacificbank: The correct pin-code for this heist is %s and the guard\'s weakness is %s'):format(pinCode, guardMethod))
end

Functions.OnHeistStop = function(source)
    -- Add any code you want executed after a police officer stops the heist, like a message to other police units for instance
end

Functions.OnLaserHit = function(source)
    -- Add any extra code you want executed if a player walks through the lasers
end

-- You can log actions if you whish. The function below is triggered when starting the heist, when ending the heist and when a player loots a box
Functions.LogAction = function(source, action, data)
    local steamname = GetPlayerName(source)

    if (action == 'start') then
        -- The source has started the heist
    elseif (action == 'stop') then
        -- The source has stopped the heist
    elseif (action == 'loot') then
        local item = data.item
        local amount = data.amount
        -- The source has obtained loot from the heist
    end
end

Code is accessible No, but core functions are accessible
Subscription-based No
Lines (approximately) ~3000
Requirements ox_lib, ox_target
Support Yes
1 Like

Small update: The function where the ox-target interactions are defined, is moved to the client bridge file and can now be modified to any other target-script.

I just released a small update, a configurable alarm (also possible to disable entirely) can now be heard after starting the heist.

Here is a preview of the new feature: Watch gs_pacificbank_ alarm | Streamable