STG ScriptsDocumentation
Vanaheim Arms

Configuration

Every option in STG Vanaheim Arms, from stores, economy and blips to restock runs, the shooting range, weapon licenses, ranks, the weapon catalog and license hooks.

Most settings live in stg-vanaheimarms/shared/config.lua. Notifications are in shared/functions.lua, the weapon catalog in shared/weapons.lua and the license hooks in server/editable/license.lua. These files stay editable in the escrow version. Restart the resource after a change.

Language

Config.Locale = 'en'

Available: en, de, fr, es, pt, it, nl, pl, sv, no, fi, ar, he, jp, zh. To change a text, edit the matching file in stg-vanaheimarms/locales/. The license exam questions and the NPC speech lines are in the same files.

Debug

Config.Debug = false

Prints shelf, placement and spawn details to the F8 console. Leave it off on a live server.

Target system

Config.Target = false

Prop

Type

Store doors always use the floating prompt with E, whatever this is set to.

Economy

Config.WithdrawTax = 0.05
Config.EmployeeCut = 0.05
Config.BlockDuplicateWeapon = true
Config.MaxPurchaseAmount = 100
Config.ShowIdleStaff = true

Prop

Type

Customers pay from their bank account and the money goes into the store safe.

Blips

Config.UniformBlipName = true
Config.BlipName = '[Selling] Ammu-Nation'

Config.Blips = {
    unowned = { sprite = 110, color = 1, scale = 0.8 },
    owned   = { sprite = 110, color = 2, scale = 0.8 },
}

Prop

Type

Once an owner renames the store, the blip shows the new name.

Stores

The script ships with 9 stores. The key, for example legion, is how the store is saved in the database, so never rename it once the server is live.

KeyStoreSale priceRange
legionLegion Ammu-Nation$250,000Yes
littleseoulLittle Seoul Ammu-Nation$200,000No
popularstPopular St Ammu-Nation$200,000Yes
sandyshoresSandy Shores Ammu-Nation$200,000No
paletoPaleto Bay Ammu-Nation$200,000No
powerstPower St Ammu-Nation$200,000No
palominoPalomino Fwy Ammu-Nation$200,000No
route68Route 68 Ammu-Nation$200,000No
vespucciVespucci Blvd Ammu-Nation$200,000No

Legion uses every option:

Config.Shops = {
    ['legion'] = {
        label = 'Legion Ammu-Nation',
        salePrice = 250000,
        placeSpawn = vec3(20.1263, -1105.2902, 29.7921),
        inspectSpot = vec4(19.892, -1105.210, 29.527, -319.59),
        license = vec4(23.65, -1108.38, 29.8, 70.38),
        ped = {
            model = 's_m_m_ammucountry',
            coords = vec4(10.23, -1110.9, 29.8, 296.79),
            scenario = 'WORLD_HUMAN_STAND_IMPATIENT',
        },
        doors = {
            { model = -8873588, coords = vec3(18.57, -1115.50, 29.95), group = 'main' },
            { model = 97297972, coords = vec3(16.13, -1114.61, 29.95), group = 'main' },
            { model = 825720073, coords = vec3(14.42, -1106.95, 29.94) },
        },
        range = {
            attendant = { model = 's_m_y_ammucity_01', coords = vec4(8.83, -1099.38, 29.80, 161.07) },
            shootSpot = vec4(13.60, -1097.16, 29.83, 338.83),
            targets = {
                vec4(15.52, -1079.03, 29.80, 160.85),
                vec4(17.03, -1079.61, 29.80, 159.44),
                vec4(21.11, -1081.15, 29.80, 163.93),
                vec4(22.56, -1081.95, 29.80, 161.37),
            },
            door = { model = 452874391, coords = vec3(6.82, -1098.21, 29.95) },
        },
    },
    -- ...
}

Prop

Type

The range block:

Prop

Type

A store with a license spot but no range still runs the shooting drill: the player is moved to the first store that has a range for the drill and brought back after it.

The Popular St range attendant and target positions are estimates. Check them in-game and adjust them if they do not line up.

Speech bubbles

Config.SpeechBubble = {
    enabled = true,
    viewDistance = 12.0,
    triggerDistance = 14.0,
    greetDistance = 8.0,
    duration = 5000,
    cooldown = 20000,
    checkInterval = 5000,
}

Lines float above the store clerk, the range attendant and the license examiner.

Prop

Type

Restock mission

Config.Mission = {
    DailyLimit = 5,
    Pickup = vec4(249.73, -3110.94, 5.79, 3.02),
    Weapons = {
        ['weapon_pistol'] = 1500,
        ['weapon_carbinerifle'] = 16000,
        ['armour'] = 1200,
        ['ammo-9'] = 500,
        -- ...
    },
}

Members with stock permission order from the management menu. The order is paid from the safe, then they collect the cargo at the supplier, load it into a vehicle and carry it to the store clerk. Delivered stock goes to the store warehouse, ready to be placed on a shelf.

Prop

Type

Shooting range

Config.Range = {
    dailyWage = 100,
    entryFee = 500,
    chargeMode = 'ammo',
    pricePerMag = 250,
    pricePerMinute = 300,
    timeLimit = 120,
}

The range opens when the owner hires the attendant. Players shoot with the weapon in their hands and pick Endless, Time Attack or Versus 2P. Every store keeps its own leaderboard, and all fees go into the safe.

Prop

Type

Weapon licenses

Config.License = {
    pedModel = 's_m_y_cop_01',
    dailyWage = 100,
    licenses = {
        {
            key = 'weapon',
            label = 'Weapon License',
            licenseType = 'weapon',
            licenseItem = nil,
            examFee = 2500,
            passScore = 75,
            drillWeapon = 'weapon_pistol',
            drillTarget = 350,
            drillTime = 60,
        },
        {
            key = 'heavy_weapon',
            label = 'Heavy Weapon License',
            licenseType = 'heavy_weapon',
            licenseItem = nil,
            examFee = 5000,
            passScore = 80,
            drillWeapon = 'weapon_carbinerifle',
            drillTarget = 450,
            drillTime = 60,
        },
    },
}

The license desk opens when the owner hires the examiner. Players take the written exam first, then the shooting drill. Passing both grants the license.

Prop

Type

Each license entry:

Prop

Type

Purchase licenses

Config.PurchaseLicense = {
    ['Handguns']        = 'weapon',
    ['Submachine Guns'] = 'weapon',
    ['Shotguns']        = 'heavy_weapon',
    ['Assault Rifles']  = 'heavy_weapon',
    ['Sniper Rifles']   = 'heavy_weapon',
    ['Machine Guns']    = 'heavy_weapon',
    ['Heavy Weapons']   = 'heavy_weapon',
    ['Throwables']      = 'heavy_weapon',
    ['Melee']           = false,
    ['Armor']           = false,
    ['Ammo']            = false,
}

License a player needs to buy from each weapon category. The value must match a licenseType above. Set a category to false to sell it without a license.

Ranks and permissions

Config.DefaultRanks = {
    { name = 'Owner', level = 100, salary = 0, isDefault = true, owner = true,
      permissions = { manage = true, hire = true, manageStock = true, manageMembers = true, withdraw = true, viewFinance = true } },
    { name = 'Manager', level = 50, salary = 1000, isDefault = true, owner = false,
      permissions = { manage = true, hire = true, manageStock = true, manageMembers = true, withdraw = false, viewFinance = true } },
    { name = 'Employee', level = 10, salary = 500, isDefault = true, owner = false,
      permissions = { manage = false, hire = false, manageStock = true, manageMembers = false, withdraw = false, viewFinance = false } },
}

Ranks every store starts with. In the management menu, members with Manage Members can add custom ranks, rename ranks, change salaries and toggle permissions, so these only set the starting point. The starting ranks cannot be deleted, the owner rank always holds every permission and new members join on the lowest rank.

Prop

Type

Config.PermissionList = {
    { key = 'manage', label = 'Manage Business', desc = 'Rename the business and change its settings.' },
    { key = 'hire', label = 'Hire & Fire NPC Staff', desc = 'Open or close the range and license services.' },
    { key = 'manageStock', label = 'Manage Stock', desc = 'Place weapons on shelves, restock and use the warehouse.' },
    { key = 'manageMembers', label = 'Manage Members', desc = 'Invite, remove and change the rank of members.' },
    { key = 'withdraw', label = 'Withdraw Money', desc = 'Withdraw money from the business safe.' },
    { key = 'viewFinance', label = 'View Finances', desc = 'See sales, transactions and business statistics.' },
}

Toggles shown in the rank editor. You can change label and desc, but keep the keys.

Config.PermissionMap = {
    manageStock = 'manageStock',
    deposit = 'manageStock',
    hire = 'hire',
    viewSales = 'viewFinance',
    withdraw = 'withdraw',
    rename = 'manage',
    editShelves = 'manage',
    lockDoors = 'manageStock',
    manage = 'manage',
    manageMembers = 'manageMembers',
    viewFinance = 'viewFinance',
}

Links each action in the store to the permission it needs. For example, deposit and lockDoors need Manage Stock by default. Change the value on the right to move an action to another permission, for example lockDoors = 'manage'.

Config.EmployeeGrades = { ... }
Config.Permissions = { ... }

Older settings that the management menu no longer reads. Leave them as they are.

Business

Config.Business = {
    SellbackRate = 0.6,
    PayrollIntervalMinutes = 60,
}

Prop

Type

Weapon catalog

Every item the store can sell is defined in shared/weapons.lua. Most GTA weapons, the vest and 12 ammo types are already there, so adding a weapon to Config.Mission.Weapons is usually enough.

Config.Weapons = {
    ['weapon_carbinerifle'] = { label = 'Carbine Rifle', category = 'Assault Rifles', prop = 'w_ar_carbinerifle', stats = { damage = 36, fireRate = 74, accuracy = 68, range = 80, clip = 30 } },
    ['armour'] = { label = 'Bulletproof Vest', category = 'Armor', prop = 'prop_armour_pickup', type = 'item' },
    ['ammo-9'] = { label = '9mm', category = 'Ammo', prop = 'v_ret_gc_ammo5', type = 'item', bundle = 50 },
    -- ...
}

Prop

Type

The key is the inventory item the customer receives. The store image is loaded from web/assets/img/weapons/<key>.png for weapons and web/assets/img/items/<key>.png for items.

Notifications

In shared/functions.lua:

Config.Functions.CustomNotify = false

Config.Functions.Notify = function(notifyType, message, title)
    if GetResourceState('stg-hud') == 'started' then
        exports['stg-hud']:Notify({ type = notifyType == 'inform' and 'info' or notifyType, title = title, message = message, duration = 5000 })
        return
    end

    lib.notify({ type = notifyType, description = message })
end
  • CustomNotify: false uses the built-in notifications. true sends every notification through Config.Functions.Notify.
  • Notify: replace the body to use your own notification system. By default it uses stg-hud when it is running, otherwise ox_lib.

License hooks

Config.Functions.OnLicenseReceived in shared/functions.lua runs on the player's screen right after they earn a license. By default it refreshes stg-walletv2 cards when that resource is running. Use it to refresh your own ID card or wallet resource.

Config.Functions.OnLicenseReceived = function(licenseType)
    if GetResourceState('stg-walletv2') == 'started' then
        TriggerEvent('stg-walletv2:cardsRefreshing')
        return
    end
end

server/editable/license.lua connects the store to your license system. Edit it if your server stores licenses somewhere else.

  • GiveLicense(source, licenseType): runs when a player passes the exam and the drill. By default it uses esx_license on ESX and metadata.licences on QBCore and QBOX.
  • HasLicense(source, licenseType): runs before a purchase and before an exam, and must return true or false.
  • CanStartExam(source, data): runs before the written exam and before the drill. data.examType is 'written' or 'drill'. Return false and an optional message to block it, for example to keep the drill for police only.

On ESX, the default HasLicense lets every purchase through when esx_license is not running.

Still having trouble?

Open a ticket on our Discord. Our team answers around 15 hours a day.

Get help on Discord

On this page