Configuration

9 min readUpdated Today

Overview

  • shared/config.lua: UI behavior, XP curves, listeners, prestige, admin permission
  • server/config_server.lua: Webhooks, log provider, rate-limit cooldown

Most servers never need to touch the listener caps. The defaults match a balanced survival/RP economy.

UI / Open Behavior

shared/config.lua
1Config.OpenCommand = 'skills'
2Config.OpenKey = 'J'
3Config.DefaultCategory = 'personal'
4Config.UnifiedView = true
  • OpenCommand: Chat command that opens the tree
  • OpenKey: Keybind that opens the tree. Any FiveM input name is accepted
  • DefaultCategory: Category shown when the UI opens. 'all' opens the unified view; any category UID jumps straight to that tree
  • UnifiedView: When true, all categories render on one scrollable canvas. When false, the sidebar switches between separate canvases per category

Theme & Visuals

shared/config.lua
1Config.Theme = 'default'
2Config.MainColor = nil
3Config.UIOpacity = 0.85
4Config.LockedPreviewGrayscale = true
  • Theme: One of 'default', 'cyberpunk', 'wasteland', 'noir', 'fantasy'
  • MainColor: Hex string (e.g. '#ff8800') that overrides the theme accent. nil falls back to the theme default
  • UIOpacity: Background opacity of the tree panel, from 0.0 to 1.0
  • LockedPreviewGrayscale: When true, the hover preview image renders grayscale until the skill is unlockable

XP Notifications

shared/config.lua
1Config.XpNotification = 'toast'
2Config.XpToastPosition = 'bottom'
3Config.XpToastDuration = 3000
  • XpNotification: 'toast' shows the floating in-panel toast; 'bridge' routes through Bridge.Notify for system-style notifications; false disables XP notifications entirely
  • XpToastPosition: 'top', 'bottom', or 'center'
  • XpToastDuration: Toast display time in ms

Performance / LOD

shared/config.lua
1Config.PerformanceMode = 'auto'
2Config.LodZoomThreshold = 0.45
3Config.LodNodeThreshold = 120
  • PerformanceMode: 'auto' (recommended), 'quality' (always full detail), 'performance' (LOD always on for low-end rigs)
  • LodZoomThreshold: Below this zoom level the canvas switches to simple nodes
  • LodNodeThreshold: When more than this many nodes are visible, heavy animations pause

XP Curve

shared/config.lua
1Config.DefaultXpBase = 400
2Config.DefaultXpMultiplier = 1.12
3Config.DefaultMaxLevel = 50
4Config.PointsPerLevel = 1
5Config.UnifiedPoints = false
6Config.XpPerLevelCap = 20000
  • DefaultXpBase: Base XP required for level 1
  • DefaultXpMultiplier: Exponential scaling per level. Required XP for level N is base * multiplier^N
  • DefaultMaxLevel: Global maximum level
  • PointsPerLevel: Skill points granted per level up
  • UnifiedPoints: When true, all categories share one point pool. When false, each category has its own pool
  • XpPerLevelCap: Hard cap on the XP required to clear a single level. Once the exponential curve crosses this value, every later level costs exactly XpPerLevelCap. Set 0 to disable

Each category in Config.Categories can override xpBase, xpMultiplier and maxLevel.

Categories

shared/config.lua
1Config.Categories = {
2    personal = { label = _L('cat_personal'), icon = 'user', xpBase = 400, xpMultiplier = 1.12, maxLevel = 50 },
3}
4
5Config.DisabledCategories = {}
  • Each entry keys on the category UID, the same string used everywhere in the API
  • label: Display name, supports _L('key') for localization
  • icon: Lucide icon name (user, anvil, swords, crosshair, etc.)
  • xpBase, xpMultiplier, maxLevel: Per-category overrides for the curve
  • DisabledCategories: Keys listed here are stripped at boot from Config.Categories, Config.DefaultTrees and Config.XpListeners. Useful to ship a default tree but hide it on a specific server

XP Listeners

Passive XP awarded by the client when the player performs an action. The server validates the timing, the movement, and enforces the daily cap.

shared/config.lua
1Config.XpListeners = {
2    driving = {
3        category = 'personal',
4        xp = 1,
5        interval = 30000,
6        intervalJitter = 6000,
7        condition = function()
8            local veh = cache.vehicle
9            if not veh or veh == 0 then return false end
10            if GetPedInVehicleSeat(veh, -1) ~= cache.ped then return false end
11            return GetEntitySpeed(veh) > 5.0
12        end,
13    },
14}
  • category: Category UID the XP is awarded to
  • xp: XP amount per trigger
  • interval: Base check interval in ms
  • intervalJitter: Random ± jitter added on every wait. Each tick waits between interval - jitter and interval + jitter
  • condition: Function returning true when the activity is happening. Uses cache.ped and cache.vehicle from ox_lib
  • requiresMovement: Optional. Defaults to true. Set false for an activity done standing still (shooting from cover, or a kill relayed by another resource), otherwise the server drops the claim as AFK farming
  • movementDistance: Optional metres the ped must cover between two grants of this listener. Default 0.5
  • cooldown: Optional minimum ms between two grants of this listener. Defaults to interval - intervalJitter capped at 5000, and never goes below 1000

Built-in listeners: driving, swimming, sprinting, shooting, flying. Replace, extend, or remove any of them.

A listener with no interval and no condition never fires by itself. It is the whitelist entry a client script requests by name through exports.ml_skills:RequestXp(categoryUid, amount, listenerName), which is how a client-side event awards XP without trusting the client with an amount.

Listener Anti-Abuse

shared/config.lua
1Config.XpListenerCapPerMinute = 60
2Config.XpListenerDailyCapPerCategory = 50000
3Config.XpListenerMovementCheck = true
  • XpListenerCapPerMinute: Hard cap on listener XP per minute per category, server-enforced. Listeners are activity hints sent by the client, so this value is what bounds a spoofed claim. Size it on the listeners a single player can trigger at once: the shipped set stays under 40 per minute
  • XpListenerDailyCapPerCategory: Daily hard cap per category
  • XpListenerMovementCheck: When true, the server rejects listener XP if the ped has not moved since the previous fire. Listeners that set requiresMovement = false are exempt

Daily XP Cap

shared/config.lua
1Config.DailyXpCap = {
2    enabled = false,
3    globalPerDay = 0,
4    perCategoryPerDay = 0,
5    resetMode = 'fixed',
6    resetHour = 0,
7    showInUI = true,
8    notifyOnCap = true,
9}

Opt-in ceiling on how much XP a player can earn per day. It applies to every gain routed through AddXp (listeners, integrations, skill events); admin give/remove and SetLevel are exempt.

  • enabled: Master switch. Off by default, so there is no daily cap until you turn it on.
  • globalPerDay: Max total XP per day across all categories. 0 = unlimited.
  • perCategoryPerDay: Default daily ceiling per category. 0 = unlimited. Override one category with Config.Categories[uid].dailyXpCap, or scale it by level with Config.Categories[uid].dailyXpCapTiers (a list of { minLevel, cap } bands; the highest band at or below the player level wins). Tiers take precedence over a flat cap.
  • resetMode: 'fixed' resets every day at resetHour; 'rolling' resets 24h after the player's first XP of the cycle.
  • resetHour: Hour (0-23, server local time) of the fixed reset.
  • showInUI: Show the Daily XP usage indicator inside the skill UI.
  • notifyOnCap: Notify the player when the daily limit is reached.

Admin Permission

shared/config.lua
1Config.AdminPermission = {
2    level = 'admin',
3    ace = nil,
4}
  • level: ml_bridge permission level required for the Player Manager and the tree editor. Common values: 'admin', 'god', 'mod' or 'mod'
  • ace: Optional ACE permission (e.g. 'ml_skills.admin'). When set, ACE takes precedence over the Bridge level

Reset & Wipe Permissions

shared/config.lua
1Config.AllowReset = false
2Config.AdminAllowResetSkills = true
3Config.AdminAllowFullWipe = true
  • AllowReset: When true, players can reset their own skill points from the UI
  • AdminAllowResetSkills: When true, the Player Manager exposes the per-category reset action
  • AdminAllowFullWipe: When true, the Player Manager exposes the full wipe action

Prestige

shared/config.lua
1Config.PrestigeEnabled = false
2Config.PrestigeBonus = 0.10
3Config.MaxPrestige = 5
  • PrestigeEnabled: Master switch. When false, no prestige UI shows up
  • PrestigeBonus: XP multiplier added per prestige level. 0.10 = +10% per tier
  • MaxPrestige: Maximum prestige levels achievable

External Export Gate

shared/config.lua
1Config.EnableUnlockSkillExport = true
  • EnableUnlockSkillExport: When true, other resources can call exports.ml_skills:UnlockSkill(catUid, skillUid, src). Set false to lock unlocks to the in-UI flow

Skill Default Values

shared/config.lua
1Config.SkillDefaultValues = {
2    moreStamina = 100.0,
3    moreMaxHp = 200,
4    timeUnderwater = 10.0,
5}

GTA-default values used by open/effects.lua handlers as the baseline for percentage-based skill effects. Override these only when another resource modifies the same natives.

HP Regeneration

shared/config.lua
1Config.HpRegen = {
2    idleSeconds = 300,
3    maxRegenPercent = 25,
4    tickHp = 1,
5    tickMs = 5000,
6    breakOnSprint = true,
7    breakOnShoot = true,
8    breakOnVehicle = true,
9    breakOnDamage = true,
10    notifyOnStart = true,
11    disableNativeRegen = false,
12}

Powers the hpRegeneration perks in the personal tree: a slow out-of-combat heal that begins after the player has been still for idleSeconds and tops out at maxRegenPercent of the playable HP range.

  • idleSeconds: Idle time before regen begins (300 = 5 minutes).
  • maxRegenPercent: Regen ceiling as a percent of the HP range (25 stops at 125 HP).
  • tickHp / tickMs: HP restored per tick (multiplied by the skill effect) and the tick interval.
  • breakOnSprint / breakOnShoot / breakOnVehicle / breakOnDamage: Any of these resets the idle timer.
  • notifyOnStart: Notify the player the moment regen kicks in.
  • disableNativeRegen: Suppresses GTA's native HP regen. Leave false if another script (ml_healthsystem, esx_basicneeds) manages regen; ml_skills auto-skips it when ml_healthsystem is detected.

Achievements

shared/config.lua
1Config.Achievements = {
2    firstSkill = {
3        label = _L('achievement_first_step'),
4        description = _L('achievement_first_step_desc'),
5        icon = 'star',
6        condition = { type = 'skills_unlocked', count = 1 },
7        rewards = {
8            { item = 'bread', count = 3 },
9        },
10    },
11}

Achievements declared here are seeded into the ml_skills_achievements table on first start. After seeding, the in-game achievement editor becomes the source of truth.

  • condition.type: 'skills_unlocked', 'skill_unlocked', or 'level_reached'
  • condition.count: Used by 'skills_unlocked' and 'level_reached'
  • condition.skillId / condition.category: Used by 'skill_unlocked'
  • rewards: Array of { item, count } or { cash = N } / { bank = N }

Webhooks

server/config_server.lua
1ServerConfig.Webhook = ''
2
3ServerConfig.Webhooks = {
4    player_skill_unlock      = '',
5    player_claim_reward      = '',
6    player_claim_achievement = '',
7    player_prestige          = '',
8    player_reset_skills      = '',
9    player_reset_category    = '',
10    admin_give_xp            = '',
11    admin_remove_xp          = '',
12    admin_set_level          = '',
13    admin_give_points        = '',
14    admin_remove_points      = '',
15    admin_reset_skills       = '',
16    admin_full_wipe          = '',
17    admin_unlock_skill       = '',
18    admin_save_tree          = '',
19    admin_save_categories    = '',
20    admin_save_achievement   = '',
21    admin_delete_achievement = '',
22    external_unlock_skill    = '',
23    security_devtools        = '',
24}
25
26ServerConfig.LogProvider = 'discord'
27ServerConfig.RateLimitCooldown = 1000
  • Webhook: Generic fallback used when a specific slot is empty
  • Webhooks.<action>: Dedicated webhook per action category
  • LogProvider: 'discord', 'fivemanage', 'ox_lib', 'both', or 'all'
  • RateLimitCooldown: Minimum ms between two UI actions from the same player

Set any webhook to false to disable that log entirely.