FearlessStudios

Create a Vault

Step-by-step guide to adding a custom vault to FS-Robberies.

Overview

This guide shows how to add a new vault using Config.CustomLocationRobberies and Config.CustomVaults in config.lua. The vault door interaction and the managed door prop must share the same id.

Interior and Door Limits

Some interiors may not work properly with vaults, and vaults currently only support swinging vault doors. Other door types are not supported at this time. The Paleto Bay default bank also has issues where the door becomes invisible at certain angles, which appears to be a limitation of spawning a custom door in an MLO.

Step 1: Enable admin tools

  1. Set Config.PolyBuilderPermission and Config.FindVaultPermission in config.lua.
  2. Grant those ACE permissions to your admin group so you can use /fs_poly_start and /fs_find_closest_vault_door_coord.

Step 2: Capture door data

  1. Stand at the vault door in-game.
  2. Run /fs_find_closest_vault_door_coord and read the printed model, coords, and heading in your F8 console.
  3. If no door is found, add your door model to Config.VAULT_MODELS in config.lua and try again.

Step 3: Add the vault door interaction

Add a new entry to Config.CustomLocationRobberies:

Config.CustomLocationRobberies = {
  {
    id = "example_vault_door",
    isVaultDoor = true,
    coords = vec3(123.4567, 456.7890, 21.1234),
    heading = 180.0,
    interactDist = 1.6,
    label = "Example Vault",
    markerZ = 0.55,
    timerMin = Config.Robbery.Vault.Timer.Min,
    timerMax = Config.Robbery.Vault.Timer.Max,
    amountMin = 0,
    amountMax = 0,
    cooldown = Config.Robbery.Vault.Cooldown,
  },
}

Note: amountMin and amountMax must stay 0 for the vault door because opening the door does not grant loot.

Step 4: Define the vault door

Create a matching entry in Config.CustomVaults. The key must be the same as the location id:

Config.CustomVaults = {
  example_vault_door = {
    hideOriginal = true,
    model = `v_ilev_bk_vaultdoor`,
    spawnPos = vec3(123.4567, 456.7890, 21.1234),
    closedHeading = 180.0,
    openHeading = 50.0,
    durationToOpen = 5,

    insideSphere = { center = vec3(125.0000, 458.0000, 21.2000), radius = 6.5 },
    autoClose = { idleSeconds = 25 },

    insideRobberies = {
      {
        id = "example_deposit_box_1",
        coords = vec3(126.0000, 459.0000, 21.2000),
        heading = 90.0,
        label = "Deposit Box 1",
        amountMin = Config.Robbery.Vault.Rewards.MinMoney,
        amountMax = Config.Robbery.Vault.Rewards.MaxMoney,
      },
    },
  },
}

Notes:

  • openHeading is an absolute heading. Values will be wrapped to 0-360 when applied, but it is best to keep them in range for clarity.
  • closedHeading follows the same rule as openHeading.
  • insideSphere is the fastest way to get a working vault. Use insidePoly instead for exact interior shapes.
  • insideRobberies entries must have unique id values.

Step 5: (Optional) Use a polygon inside zone

If your vault interior is not circular or you want a tighter fit than a sphere, use a polygon to define the inside zone. The polygon is a 2D outline (top-down) with minZ and maxZ defining the vertical range.

When to use insidePoly

  • Your vault is rectangular or irregular.
  • You need the trigger area to follow walls/doors closely.
  • A sphere would include areas outside the vault.
  1. Run /fs_poly_start example_vault_inside.
  2. Left click to add points around the interior.
  3. Use Z to set minZ and X to set maxZ.
  4. Press ENTER to finish and copy the printed Lua table.
  5. Paste the insidePoly block into your vault definition, replacing insideSphere.

Min/Max Z Tuning

The generated minZ and maxZ may need manual tuning to fit your vault interior.

Hysteresis

hysteresis is a buffer (in meters) from the boundary required to flip inside/outside, which prevents rapid toggling when players stand on the edge.

Example output from the poly builder:

insidePoly = {
  points = {
    vec3(123.1000, 456.2000, 21.10),
    vec3(124.5000, 456.2500, 21.10),
    vec3(124.4500, 457.8000, 21.10),
    vec3(123.0500, 457.7500, 21.10),
  },
  minZ = 21.10,
  maxZ = 24.10,
  hysteresis = 0.75,
},

Add insidePoly to your vault config

Replace the insideSphere line in your vault with the insidePoly block you generated:

Config.CustomVaults = {
  example_vault_door = {
    hideOriginal = true,
    model = `v_ilev_bk_vaultdoor`,
    spawnPos = vec3(123.4567, 456.7890, 21.1234),
    closedHeading = 180.0,
    openHeading = 50.0,
    durationToOpen = 5,

    insidePoly = {
      points = {
        vec3(123.1000, 456.2000, 21.10),
        vec3(124.5000, 456.2500, 21.10),
        vec3(124.4500, 457.8000, 21.10),
        vec3(123.0500, 457.7500, 21.10),
      },
      minZ = 21.10,
      maxZ = 24.10,
      hysteresis = 0.75,
    },
    autoClose = { idleSeconds = 25 },

    insideRobberies = {
      {
        id = "example_deposit_box_1",
        coords = vec3(126.0000, 459.0000, 21.2000),
        heading = 90.0,
        label = "Deposit Box 1",
        amountMin = Config.Robbery.Vault.Rewards.MinMoney,
        amountMax = Config.Robbery.Vault.Rewards.MaxMoney,
      },
    },
  },
}

Step 6: Test and debug

  1. Set Config.DebugDrawZones = true to visualize inside zones.
  2. Use /openvault to open the nearest vault and verify inside loot points.
  3. Use /closevault to reset it.

Replacing a default vault

If you are replacing a default vault door, set the matching id to false in Config.EnableDefaultVaults and add your custom entries with the same id so you do not get duplicate vault doors.