Gridbase
Paid releasesDime Skill TreeGuides

Node Effects

Create custom effects when skill tree nodes are unlocked

Node Effects

Learn how to create custom effects that trigger when players unlock skill tree nodes.

Overview

Node effects are callbacks that execute when a player unlocks a specific node. They can:

  • Grant stat bonuses
  • Unlock abilities
  • Trigger events
  • Modify gameplay mechanics
  • Award items or currency

Registering Effects

Effects are registered using the registerNodeEffect export from Dime Skills:

exports.dime_skills:registerNodeEffect(nodeId, callback)

Parameters:

  • nodeId (string): Must match the node ID in your tree config
  • callback (function): Called with (source, nodeData)

Basic Example

-- In your resource's server.lua
CreateThread(function()
    -- Wait for dime_skills to load
    while GetResourceState('dime_skills') ~= 'started' do
        Wait(100)
    end
    
    -- Register effect for 'combat_damage' node
    exports.dime_skills:registerNodeEffect('combat_damage', function(src, nodeData)
        -- nodeData contains: value, data, and config from node definition
        local damageBoost = nodeData.value or 0.10
        
        -- Store in player state for other resources to read
        Player(src).state:set('meleeDamageBoost', damageBoost, true)
        
        -- Notify player
        TriggerClientEvent('ox_lib:notify', src, {
            type = 'success',
            title = 'Node Unlocked!',
            description = ('+%d%% Melee Damage'):format(damageBoost * 100)
        })
    end)
end)

Effect Types

Stat Bonus Effects

Modify player stats when a node is unlocked:

-- Speed boost node
exports.dime_skills:registerNodeEffect('survival_speed', function(src, nodeData)
    local boost = nodeData.value or 1.1  -- 10% speed boost
    
    -- Use statebags for persistent state
    Player(src).state:set('runSpeedMultiplier', boost, true)
    
    -- Apply on client
    TriggerClientEvent('myresource:applySpeedBoost', src, boost)
end)

-- Client-side handler
RegisterNetEvent('myresource:applySpeedBoost', function(multiplier)
    SetRunSprintMultiplierForPlayer(PlayerId(), multiplier)
end)

Ability Unlock Effects

Enable abilities when nodes are unlocked:

-- Double jump ability
exports.dime_skills:registerNodeEffect('movement_double_jump', function(src, nodeData)
    Player(src).state:set('canDoubleJump', true, true)
    
    TriggerClientEvent('abilities:unlock', src, 'double_jump', {
        height = nodeData.data?.height or 2.0
    })
end)

-- Execute ability (finishing move)
exports.dime_skills:registerNodeEffect('combat_execute', function(src, nodeData)
    Player(src).state:set('canExecute', true, true)
    
    TriggerClientEvent('combat:unlockExecute', src, {
        damageThreshold = nodeData.data?.threshold or 0.2
    })
end)

Passive Effects

Grant passive bonuses:

-- Health regeneration
exports.dime_skills:registerNodeEffect('survival_regen', function(src, nodeData)
    local regenRate = nodeData.value or 1  -- HP per second
    
    Player(src).state:set('healthRegen', regenRate, true)
    TriggerClientEvent('survival:startRegen', src, regenRate)
end)

-- Armor bonus
exports.dime_skills:registerNodeEffect('combat_armor', function(src, nodeData)
    local armorBonus = nodeData.value or 25
    
    -- Increase max armor
    TriggerClientEvent('myresource:setMaxArmor', src, 100 + armorBonus)
end)

Crafting/Job Effects

Unlock crafting recipes or job perks:

-- Advanced crafting recipes
exports.dime_skills:registerNodeEffect('crafting_advanced', function(src, nodeData)
    -- Enable advanced crafting for this player
    Player(src).state:set('advancedCrafting', true, true)
    
    -- Could also grant access to crafting bench
    TriggerEvent('crafting:grantAccess', src, 'advanced_bench')
end)

-- Job-specific perk
exports.dime_skills:registerNodeEffect('mechanic_efficiency', function(src, nodeData)
    local efficiency = nodeData.value or 1.5  -- 50% faster repairs
    
    Player(src).state:set('repairSpeed', efficiency, true)
end)

Listening to Unlock Events

Instead of registering callbacks, you can listen to the global unlock event:

AddEventHandler('dime_skills:nodeUnlocked', function(src, nodeId, nodeData)
    print(('Player %d unlocked: %s'):format(src, nodeId))
    
    -- Handle multiple nodes in one listener
    if nodeId == 'combat_damage_1' then
        -- Apply damage boost
    elseif nodeId == 'combat_damage_2' then
        -- Apply bigger damage boost
    elseif nodeId:match('^crafting_') then
        -- Handle all crafting nodes
        TriggerEvent('crafting:nodeUnlocked', src, nodeId)
    end
end)
RegisterNetEvent('dime_skills:nodeUnlocked', function(nodeId, nodeData)
    -- Play unlock effects
    PlaySoundFrontend(-1, 'MEDAL_UP', 'HUD_MINI_GAME_SOUNDSET', true)
    
    -- Show unlock animation
    TriggerEvent('myui:showUnlockAnimation', nodeId)
    
    -- Apply client-side effects immediately
    if nodeId == 'survival_speed' then
        local boost = nodeData.value or 1.1
        SetRunSprintMultiplierForPlayer(PlayerId(), boost)
    end
end)

Persisting Effects

Node effects should persist across sessions. Use statebags or database storage:

Using Statebags

-- On node unlock
exports.dime_skills:registerNodeEffect('combat_damage', function(src, nodeData)
    Player(src).state:set('meleeDamageBoost', nodeData.value, true)
end)

-- On player load (re-apply effects for unlocked nodes)
AddEventHandler('playerJoined', function()
    local src = source
    local unlockedNodes = exports.dime_skilltree:getUnlockedNodes(src)
    
    for _, nodeId in ipairs(unlockedNodes) do
        local nodeInfo = exports.dime_skilltree:getNodeInfo(nodeId)
        if nodeInfo and nodeInfo.effect.type == 'stat_bonus' then
            -- Re-apply stat bonus
            applyNodeEffect(src, nodeId, nodeInfo.effect)
        end
    end
end)

Checking Unlocked Nodes

-- Before applying effect, check if player has node
local function getPlayerDamageBoost(src)
    local baseBoost = 1.0
    
    if exports.dime_skilltree:hasNode(src, 'combat_damage_1') then
        baseBoost = baseBoost + 0.05
    end
    
    if exports.dime_skilltree:hasNode(src, 'combat_damage_2') then
        baseBoost = baseBoost + 0.10
    end
    
    if exports.dime_skilltree:hasNode(src, 'combat_master') then
        baseBoost = baseBoost + 0.15
    end
    
    return baseBoost
end

Complete Example

Here's a complete example of a combat effects system:

-- combat_effects.lua (server)

local PlayerEffects = {}

-- Helper to update player effects
local function UpdatePlayerEffects(src)
    local effects = PlayerEffects[src] or {
        meleeDamage = 1.0,
        weaponDamage = 1.0,
        accuracy = 1.0,
        armor = 100,
    }
    
    -- Check each combat node
    if exports.dime_skilltree:hasNode(src, 'combat_melee_1') then
        effects.meleeDamage = effects.meleeDamage + 0.05
    end
    if exports.dime_skilltree:hasNode(src, 'combat_melee_2') then
        effects.meleeDamage = effects.meleeDamage + 0.10
    end
    if exports.dime_skilltree:hasNode(src, 'combat_ranged_1') then
        effects.weaponDamage = effects.weaponDamage + 0.05
        effects.accuracy = effects.accuracy + 0.05
    end
    if exports.dime_skilltree:hasNode(src, 'combat_armor') then
        effects.armor = effects.armor + 25
    end
    if exports.dime_skilltree:hasNode(src, 'combat_master') then
        effects.meleeDamage = effects.meleeDamage + 0.05
        effects.weaponDamage = effects.weaponDamage + 0.05
    end
    
    PlayerEffects[src] = effects
    
    -- Sync to client
    TriggerClientEvent('combat:syncEffects', src, effects)
    
    return effects
end

-- Register effects for all combat nodes
local combatNodes = {
    'combat_melee_1', 'combat_melee_2',
    'combat_ranged_1', 'combat_ranged_2',
    'combat_armor', 'combat_master'
}

CreateThread(function()
    while GetResourceState('dime_skills') ~= 'started' do Wait(100) end
    
    for _, nodeId in ipairs(combatNodes) do
        exports.dime_skills:registerNodeEffect(nodeId, function(src, nodeData)
            UpdatePlayerEffects(src)
            
            TriggerClientEvent('ox_lib:notify', src, {
                type = 'success',
                title = 'Combat Skill Unlocked',
                description = 'Your combat abilities have improved!'
            })
        end)
    end
end)

-- Re-apply on player load
RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()
    UpdatePlayerEffects(source)
end)

-- Export for other resources
exports('getPlayerCombatEffects', function(src)
    return PlayerEffects[src] or UpdatePlayerEffects(src)
end)

-- Cleanup on disconnect
AddEventHandler('playerDropped', function()
    PlayerEffects[source] = nil
end)

Best Practices

  1. Register effects early - Use CreateThread on resource start
  2. Handle reconnects - Re-apply effects when players rejoin
  3. Use statebags - For effects other resources need to read
  4. Validate server-side - Never trust client for effect calculations
  5. Clean up on disconnect - Remove player data when they leave
  6. Stack effects carefully - Consider how multiple nodes combine