Install
- Drop
avr_skilltreeinto your resources folder. ensure ox_libandensure oxmysqlbefore it, thenensure avr_skilltree.- Start the server once. The
avr_skilltreetable is created for you.
Open the interface with /skills or F7. Both are configurable in config/config.lua.
Controls
- Click a skill to read what it does, what it costs and what it closes off.
- Double-click a skill to unlock it or rank it up. The panel's button does the same thing.
- Drag anywhere, nodes included, to pan. Scroll to zoom, anchored under the cursor.
- The interface scales with screen resolution, so text stays readable on 1440p and ultrawide.
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.
Update check
A few seconds after start the resource fetches averstudio.dev/versions/avr_skilltree.json and prints one line in the server console if a newer version has been published, with that version and the one you are running. Nothing about your server is sent: it is a plain GET of a static file, and players never see it. Set Config.VersionCheck = false to turn it off.
Editions
Same features, same updates, same support, one server each. The only difference is which two files are escrowed.
| File | Standard | Open |
|---|---|---|
| config/*.lua | open | open |
| bridge/*.lua | open | open |
| client/effects.lua | open | open |
| web/* (prebuilt UI) | open | open |
| locales/*.json | open | open |
| server/main.lua | escrowed | open |
| client/main.lua | escrowed | open |
Building a tree
The box is not empty. avr_skilltree ships with five disciplines and forty ready-to-use skills (Endurance, Combat, Driving, Stealth and Vitality), every one already wired to a built-in effect. Use them as they are, reskin them, or throw them out.
Trees live in config/trees.lua. A node looks like this:
lua{ id = 'pacer', label = 'Pacer', description = 'Move a little quicker on foot.',
icon = 'boot', pos = { x = 1, y = 1 },
parents = { 'second_wind' }, -- any one of them, 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 } } }, -- value is per rank
posis a grid coordinate, not pixels.xis the column,yis the row. The interface centres and links the constellation for you.pointCapon a tree limits the total points spendable in it. Set it tofalsefor uncapped.prestigeenables prestige on that tree, with your own threshold and reward.maxinsideprestigecaps how many times that discipline can be prestiged. Leave it out to inheritConfig.Prestige.Max.- Remove a node from the config and its points refund automatically on that player's next load.
luapointCap = 20,
prestige = { level = 50, max = 3, bonus = { type = 'xp_mult', value = 0.05 } },
The prestige reward stacks every time, so an uncapped discipline keeps growing: a five per cent XP bonus is fifty per cent after ten prestiges. Config.Prestige.Max sets the ceiling for every discipline and 0 means no limit. Lowering it later never takes prestiges away from players who already earned them.
luaPrestige = {
Enabled = true,
Max = 0, -- 0 is unlimited
},
Icons
Built in: pulse, wind, boot, flame, wave, droplet, shield, crosshair, hand, reload, fist, feather. Twelve glyphs, no icon pack to install and nothing to compile.
In-game editor
Everything in config/trees.lua is also editable live, in game, by an admin. Open the skill tree, press the ⋮ menu, choose Admin panel → Tree editor.
- Create, edit and delete trees and nodes: labels, descriptions, icons, grid position, cost, ranks, level/job/gang requirements, parents (any-of or all-of), mutually exclusive branches, and effects. Require all parents appears once a node has two or more parents.
- A new tree opens in the middle of the grid so it can grow in every direction. Click a free slot to place a node; drag a node to move it.
- Save & publish applies instantly: no restart, no resource reload. The server re-validates the whole set with the same rules it uses at boot, refunds points for removed nodes on every online player, resyncs effects, and pushes the new trees to everyone with the interface open.
- The save also rewrites
config/trees.luain the same readable format we ship, so the file stays the single source of truth. Hand edits and editor edits coexist; comments are rewritten on editor saves. - Import / Export moves the whole tree set as JSON, for backups or for copying between servers.
Every editor payload is rebuilt server-side through a field whitelist and validated before anything is applied or written. A rejected save tells you exactly which node is wrong. The interface never has authority.
Admin panel
The same ⋮ menu opens player administration: view any player's trees (level, points, spent, unlocked skills, prestige), set a tree's level, reset one tree, or reset everything. Destructive actions take a second, confirming click.
The identity card shows every identifier on file: license, license2, Discord, Steam, FiveM, each one click from the clipboard. On frameworks with a citizen id it is shown too; standalone servers persist on the license, so the duplicate row is hidden.
Access is gated by an ace permission, checked server-side on every call:
cfgadd_ace group.admin "avr_skilltree.admin" allow
Config.AdminDebug, on by default, prints every admin action, who and what and target, to the server console.
Effects
Twenty are built in, and every one of them is wired to a native and does something in game:
- 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
- weapon_defense
- melee_defense
- vehicle_defense
- engine_power
- wanted_mult
- bike_grip
Values are fractions: 0.10 is +10%. Negative reduces, so recoil_mult = -0.08 is eight percent less recoil. Effects stack additively across ranks and nodes.
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.
Custom effects
Register your own from any resource. It then behaves exactly like a built-in, including stacking and removal:
luaexports.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:
luaexports.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) --> table
exports.avr_skilltree:Respec(src, 'endurance')
Events
Four server events fire on the moments worth reacting to:
luaAddEventHandler('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 at all:
lualocal mods = Player(src).state.avr_mods -- { sprint_mult = 0.05, ... }
local nodes = Player(src).state.avr_nodes -- { ['endurance:pacer'] = 2, ... }
Both are kept in sync automatically. If a player respecs, the bags update in the same tick.
Theming
Everything themeable lives in config/theme.json: colours, fonts, UI scale, node shape, node size and spacing, labels on or off, and every motion effect (link flow, dust, unlock bursts) individually or all at once. The interface reads the file when it opens, so the loop is edit, save, reopen. No rebuild.
The interface itself is React 19 built with Vite, and ships prebuilt in web/ on both editions: nothing to install, nothing to compile, no build step at any point. The React source is not shipped, so config/theme.json is the supported way to change how the menu looks.
A malformed or half-deleted theme.json falls back to the shipped defaults instead of breaking the menu, so a typo costs you a reopen, not a session.
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 if you run something custom.
Performance
0.00ms idle on both sides. No ticks while the interface is closed. Effects are applied from statebag changes rather than a polling loop, and the one managed thread only runs while a stamina effect is active.
Saves are batched (fifteen seconds by default) and flushed on drop and on resource stop. Storage is one row per player, versioned JSON.
Buying and refunds
Both editions are a one-time payment covering one server, with lifetime updates and support. Reselling, re-uploading or shipping this inside another paid resource is not permitted. Open means readable, not redistributable.
Checkout is owned and operated by Tebex Limited, who handle payment, product fulfilment and billing support. Standard is granted to your Cfx.re keymaster, which is why checkout asks you to sign in with your Cfx account before the basket. Open is delivered as a file download.
Digital goods are delivered immediately and are not refundable once downloaded, or once the asset has been granted in your keymaster. If the resource does not work as described, open a ticket in the Discord before requesting a refund. Almost every "it's broken" turns out to be a missing dependency or a config typo, and it gets fixed the same day.
The reply window, what to include in a ticket and exactly what support covers are on the support page. What is being built next is on the roadmap.