Skip to main content

Introduction

Server events are the backbone of RSG Framework’s server-side communication system. They allow resources to listen for and respond to player actions, system events, and cross-resource communication.
Server events run on the server and can be triggered by clients using TriggerServerEvent or by other server-side code using TriggerEvent.

How It Works

Event Flow

Client Script → TriggerServerEvent('eventName', args) → Server Listens → Server Processes → Response

Registering Events

Server events are registered using RegisterNetEvent:
-- SERVER SIDE
RegisterNetEvent('eventName', function(arg1, arg2)
    local src = source -- The player who triggered the event
    -- Process event
end)
Always validate data received from client events! Never trust client input.

Core Player Events

RSGCore:Server:OnPlayerLoaded

Triggered when a player successfully loads into the server after character selection.
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:OnPlayerLoaded', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    print('Player loaded:', Player.PlayerData.charinfo.firstname)

    -- Your custom logic here
    -- Example: Give welcome bonus
    Player.Functions.AddMoney('cash', 100, 'welcome-bonus')
end)
Use Cases:
  • Initialize player-specific data
  • Load custom player stats
  • Trigger welcome messages
  • Set up player blips/markers

RSGCore:Server:OnPlayerUnload

Triggered when a player logs out or returns to character selection.
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:OnPlayerUnload', function()
    local src = source
    print('Player', src, 'logged out')

    -- Clean up player-specific data
    -- Remove from custom tables
    -- Save additional data if needed
end)
Use Cases:
  • Clean up temporary data
  • Save custom player stats
  • Remove player from active lists
  • Stop player-specific threads

Server Management Events

RSGCore:Server:CloseServer

Closes the server to non-whitelisted players with a custom reason.
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:CloseServer', function(reason)
    local src = source

    -- Permission check
    if not RSGCore.Functions.HasPermission(src, 'admin') then
        RSGCore.Functions.Kick(src, 'You don\'t have permissions for this', nil, nil)
        return
    end

    reason = reason or 'Server maintenance'
    RSGCore.Config.Server.Closed = true
    RSGCore.Config.Server.ClosedReason = reason

    -- Kick all non-whitelisted players
    for playerId in pairs(RSGCore.Players) do
        if not RSGCore.Functions.HasPermission(playerId, RSGCore.Config.Server.WhitelistPermission) then
            RSGCore.Functions.Kick(playerId, reason, nil, nil)
        end
    end

    print('Server closed:', reason)
end)
Trigger from Client:
-- CLIENT SIDE (admin only)
TriggerServerEvent('RSGCore:Server:CloseServer', 'Emergency maintenance')
Use Cases:
  • Emergency maintenance
  • Scheduled restarts
  • Server events preparation
  • Testing with limited players

RSGCore:Server:OpenServer

Reopens the server to all players.
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:OpenServer', function()
    local src = source

    if not RSGCore.Functions.HasPermission(src, 'admin') then
        RSGCore.Functions.Kick(src, 'You don\'t have permissions for this', nil, nil)
        return
    end

    RSGCore.Config.Server.Closed = false
    print('Server opened')
end)
Trigger from Client:
-- CLIENT SIDE (admin only)
TriggerServerEvent('RSGCore:Server:OpenServer')

Player Data Events

RSGCore:UpdatePlayer

Updates and saves player needs (hunger, thirst, cleanliness).
-- SERVER SIDE
RegisterNetEvent('RSGCore:UpdatePlayer', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Decrease player needs
    local newHunger = math.max(0, Player.PlayerData.metadata.hunger - RSGCore.Config.Player.HungerRate)
    local newThirst = math.max(0, Player.PlayerData.metadata.thirst - RSGCore.Config.Player.ThirstRate)
    local newCleanliness = math.max(0, Player.PlayerData.metadata.cleanliness - RSGCore.Config.Player.CleanlinessRate)

    -- Update metadata
    Player.Functions.SetMetaData('hunger', newHunger)
    Player.Functions.SetMetaData('thirst', newThirst)
    Player.Functions.SetMetaData('cleanliness', newCleanliness)

    -- Notify client
    TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst, newCleanliness)

    -- Save player data
    Player.Functions.Save()
end)
This event is typically called on a timer to gradually decrease player needs over time.
Complete Example with Timer:
-- SERVER SIDE
CreateThread(function()
    while true do
        Wait(RSGCore.Config.Player.UpdateInterval or 300000) -- 5 minutes default

        for src, Player in pairs(RSGCore.Players) do
            if Player then
                TriggerEvent('RSGCore:UpdatePlayer', src)
            end
        end
    end
end)

RSGCore:Server:SetMetaData

Sets a specific metadata value for a player with validation.
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:SetMetaData', function(meta, data)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate needs (cap at 100)
    if meta == 'hunger' or meta == 'thirst' or meta == 'cleanliness' then
        if data > 100 then
            data = 100
        elseif data < 0 then
            data = 0
        end
    end

    -- Update metadata
    Player.Functions.SetMetaData(meta, data)

    -- Update HUD
    TriggerClientEvent('hud:client:UpdateNeeds', src,
        Player.PlayerData.metadata.hunger,
        Player.PlayerData.metadata.thirst,
        Player.PlayerData.metadata.cleanliness
    )
end)
Trigger from Client:
-- CLIENT SIDE
TriggerServerEvent('RSGCore:Server:SetMetaData', 'hunger', 75)
Use Cases:
  • Restore hunger after eating
  • Restore thirst after drinking
  • Restore cleanliness after bathing
  • Custom metadata updates

Job & Duty Events

RSGCore:ToggleDuty

Toggles a player’s on-duty status for their job.
-- SERVER SIDE
RegisterNetEvent('RSGCore:ToggleDuty', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Check if job has duty system
    if not Player.PlayerData.job.name then return end

    -- Toggle duty
    local newDutyStatus = not Player.PlayerData.job.onduty
    Player.Functions.SetJobDuty(newDutyStatus)

    -- Notify player
    lib.notify(src, {
        description = newDutyStatus and 'You are now on duty' or 'You are now off duty',
        type = 'info'
    })

    -- Trigger client event for additional logic
    TriggerClientEvent('RSGCore:Client:SetDuty', src, newDutyStatus)
end)
Trigger from Client:
-- CLIENT SIDE
RegisterCommand('duty', function()
    TriggerServerEvent('RSGCore:ToggleDuty')
end)
Complete Example with Job Restrictions:
-- SERVER SIDE
local dutyJobs = {
    ['police'] = true,
    ['doctor'] = true,
    ['mechanic'] = true
}

RegisterNetEvent('RSGCore:ToggleDuty', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Check if job has duty system
    if not dutyJobs[Player.PlayerData.job.name] then
        lib.notify(src, {
            description = 'Your job doesn\'t have a duty system',
            type = 'error'
        })
        return
    end

    -- Toggle duty
    local newDutyStatus = not Player.PlayerData.job.onduty
    Player.Functions.SetJobDuty(newDutyStatus)

    -- Job-specific logic
    if Player.PlayerData.job.name == 'police' then
        if newDutyStatus then
            -- Add police blip
            TriggerClientEvent('police:client:addBlip', src)
        else
            -- Remove police blip
            TriggerClientEvent('police:client:removeBlip', src)
        end
    end

    lib.notify(src, {
        description = newDutyStatus and 'Clocked in' or 'Clocked out',
        type = 'success'
    })
end)

Callback Events

RSGCore:Server:TriggerCallback

Handles server-side callback requests from clients.
-- SERVER SIDE (internal - used by callback system)
RegisterNetEvent('RSGCore:Server:TriggerCallback', function(name, ...)
    local src = source

    if not RSGCore.ServerCallbacks[name] then
        print('^1[ERROR]^7 Callback', name, 'does not exist')
        return
    end

    RSGCore.ServerCallbacks[name](src, function(...)
        TriggerClientEvent('RSGCore:Client:TriggerCallback', src, name, ...)
    end, ...)
end)
This is an internal event used by the callback system. Use RSGCore.Functions.CreateCallback instead of listening to this event directly.
Proper Usage:
-- SERVER SIDE - Create callback
RSGCore.Functions.CreateCallback('myCallback', function(source, cb, arg1)
    local Player = RSGCore.Functions.GetPlayer(source)
    if not Player then
        cb(nil)
        return
    end

    cb(Player.PlayerData.money.cash)
end)
-- CLIENT SIDE - Trigger callback
RSGCore.Functions.TriggerCallback('myCallback', function(cashAmount)
    print('Player has $' .. cashAmount)
end, someArgument)

Permission Events

RSGCore:Server:GivePermission

Grants a permission level to a player (typically admin/god).
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:GivePermission', function(targetId, permission)
    local src = source

    -- Only admins can grant permissions
    if not RSGCore.Functions.HasPermission(src, 'god') then
        lib.notify(src, {
            description = 'You don\'t have permission to do this',
            type = 'error'
        })
        return
    end

    local Target = RSGCore.Functions.GetPlayer(targetId)
    if not Target then
        lib.notify(src, {
            description = 'Player not found',
            type = 'error'
        })
        return
    end

    -- Grant permission
    RSGCore.Functions.AddPermission(targetId, permission)

    lib.notify(src, {
        description = 'Granted ' .. permission .. ' to player',
        type = 'success'
    })

    lib.notify(targetId, {
        description = 'You have been granted ' .. permission .. ' permissions',
        type = 'info'
    })
end)

RSGCore:Server:RemovePermission

Removes a permission level from a player.
-- SERVER SIDE
RegisterNetEvent('RSGCore:Server:RemovePermission', function(targetId, permission)
    local src = source

    if not RSGCore.Functions.HasPermission(src, 'god') then
        lib.notify(src, {
            description = 'You don\'t have permission to do this',
            type = 'error'
        })
        return
    end

    -- Remove permission
    RSGCore.Functions.RemovePermission(targetId, permission)

    lib.notify(src, {
        description = 'Removed ' .. permission .. ' from player',
        type = 'success'
    })
end)

Best Practices

1. Always Validate Player

-- GOOD
RegisterNetEvent('myEvent', function(arg)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Safe to use Player
end)

-- BAD
RegisterNetEvent('myEvent', function(arg)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    -- No check - will error if player doesn't exist!
    Player.Functions.AddMoney('cash', 100)
end)

2. Validate Client Input

-- GOOD
RegisterNetEvent('shop:purchase', function(itemName, amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate item exists
    if not RSGCore.Shared.Items[itemName] then
        return
    end

    -- Validate amount
    if type(amount) ~= 'number' or amount < 1 or amount > 100 then
        return
    end

    -- Process purchase
end)

3. Use Meaningful Event Names

-- GOOD
RegisterNetEvent('banking:server:deposit', ...)
RegisterNetEvent('inventory:server:giveItem', ...)
RegisterNetEvent('police:server:handcuff', ...)

-- BAD
RegisterNetEvent('doStuff', ...)
RegisterNetEvent('event1', ...)
RegisterNetEvent('handle', ...)

4. Handle Permissions Properly

-- GOOD
RegisterNetEvent('admin:giveWeapon', function(targetId, weapon)
    local src = source

    if not RSGCore.Functions.HasPermission(src, 'admin') then
        return -- Silent fail or notify
    end

    -- Admin action
end)

Common Patterns

Pattern 1: Transaction Event

RegisterNetEvent('banking:server:deposit', function(amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate amount
    if type(amount) ~= 'number' or amount < 1 then
        lib.notify(src, {description = 'Invalid amount', type = 'error'})
        return
    end

    -- Check player has cash
    if Player.Functions.GetMoney('cash') < amount then
        lib.notify(src, {description = 'Not enough cash', type = 'error'})
        return
    end

    -- Process deposit
    Player.Functions.RemoveMoney('cash', amount, 'bank-deposit')
    Player.Functions.AddMoney('bank', amount, 'bank-deposit')

    lib.notify(src, {
        description = 'Deposited $' .. amount,
        type = 'success'
    })
end)

Pattern 2: Item Give/Take Event

RegisterNetEvent('admin:giveItem', function(targetId, itemName, amount)
    local src = source

    -- Permission check
    if not RSGCore.Functions.HasPermission(src, 'admin') then
        return
    end

    -- Validate inputs
    local Target = RSGCore.Functions.GetPlayer(targetId)
    if not Target then return end

    if not RSGCore.Shared.Items[itemName] then return end

    amount = tonumber(amount) or 1

    -- Give item
    exports['rsg-inventory']:AddItem(targetId, itemName, amount, nil, nil, 'admin-give')

    lib.notify(src, {
        description = 'Gave item to player',
        type = 'success'
    })
end)

Pattern 3: Proximity Check Event

RegisterNetEvent('shop:server:purchase', function(shopId, itemName)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Get shop location
    local shopCoords = Config.Shops[shopId].coords

    -- Check if player is near
    local ped = GetPlayerPed(src)
    local playerCoords = GetEntityCoords(ped)
    local distance = #(playerCoords - shopCoords)

    if distance > 5.0 then
        lib.notify(src, {description = 'Too far from shop', type = 'error'})
        return
    end

    -- Process purchase
end)

Troubleshooting

Event Not Triggering

Make sure the event name matches exactly between client and server
Use RegisterNetEvent for networked events triggered across client/server
Always validate source exists before using it

Player Not Found

-- Common issue
RegisterNetEvent('myEvent', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)

    if not Player then
        print('Player not loaded yet or disconnected')
        return
    end
end)

Permission Denied

-- Always check permissions
if not RSGCore.Functions.HasPermission(src, 'admin') then
    print('Player', src, 'tried to use admin command')
    return
end

Summary

EventPurposePermission Required
RSGCore:Server:OnPlayerLoadedPlayer loginNone
RSGCore:Server:OnPlayerUnloadPlayer logoutNone
RSGCore:Server:CloseServerClose serverAdmin
RSGCore:Server:OpenServerOpen serverAdmin
RSGCore:UpdatePlayerUpdate needsNone
RSGCore:Server:SetMetaDataSet player metadataNone
RSGCore:ToggleDutyToggle job dutyNone
RSGCore:Server:GivePermissionGrant permissionsGod
RSGCore:Server:RemovePermissionRemove permissionsGod

Next Steps


Need more help? Join the RSG Framework Discord!