Documentation

avr_skilltree

Config-driven skill trees for FiveM. Qbox, QBCore, ESX and standalone. One dependency: ox_lib.

Install

  1. Drop avr_skilltree in your resources folder.
  2. ensure ox_lib and ensure oxmysql before it, then ensure avr_skilltree.
  3. Start the server once. The avr_skilltree table is created automatically.

Open the UI with /skills or F7. Both are configurable in config/config.lua.

On first start the resource validates your config and prints anything wrong: unknown parent ids, two nodes sharing a grid slot, effects that do not exist. A broken tree fails loudly at boot instead of quietly in game.

Editions

Same features, same updates, same support. Both cover one server. The only difference is which two files are escrowed.

FileStandardOpen
config/*.luaopenopen
bridge/*.luaopenopen
client/effects.luaopenopen
web/* (whole UI)openopen
locales/*.jsonopenopen
server/main.luaescrowedopen
client/main.luaescrowedopen

Building a tree

Trees live in config/trees.lua. A node looks like this:

{ id = 'pacer', label = 'Pacer', description = 'Move a little quicker on foot.',
  icon = 'boot', pos = { x = 1, y = 1 },
  parents = { 'second_wind' },      -- any one unless requireAll = true
  excludes = { 'riptide' },         -- taking this permanently locks riptide
  requires = { level = 10, job = 'police', gang = 'lost' },
  cost = 1, maxRank = 5,
  effects = { { type = 'sprint_mult', value = 0.01 } } },  -- per rank
  • pos is a grid coordinate, not pixels. x is the column, y is the row. The UI centres and links the constellation for you.
  • pointCap on a tree limits total points spendable in it. Set false for uncapped.
  • prestige = { level = 50, bonus = { type = 'xp_mult', value = 0.05 } } enables prestige on that tree.
  • Remove a node from the config and its points refund automatically on the player's next load.

Icons

Built in: pulse, wind, boot, flame, wave, droplet, shield, crosshair, hand, reload, fist, feather. Add your own in web/app.js under ICONS.

Effects

Fourteen are built in:

stamina_regen, sprint_mult, sprint_drain, swim_mult, lung_capacity, health_regen, melee_mult, weapon_damage, recoil_mult, noise_mult, fall_damage, max_armor, max_health, xp_mult.

Every one is wired to a native and does something in game.

There is no reload-speed effect. GTA does not expose reload timing, so a config that asks for one gets a console warning rather than a node that silently does nothing.

Values are fractions: 0.10 is +10%. Negative reduces, so recoil_mult = -0.08 is 8% less recoil. Effects stack additively across ranks and nodes.

Custom effects

Register your own from any resource:

exports.avr_skilltree:RegisterEffect('carry_weight', function(value)
    -- value is the total across every unlocked rank; 0 means "remove it"
    exports.ox_inventory:setPlayerWeight(100000 * (1.0 + value))
end)

API

Server exports:

exports.avr_skilltree:AddXP(src, 'endurance', 250)
exports.avr_skilltree:SetXP(src, 'endurance', 0)
exports.avr_skilltree:GetXP(src, 'endurance')
exports.avr_skilltree:GetLevel(src, 'endurance')
exports.avr_skilltree:GetPoints(src, 'endurance')
exports.avr_skilltree:HasNode(src, 'endurance', 'marathon')     --> boolean
exports.avr_skilltree:GetNodeRank(src, 'endurance', 'pacer')    --> number
exports.avr_skilltree:GetModifier(src, 'sprint_mult')           --> number
exports.avr_skilltree:GetPlayerData(src)                        --> full table
exports.avr_skilltree:Respec(src, 'endurance')

Events

AddEventHandler('avr_skilltree:server:nodeUnlocked', function(src, treeId, nodeId, rank) end)
AddEventHandler('avr_skilltree:server:levelUp', function(src, treeId, level) end)
AddEventHandler('avr_skilltree:server:respec', function(src, treeId) end)
AddEventHandler('avr_skilltree:server:prestige', function(src, treeId, prestige) end)

Statebags

Read a build from any resource without depending on this one:

local mods = Player(src).state.avr_mods     -- { sprint_mult = 0.05, ... }
local nodes = Player(src).state.avr_nodes   -- { ['endurance:pacer'] = 2, ... }

Theming

web/ is plain HTML, CSS and JS. No build step, no bundler.

Everything themeable is in the :root block at the top of web/style.css. Open web/index.html directly in a browser: it loads sample data so you can retheme without launching the game.

Frameworks

bridge/server.lua auto-detects Qbox, QBCore and ESX, and falls back to standalone using the license identifier with no economy. Both bridge files are open in every edition: five functions to adapt for a custom framework.

Performance

0.00ms idle on both sides. No ticks while the UI is closed. Effects are applied from statebag changes, and the one managed thread only runs while a stamina effect is active. Saves are batched, 15 seconds by default, and flushed on drop and resource stop.