Configuration
Everything lives in config.lua.
Config reference
| Key | What it does |
|---|---|
Config.Item | Item that opens the perk tablet. Default perk_tablet. |
Config.Branding | Tablet header branding: name, suffix and accent color. Sent to the NUI. |
Config.MaxLevel | Highest level any category can reach. Default 10. |
Config.Levels | XP required for each level (the global curve). Tops out at 20,000 at level 10. |
Config.CategoryLevels | Optional. Add Config.CategoryLevels = { categoryId = { [1] = 200, ... } } to give a category its own XP curve. Omit it entirely and every category uses Config.Levels. |
Config.IgnorePerkRequirements | true (default): spend points in any order once you meet the level. false: must own the previous perk in the chain first. |
Config.Categories | The perk tree: tracks, categories, and the perks in each. |
To change level speed, tune the XP each activity awards in that activity's own config (e.g. jj-houserobbery's tiers). Don't lower the 20,000 cap.
Adding a category
A category is one progression track in the tablet (House Robbery, Fishing, your own job). It has a level 0-10 driven by XP, and a list of perks the player buys with the points they earn from levelling.
jj-perks only stores progression — it tracks XP, levels, and which perks a player owns, and shows them in the tablet. It does not change your gameplay on its own. The actual bonus is something your own script applies when it asks jj-perks "does this player own this perk?". So adding a category is two jobs: define it here, then read it from your script.
1. Define it in config
Categories live in Config.Categories, split into two tracks: criminal and civilian. Here's a full example:
{
id = "mining", -- unique key. your script passes this to every export
label = "Mining", -- shown on the tab
icon = "gem", -- Font Awesome icon name (no "fa-" prefix)
description = "Mine faster and earn double drops on ores.",
perks = {
{ id = "mine_r1", label = "Quick Gather I", description = "2% quicker gathering", icon = "gauge-high", requiredLevel = 1, requires = nil },
{ id = "mine_r2", label = "Double Drops I", description = "5% chance of double drops", icon = "clone", requiredLevel = 2, requires = "mine_r1" },
{ id = "mine_r3", label = "Quick Gather II", description = "4% quicker gathering", icon = "gauge-high", requiredLevel = 3, requires = "mine_r2" },
-- ...continue up to mine_r10
},
}| Field | What it is |
|---|---|
id | The category key. This is the string you pass to every export. Must be unique and never change once players have progress on it. |
label | Display name on the tablet tab. |
icon | Font Awesome 6 icon name without the fa- prefix. |
description | One line shown under the category title. |
perks | The list of perks, in order. |
Each perk:
| Field | What it is |
|---|---|
id | Unique perk id across the whole resource. This is what hasPerk(src, 'mine_r2') checks. |
label / description | UI text only. The description is a promise to the player; your script is what makes it true. |
icon | Font Awesome name for the perk node. |
requiredLevel | The category level the player must reach before they can buy this perk. |
requires | The perk id that must be owned first. nil for the first perk. Ignored entirely when Config.IgnorePerkRequirements = true (the default). |
Rule of thumb: keep 10 perks per category so the points line up with the 10 levels, and keep every id unique. Save and restart jj-perks and the new tab shows up.
At this point it tracks XP and lets players buy perks, but nothing in-game changes yet — see Exports & Integration for wiring the bonus into your script.