DirkScripts Logo

Login With FiveM

Login

lib.skill

Levels without inventing your own. One curve, one storage rule, one level-up event — and the same arithmetic in Lua, in the settings panel and in dirk-cfx-react, so a gate and the bar the player is looking at cannot disagree.

ℹ️ A level is never stored. Only XP is. Level is worked out from it every time it is asked for, which is what lets an admin retune the curve — or change its shape entirely — without taking anything away from anyone.

One skill, or one skill each

Some skills are single: there is one fishing level and you either have it or you do not. Others are instanced: the same skill, the same curve, held separately against each of several things.

lua
lib.skill.register('fishing', { baseXP = 83, maxLevel = 99 })
lib.skill.register('yardRep', { baseXP = 40, maxLevel = 10, instanced = true })

A scrapyard's reputation is the second kind. It is not one skill called "yard rep" — it is one definition with an instance per yard, because you are somebody to one man and nobody to the next. Every call on an instanced skill names the instance alongside the player.

Registering

Call register once, from a shared file, so both sides agree. Call update from a scriptConfig watcher for every later edit.

lua
lib.skill.register('yardRep', { instanced = true })

lib.scriptConfig.on('scrapyards', function(section)
  lib.skill.update('yardRep', section?.rep)
end)
OptionMeaning
baseLevelWhere everyone starts. Default 1.
maxLevelThe ceiling. Default 99.
baseXPWhat level 2 costs. Every later level scales off it. Default 83.
modifierStretches the whole curve. Higher is slower. Default 1.
curveThe shape: runescape (default), linear, quadratic, softcap.
instancedHeld per-something rather than once per player.
metaKeyOverride the storage key. See below.

The shapes

curveFeels like
runescapeEarly levels fly past, the last few are a project. What every existing dirk level was earned against, so it is the default.
linearEvery level costs the same. Predictable, and flat at the top.
quadraticCost grows with the square. A real climb without a cliff.
softcapSteep early, easing near the ceiling. For a ladder meant to be finished.

Switching shape takes nobody's XP. Everyone is simply re-read against the new ladder.

The storage key

XP lives in framework player metadata under a key namespaced by the owning resource — dirk_projectCars_yardRep. A stock QBX install already ships craftingrep, jobrep and dealerrep, so an unprefixed name is a collision with somebody else's script and the loser fails silently.

metaKey overrides it, and exists for exactly one reason: a skill that already has live data. dirk_fishing keeps fishingXp because every level every player has earned lives there, and renaming it would orphan the lot.

Server

lua
lib.skill.get(src, id, key?)                 --> number
lib.skill.all(src, id)                       --> { [instance] = xp }  (instanced only)
lib.skill.set(src, id, amount, key?)         --> number
lib.skill.add(src, id, amount, key?)         --> xp, levelled, level
lib.skill.remove(src, id, amount, key?)      --> xp, levelled, level
lib.skill.atLeast(src, id, level, key?)      --> boolean
lib.skill.progressFor(src, id, key?)         --> table (see Progress)
lib.skill.syncAll(src)                       -- push everything, on spawn

add is the write path. It clamps, stores, pushes the new value to that player's client, and fires a level-up event if a threshold was crossed. Never write the metadata yourself — three sites in fishing used to, and each one left the player's own game showing a stale level until they relogged.

lua
local xp, levelled, level = lib.skill.add(src, 'yardRep', 25, yardId)
if levelled then
  -- your script decides what a level-up looks like; dirk_lib only says one happened
end

Gating

lua
if not lib.skill.atLeast(src, 'fishing', rod.lvlRequired) then
  return -- refuse
end

The client has the same call for showing things — greying a locked option, ordering a list. Never for deciding one: the server re-checks everything, because the client's number was handed to it.

Client

lua
lib.skill.get(id, key?)              --> number
lib.skill.all(id)                    --> { [instance] = xp }
lib.skill.levelOf(id, key?)          --> number
lib.skill.progressFor(id, key?)      --> table
lib.skill.atLeast(id, level, key?)   --> boolean

Pushed, never fetched. Framework client-side metadata does not reliably refresh, so the server tells the client on every write and this holds the answer. Opening a UI that shows a level costs no round trip.

Progress

progress(id, xp) — and its progressFor variants — return everything a bar needs, with the same field names createSkill uses in dirk-cfx-react, so a payload built in Lua drops straight into LevelPanel or LevelBanner with nothing in between to go stale.

lua
{
  xp = 4200, level = 24,
  currentLevelXp = 4139, nextLevelXp = 4470,
  xpToNext = 270, progress = 18.4, maxed = false,
}

Events

EventFired
dirk_lib:levelUpserver and client, when add crosses a threshold. (src, id, key, level, wasLevel) on the server; (id, key, level, wasLevel) on the client.
dirk_lib:levelSyncclient, on every write. Handled internally.
dirk_lib:levelSyncAllclient, from syncAll. Handled internally.

In the settings panel

Mark the settings block with x-skill and it draws the curve it describes — the shape of the climb, what real levels cost, the total to max, and how that compares with the default pace, all recomputed as you drag.

json
"rep": {
  "type": "object",
  "x-skill": "yardRep",
  "properties": {
    "curve":     { "type": "string", "x-enum": ["runescape", "linear", "quadratic", "softcap"] },
    "baseLevel": { "type": "number", "default": 1 },
    "maxLevel":  { "type": "number", "default": 10 },
    "baseXP":    { "type": "number", "default": 40 },
    "modifier":  { "type": "number", "default": 1, "x-control": "multiplier" }
  }
}

Named rather than flagged, because a script can have several.

Last updated on 9 September 2026

Copyright © 2026 DirkScripts.

Not affiliated with or endorsed by Rockstar North, Take-Two Interactive, or any other rights holders. FiveM is a copyright and registered trademark of Take-Two Interactive Software, Inc.
Our checkout system is provided by Tebex Limited, who manage payment processing, product delivery, and billing support. Prices shown in currencies other than GBP are approximate conversions updated daily. All purchases are processed in GBP, so the final amount charged may vary depending on your bank or payment provider’s exchange rate.