Skip to main content

Introduction

The Player Data system is the heart of RSG Frameworkโ€™s player management. It handles all player information including character data, jobs, gangs, money, metadata, and inventory. Understanding this system is essential for creating resources that interact with players. Location: resources/[framework]/rsg-core/server/player.lua
Player data is automatically saved to the database at intervals defined in config (UpdateInterval), and on logout.

Core Features

๐Ÿ“‹ Comprehensive Data Management

  • Character Info: Names, birthdate, gender, nationality
  • Money System: Multiple currency types with transaction logging
  • Jobs & Gangs: Full hierarchy system with grades and boss status
  • Metadata: Health, hunger, thirst, stress, and custom data
  • Reputation: Flexible reputation system for any activity

๐Ÿ”„ Real-time Synchronization

  • StateBags: Instant sync for frequently updated data
  • Events: Server and client events for data changes
  • Auto-save: Periodic database persistence

โš™๏ธ Flexible API

  • Player Functions: Comprehensive function set for data manipulation
  • Direct Access: Read player data directly from PlayerData table
  • Extension: Add custom methods and fields dynamically

Core Server Functions

RSGCore.Player.Login

  • On player login, gets their data or sets default values
function RSGCore.Player.Login(source, citizenid, newData)
    if source and source ~= '' then
        if citizenid then
            local license = RSGCore.Functions.GetIdentifier(source, 'license')
            local PlayerData = MySQL.prepare.await('SELECT * FROM players where citizenid = ?', { citizenid })
            if PlayerData and license == PlayerData.license then
                PlayerData.money = json.decode(PlayerData.money)
                PlayerData.job = json.decode(PlayerData.job)
                PlayerData.gang = json.decode(PlayerData.gang)
                PlayerData.position = json.decode(PlayerData.position)
                PlayerData.metadata = json.decode(PlayerData.metadata)
                PlayerData.charinfo = json.decode(PlayerData.charinfo)
                RSGCore.Player.CheckPlayerData(source, PlayerData)
            else
                DropPlayer(source, Lang:t('info.exploit_dropped'))
                TriggerEvent('rsg-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Joining Exploit', false)
            end
        else
            RSGCore.Player.CheckPlayerData(source, newData)
        end
        return true
    else
        RSGCore.ShowError(resourceName, 'ERROR RSGCore.PLAYER.LOGIN - NO SOURCE GIVEN!')
        return false
    end
end

RSGCore.Player.CheckPlayerData

  • Function called above on player join to gather player data (this is where you can add/remove additional player data)
function RSGCore.Player.CheckPlayerData(source, PlayerData)
    PlayerData = PlayerData or {}
    local Offline = not source

    if source then
        PlayerData.source = source
        PlayerData.license = PlayerData.license or RSGCore.Functions.GetIdentifier(source, 'license')
        PlayerData.name = GetPlayerName(source)
    end

    local validatedJob = false
    if PlayerData.job and PlayerData.job.name ~= nil and PlayerData.job.grade and PlayerData.job.grade.level ~= nil then
        local jobInfo = RSGCore.Shared.Jobs[PlayerData.job.name]

        if jobInfo then
            local jobGradeInfo = jobInfo.grades[tostring(PlayerData.job.grade.level)]
            if jobGradeInfo then
                PlayerData.job.label = jobInfo.label
                PlayerData.job.grade.name = jobGradeInfo.name
                PlayerData.job.payment = jobGradeInfo.payment
                PlayerData.job.grade.isboss = jobGradeInfo.isboss or false
                PlayerData.job.isboss = jobGradeInfo.isboss or false
                validatedJob = true
            end
        end
    end

    if validatedJob == false then
        -- set to nil, as the default job (unemployed) will be added by `applyDefaults`
        PlayerData.job = nil
    end

    local validatedGang = false
    if PlayerData.gang and PlayerData.gang.name ~= nil and PlayerData.gang.grade and PlayerData.gang.grade.level ~= nil then
        local gangInfo = RSGCore.Shared.Gangs[PlayerData.gang.name]

        if gangInfo then
            local gangGradeInfo = gangInfo.grades[tostring(PlayerData.gang.grade.level)]
            if gangGradeInfo then
                PlayerData.gang.label = gangInfo.label
                PlayerData.gang.grade.name = gangGradeInfo.name
                PlayerData.gang.payment = gangGradeInfo.payment
                PlayerData.gang.grade.isboss = gangGradeInfo.isboss or false
                PlayerData.gang.isboss = gangGradeInfo.isboss or false
                validatedGang = true
            end
        end
    end

    if validatedGang == false then
        -- set to nil, as the default gang (unemployed) will be added by `applyDefaults`
        PlayerData.gang = nil
    end

    applyDefaults(PlayerData, RSGCore.Config.Player.PlayerDefaults)

    if GetResourceState('rsg-inventory') ~= 'missing' then
        PlayerData.items = exports['rsg-inventory']:LoadInventory(PlayerData.source, PlayerData.citizenid)
    end

    return RSGCore.Player.CreatePlayer(PlayerData, Offline)
end

RSGCore.Player.Logout

  • Saves player on logout and removes them from active players table
function RSGCore.Player.Logout(source)
    TriggerClientEvent('RSGCore:Client:OnPlayerUnload', source)
    TriggerEvent('RSGCore:Server:OnPlayerUnload', source)
    TriggerClientEvent('RSGCore:Player:UpdatePlayerData', source)
    Wait(200)
    RSGCore.Players[source] = nil
end

RSGCore.Player.CreatePlayer

  • Creates a new character and sets default data (any function inside the self table can be called on the player after using the GetPlayer function)
function RSGCore.Player.CreatePlayer(PlayerData, Offline)
    local self = {}
    self.Functions = {}
    self.PlayerData = PlayerData
    self.Offline = Offline

    function self.Functions.UpdatePlayerData()
        if self.Offline then return end
        TriggerEvent('RSGCore:Player:SetPlayerData', self.PlayerData)
        TriggerClientEvent('RSGCore:Player:SetPlayerData', self.PlayerData.source, self.PlayerData)
    end

    function self.Functions.SetJob(job, grade)
        job = job:lower()
        grade = grade or '0'
        if not RSGCore.Shared.Jobs[job] then return false end
        self.PlayerData.job = {
            name = job,
            label = RSGCore.Shared.Jobs[job].label,
            onduty = RSGCore.Shared.Jobs[job].defaultDuty,
            type = RSGCore.Shared.Jobs[job].type or 'none',
            grade = {
                name = 'No Grades',
                level = 0,
                payment = 30,
                isboss = false
            }
        }
        local gradeKey = tostring(grade)
        local jobGradeInfo = RSGCore.Shared.Jobs[job].grades[gradeKey]
        if jobGradeInfo then
            self.PlayerData.job.grade.name = jobGradeInfo.name
            self.PlayerData.job.grade.level = tonumber(gradeKey)
            self.PlayerData.job.grade.payment = jobGradeInfo.payment
            self.PlayerData.job.grade.isboss = jobGradeInfo.isboss or false
            self.PlayerData.job.isboss = jobGradeInfo.isboss or false
        end

        if not self.Offline then
            self.Functions.UpdatePlayerData()
            TriggerEvent('RSGCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
            TriggerClientEvent('RSGCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
        end

        return true
    end

    function self.Functions.SetGang(gang, grade)
        gang = gang:lower()
        grade = grade or '0'
        if not RSGCore.Shared.Gangs[gang] then return false end
        self.PlayerData.gang = {
            name = gang,
            label = RSGCore.Shared.Gangs[gang].label,
            grade = {
                name = 'No Grades',
                level = 0,
                isboss = false
            }
        }
        local gradeKey = tostring(grade)
        local gangGradeInfo = RSGCore.Shared.Gangs[gang].grades[gradeKey]
        if gangGradeInfo then
            self.PlayerData.gang.grade.name = gangGradeInfo.name
            self.PlayerData.gang.grade.level = tonumber(gradeKey)
            self.PlayerData.gang.grade.isboss = gangGradeInfo.isboss or false
            self.PlayerData.gang.isboss = gangGradeInfo.isboss or false
        end

        if not self.Offline then
            self.Functions.UpdatePlayerData()
            TriggerEvent('RSGCore:Server:OnGangUpdate', self.PlayerData.source, self.PlayerData.gang)
            TriggerClientEvent('RSGCore:Client:OnGangUpdate', self.PlayerData.source, self.PlayerData.gang)
        end

        return true
    end

    function self.Functions.HasItem(items, amount)
        return RSGCore.Functions.HasItem(self.PlayerData.source, items, amount)
    end

    function self.Functions.SetJobDuty(onDuty)
        self.PlayerData.job.onduty = not not onDuty
        TriggerEvent('RSGCore:Server:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
        TriggerClientEvent('RSGCore:Client:OnJobUpdate', self.PlayerData.source, self.PlayerData.job)
        self.Functions.UpdatePlayerData()
    end

    function self.Functions.SetPlayerData(key, val)
        if not key or type(key) ~= 'string' then return end
        self.PlayerData[key] = val
        self.Functions.UpdatePlayerData()
    end

    function self.Functions.SetMetaData(meta, val)
        if not meta or type(meta) ~= 'string' then return end
        if meta == 'hunger' or meta == 'thirst' or meta == 'cleanliness' then
            val = val > 100 and 100 or val
        end
        self.PlayerData.metadata[meta] = val
        self.Functions.UpdatePlayerData()
    end

    function self.Functions.GetMetaData(meta)
        if not meta or type(meta) ~= 'string' then return end
        return self.PlayerData.metadata[meta]
    end

    function self.Functions.AddRep(rep, amount)
        if not rep or not amount then return end
        local addAmount = tonumber(amount)
        local currentRep = self.PlayerData.metadata['rep'][rep] or 0
        self.PlayerData.metadata['rep'][rep] = currentRep + addAmount
        self.Functions.UpdatePlayerData()
    end

    function self.Functions.RemoveRep(rep, amount)
        if not rep or not amount then return end
        local removeAmount = tonumber(amount)
        local currentRep = self.PlayerData.metadata['rep'][rep] or 0
        if currentRep - removeAmount < 0 then
            self.PlayerData.metadata['rep'][rep] = 0
        else
            self.PlayerData.metadata['rep'][rep] = currentRep - removeAmount
        end
        self.Functions.UpdatePlayerData()
    end

    function self.Functions.GetRep(rep)
        if not rep then return end
        return self.PlayerData.metadata['rep'][rep] or 0
    end

    function self.Functions.AddMoney(moneytype, amount, reason)
        reason = reason or 'unknown'
        moneytype = moneytype:lower()
        amount = tonumber(amount)
        if amount < 0 then return end
        if not self.PlayerData.money[moneytype] then return false end
        self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] + amount

        if not self.Offline then
            self.Functions.UpdatePlayerData()
            if amount > 100000 then
                TriggerEvent('rsg-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason, true)
            else
                TriggerEvent('rsg-log:server:CreateLog', 'playermoney', 'AddMoney', 'lightgreen', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') added, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason)
            end
            TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, false)
            TriggerClientEvent('RSGCore:Client:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'add', reason)
            TriggerEvent('RSGCore:Server:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'add', reason)
        end

        return true
    end

    function self.Functions.RemoveMoney(moneytype, amount, reason)
        reason = reason or 'unknown'
        moneytype = moneytype:lower()
        amount = tonumber(amount)
        if amount < 0 then return end
        if not self.PlayerData.money[moneytype] then return false end
        for _, mtype in pairs(RSGCore.Config.Money.DontAllowMinus) do
            if mtype == moneytype then
                if (self.PlayerData.money[moneytype] - amount) < 0 then
                    return false
                end
            end
        end
        self.PlayerData.money[moneytype] = self.PlayerData.money[moneytype] - amount

        if not self.Offline then
            self.Functions.UpdatePlayerData()
            if amount > 100000 then
                TriggerEvent('rsg-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason, true)
            else
                TriggerEvent('rsg-log:server:CreateLog', 'playermoney', 'RemoveMoney', 'red', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') removed, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason)
            end
            TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, amount, true)
            if moneytype == 'bank' then
                TriggerClientEvent('rsg-phone:client:RemoveBankMoney', self.PlayerData.source, amount)
            end
            TriggerClientEvent('RSGCore:Client:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'remove', reason)
            TriggerEvent('RSGCore:Server:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'remove', reason)
        end

        return true
    end

    function self.Functions.SetMoney(moneytype, amount, reason)
        reason = reason or 'unknown'
        moneytype = moneytype:lower()
        amount = tonumber(amount)
        if amount < 0 then return false end
        if not self.PlayerData.money[moneytype] then return false end
        local difference = amount - self.PlayerData.money[moneytype]
        self.PlayerData.money[moneytype] = amount

        if not self.Offline then
            self.Functions.UpdatePlayerData()
            TriggerEvent('rsg-log:server:CreateLog', 'playermoney', 'SetMoney', 'green', '**' .. GetPlayerName(self.PlayerData.source) .. ' (citizenid: ' .. self.PlayerData.citizenid .. ' | id: ' .. self.PlayerData.source .. ')** $' .. amount .. ' (' .. moneytype .. ') set, new ' .. moneytype .. ' balance: ' .. self.PlayerData.money[moneytype] .. ' reason: ' .. reason)
            TriggerClientEvent('hud:client:OnMoneyChange', self.PlayerData.source, moneytype, math.abs(difference), difference < 0)
            TriggerClientEvent('RSGCore:Client:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'set', reason)
            TriggerEvent('RSGCore:Server:OnMoneyChange', self.PlayerData.source, moneytype, amount, 'set', reason)
        end

        return true
    end

    function self.Functions.GetMoney(moneytype)
        if not moneytype then return false end
        moneytype = moneytype:lower()
        return self.PlayerData.money[moneytype]
    end

    function self.Functions.Save()
        if self.Offline then
            RSGCore.Player.SaveOffline(self.PlayerData)
        else
            RSGCore.Player.Save(self.PlayerData.source)
        end
    end

    function self.Functions.Logout()
        if self.Offline then return end
        RSGCore.Player.Logout(self.PlayerData.source)
    end

    function self.Functions.AddMethod(methodName, handler)
        self.Functions[methodName] = handler
    end

    function self.Functions.AddField(fieldName, data)
        self[fieldName] = data
    end

    if self.Offline then
        return self
    else
        RSGCore.Players[self.PlayerData.source] = self
        RSGCore.Player.Save(self.PlayerData.source)
        TriggerEvent('RSGCore:Server:PlayerLoaded', self)
        self.Functions.UpdatePlayerData()
    end
end

RSGCore.Player.Save

  • Saves the playerโ€™s info to the database
function RSGCore.Player.Save(source)
    local ped = GetPlayerPed(source)
    local pcoords = GetEntityCoords(ped)
    local PlayerData = RSGCore.Players[source].PlayerData
    if PlayerData then
        MySQL.insert('INSERT INTO players (citizenid, cid, license, name, money, charinfo, job, gang, position, metadata) VALUES (:citizenid, :cid, :license, :name, :money, :charinfo, :job, :gang, :position, :metadata) ON DUPLICATE KEY UPDATE cid = :cid, name = :name, money = :money, charinfo = :charinfo, job = :job, gang = :gang, position = :position, metadata = :metadata', {
            citizenid = PlayerData.citizenid,
            cid = tonumber(PlayerData.cid),
            license = PlayerData.license,
            name = PlayerData.name,
            money = json.encode(PlayerData.money),
            charinfo = json.encode(PlayerData.charinfo),
            job = json.encode(PlayerData.job),
            gang = json.encode(PlayerData.gang),
            position = json.encode(pcoords),
            metadata = json.encode(PlayerData.metadata)
        })
        if GetResourceState('rsg-inventory') ~= 'missing' then exports['rsg-inventory']:SaveInventory(source) end
        RSGCore.ShowSuccess(resourceName, PlayerData.name .. ' PLAYER SAVED!')
    else
        RSGCore.ShowError(resourceName, 'ERROR RSGCore.PLAYER.SAVE - PLAYERDATA IS EMPTY!')
    end
end

RSGCore.Player.DeleteCharacter

  • Deletes a character and all corresponding data in the database
local playertables = { -- Add tables as needed
    { table = 'players'},
    { table = 'playeroutfit'},
    { table = 'playerskins'},
    { table = 'player_horses'},
    { table = 'player_weapons'},
    { table = 'address_book'},
    { table = 'telegrams'},
}

function RSGCore.Player.DeleteCharacter(source, citizenid)
    local license = RSGCore.Functions.GetIdentifier(source, 'license')
    local result = MySQL.scalar.await('SELECT license FROM players where citizenid = ?', { citizenid })
    if license == result then
        local query = 'DELETE FROM %s WHERE citizenid = ?'
        local tableCount = #playertables
        local queries = table.create(tableCount, 0)

        for i = 1, tableCount do
            local v = playertables[i]
            queries[i] = { query = query:format(v.table), values = { citizenid } }
        end

        MySQL.transaction(queries, function(result2)
            if result2 then
                TriggerEvent('rsg-log:server:CreateLog', 'joinleave', 'Character Deleted', 'red', '**' .. GetPlayerName(source) .. '** ' .. license .. ' deleted **' .. citizenid .. '**..')
            end
        end)
    else
        DropPlayer(source, Lang:t('info.exploit_dropped'))
        TriggerEvent('rsg-log:server:CreateLog', 'anticheat', 'Anti-Cheat', 'white', GetPlayerName(source) .. ' Has Been Dropped For Character Deletion Exploit', true)
    end
end

Player Object Functions

Once you have a Player object (via RSGCore.Functions.GetPlayer(source)), you can use the following functions on that player:

Player.Functions.SetJob

  • Sets a playerโ€™s job and grade
function Player.Functions.SetJob(job, grade)
    job = job:lower()
    grade = grade or '0'
    -- Sets the job and triggers events
    return true/false
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetJob('vallaw', '2') -- Set to Valentine Law Sheriff

Player.Functions.SetGang

  • Sets a playerโ€™s gang and grade
function Player.Functions.SetGang(gang, grade)
    gang = gang:lower()
    grade = grade or '0'
    -- Sets the gang and triggers events
    return true/false
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetGang('odriscoll', '1') -- Set to O'Driscoll Enforcer

Player.Functions.SetJobDuty

  • Sets a playerโ€™s on/off duty status for their job
function Player.Functions.SetJobDuty(onDuty)
    -- Sets duty status and triggers events
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetJobDuty(true) -- Clock player in
Player.Functions.SetJobDuty(false) -- Clock player out

Player.Functions.AddMoney

  • Add money to a playerโ€™s account
function Player.Functions.AddMoney(moneytype, amount, reason)
    -- Adds money and triggers events/logs
    return true/false
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
if Player.Functions.AddMoney('cash', 100, 'Sold item') then
    print('Added $100 cash to player')
end

-- Different money types: 'cash', 'bank', 'valbank', 'rhobank', 'blkbank', 'armbank', 'bloodmoney'

Player.Functions.RemoveMoney

  • Remove money from a playerโ€™s account
function Player.Functions.RemoveMoney(moneytype, amount, reason)
    -- Removes money if player has enough
    return true/false
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
if Player.Functions.RemoveMoney('cash', 50, 'Bought item') then
    print('Removed $50 cash from player')
else
    print('Player does not have enough money')
end

Player.Functions.SetMoney

  • Set a playerโ€™s money to a specific amount
function Player.Functions.SetMoney(moneytype, amount, reason)
    -- Sets money to exact amount
    return true/false
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetMoney('bank', 5000, 'Admin set money')

Player.Functions.GetMoney

  • Get the amount of money a player has in a specific account
function Player.Functions.GetMoney(moneytype)
    return amount
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
local cashAmount = Player.Functions.GetMoney('cash')
print('Player has $' .. cashAmount .. ' cash')

Player.Functions.SetMetaData

  • Set metadata for a player
function Player.Functions.SetMetaData(meta, val)
    -- Sets metadata (hunger, thirst, cleanliness are clamped to 0-100)
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetMetaData('hunger', 75) -- Set hunger to 75
Player.Functions.SetMetaData('thirst', 80) -- Set thirst to 80
Player.Functions.SetMetaData('isdead', false) -- Set player as alive
Player.Functions.SetMetaData('armor', 100) -- Set armor to 100

-- You can also pass a table to set multiple at once
Player.Functions.SetMetaData({
    hunger = 100,
    thirst = 100,
    stress = 0
})

Player.Functions.GetMetaData

  • Get metadata for a player
function Player.Functions.GetMetaData(meta)
    return value
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
local hunger = Player.Functions.GetMetaData('hunger')
if hunger < 20 then
    print('Player is very hungry!')
end

Player.Functions.AddRep

  • Add reputation points for a specific reputation type
function Player.Functions.AddRep(rep, amount)
    -- Adds reputation points to metadata.rep[rep]
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.AddRep('hunting', 10)   -- Add 10 hunting rep
Player.Functions.AddRep('fishing', 5)    -- Add 5 fishing rep
Player.Functions.AddRep('lawman', 25)    -- Add 25 lawman rep
Reputation System: The framework includes a flexible reputation system stored in metadata.rep. You can create any reputation types you need (hunting, fishing, crafting, lawman, outlaw, etc.)

Player.Functions.RemoveRep

  • Remove reputation points for a specific reputation type
function Player.Functions.RemoveRep(rep, amount)
    -- Removes reputation points (won't go below 0)
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.RemoveRep('hunting', 5) -- Remove 5 hunting rep

-- Reputation is clamped at 0 (won't go negative)
Player.Functions.RemoveRep('lawman', 1000) -- If player has 50, it goes to 0, not -950

Player.Functions.GetRep

  • Get reputation points for a specific reputation type
function Player.Functions.GetRep(rep)
    return amount  -- Returns 0 if reputation type doesn't exist
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
local huntingRep = Player.Functions.GetRep('hunting')
if huntingRep >= 100 then
    print('Player is a master hunter!')
    -- Give special rewards or unlock content
end

-- Check multiple reputations
local totalRep = 0
totalRep = totalRep + Player.Functions.GetRep('hunting')
totalRep = totalRep + Player.Functions.GetRep('fishing')
totalRep = totalRep + Player.Functions.GetRep('crafting')
print('Player total rep: '.. totalRep)
Common Reputation Types:
  • hunting - Tracking, animal kills, perfect pelts
  • fishing - Fish caught, legendary fish
  • crafting - Items crafted, quality of work
  • lawman - Criminal arrests, bounties collected
  • outlaw - Crimes committed, bounties escaped
  • trader - Goods sold, trading reputation
  • doctor - Patients healed, medical services
  • Custom types as needed for your server
Reputation Use Cases:
-- Grant bonuses based on reputation
RegisterNetEvent('hunting:server:sellPelt', function(peltType, quality)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    local huntingRep = Player.Functions.GetRep('hunting')
    local basePrice = 10
    local repBonus = math.floor(huntingRep / 10) -- 1% bonus per 10 rep

    local finalPrice = basePrice + (basePrice * repBonus / 100)

    Player.Functions.AddMoney('cash', finalPrice, 'pelt-sale')
    Player.Functions.AddRep('hunting', 2) -- Gain rep for selling

    TriggerClientEvent('ox_lib:notify', src, {
        description = 'Sold pelt for $'.. finalPrice ..' (+' .. repBonus ..'% rep bonus)',
        type = 'success'
    })
end)

-- Lock content behind reputation requirements
RegisterNetEvent('shop:server:buySpecialItem', function(itemName)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    local requiredRep = 50
    local playerRep = Player.Functions.GetRep('trader')

    if playerRep < requiredRep then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Requires '.. requiredRep ..' trader reputation (you have '.. playerRep ..')',
            type = 'error'
        })
        return
    end

    -- Process purchase...
end)

Player.Functions.HasItem

  • Check if player has a specific item
function Player.Functions.HasItem(items, amount)
    return RSGCore.Functions.HasItem(source, items, amount)
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
if Player.Functions.HasItem('bread', 1) then
    print('Player has bread')
end

Player.Functions.SetPlayerData

  • Set any player data field
function Player.Functions.SetPlayerData(key, val)
    -- Sets player data and updates
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetPlayerData('weight', 50000) -- Change inventory weight
Player.Functions.SetPlayerData('slots', 30) -- Change inventory slots

Player.Functions.Save

  • Manually save a playerโ€™s data to database
function Player.Functions.Save()
    -- Saves player data to database
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
-- Make changes to player data
Player.Functions.SetMoney('cash', 1000, 'test')
-- Manually save
Player.Functions.Save()

Player.Functions.Logout

  • Log a player out (removes from active players)
function Player.Functions.Logout()
    -- Logs player out and triggers events
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.Logout()

Player.Functions.UpdatePlayerData

  • Trigger an update of player data to client
function Player.Functions.UpdatePlayerData()
    -- Updates player data on client
end

-- Example

local Player = RSGCore.Functions.GetPlayer(source)
-- Make changes
Player.PlayerData.charinfo.firstname = 'John'
-- Manually trigger update
Player.Functions.UpdatePlayerData()

Accessing Player Data

Once you have a Player object, you can access their data directly:
local Player = RSGCore.Functions.GetPlayer(source)

-- Access character info
local firstname = Player.PlayerData.charinfo.firstname
local lastname = Player.PlayerData.charinfo.lastname
local birthdate = Player.PlayerData.charinfo.birthdate
local gender = Player.PlayerData.charinfo.gender
local account = Player.PlayerData.charinfo.account

-- Access money
local cash = Player.PlayerData.money.cash
local bank = Player.PlayerData.money.bank

-- Access job info
local jobName = Player.PlayerData.job.name
local jobLabel = Player.PlayerData.job.label
local jobGrade = Player.PlayerData.job.grade.level
local jobGradeName = Player.PlayerData.job.grade.name
local jobPayment = Player.PlayerData.job.payment
local onDuty = Player.PlayerData.job.onduty
local isBoss = Player.PlayerData.job.isboss

-- Access gang info
local gangName = Player.PlayerData.gang.name
local gangLabel = Player.PlayerData.gang.label
local gangGrade = Player.PlayerData.gang.grade.level
local gangIsBoss = Player.PlayerData.gang.isboss

-- Access metadata
local hunger = Player.PlayerData.metadata.hunger
local thirst = Player.PlayerData.metadata.thirst
local cleanliness = Player.PlayerData.metadata.cleanliness
local stress = Player.PlayerData.metadata.stress
local isDead = Player.PlayerData.metadata.isdead
local armor = Player.PlayerData.metadata.armor
local bloodtype = Player.PlayerData.metadata.bloodtype
local fingerprint = Player.PlayerData.metadata.fingerprint
local walletid = Player.PlayerData.metadata.walletid
local callsign = Player.PlayerData.metadata.callsign

-- Access other info
local citizenid = Player.PlayerData.citizenid
local license = Player.PlayerData.license
local name = Player.PlayerData.name
local source = Player.PlayerData.source

StateBags System

The RSG Framework uses FiveMโ€™s StateBags system to sync specific metadata between server and client in real-time. This provides better performance than constantly triggering events.

What are StateBags?

StateBags are key-value stores attached to entities (players, vehicles, etc.) that automatically sync between server and client. Theyโ€™re more efficient than events for frequently updated data. Synced Metadata Keys:
  • hunger (0-100)
  • thirst (0-100)
  • cleanliness (0-100)
  • stress (0-100)
  • health (0-600)
  • isLoggedIn (boolean)

How StateBags Work

-- SERVER SIDE - Automatically synced on player load
function Player.Functions.InitializeStateBags()
    local metadata = self.PlayerData.metadata
    local keys = { "hunger", "thirst", "cleanliness", "stress", "health" }

    local state = Player(self.PlayerData.source).state
    for _, key in ipairs(keys) do
        if metadata[key] ~= nil then
            state[key] = metadata[key]  -- Automatically syncs to client
        end
    end
end

-- When player data saves, StateBags are persisted back to metadata
function Player.Functions.PersistStateBags()
    local metadata = {}
    local keys = { "hunger", "thirst", "cleanliness", "stress", "health" }

    local state = Player(self.PlayerData.source).state
    for _, key in ipairs(keys) do
        if state[key] ~= nil then
            metadata[key] = state[key]
        end
    end

    if next(metadata) then
        self.Functions.SetMetaData(metadata)
    end
end

Reading StateBags (Client-Side)

-- CLIENT SIDE
-- Read player's own state
local hunger = LocalPlayer.state.hunger
local thirst = LocalPlayer.state.thirst
local health = LocalPlayer.state.health

if hunger < 20 then
    print('You are very hungry!')
end

-- Watch for state changes
AddStateBagChangeHandler('hunger', nil, function(bagName, key, value)
    if bagName ~= ('player:%s'):format(GetPlayerServerId(PlayerId())) then return end
    print('Your hunger changed to: '.. value)
    -- Update UI, trigger effects, etc.
end)

-- Read another player's state
local targetPlayerId = GetPlayerFromServerId(targetServerId)
local targetState = Player(targetPlayerId).state
local targetHunger = targetState.hunger

Using StateBags in Resources

-- SERVER SIDE - Update state directly
local state = Player(source).state
state.hunger = 50  -- Automatically syncs to client

-- Or use the player function (recommended)
local Player = RSGCore.Functions.GetPlayer(source)
Player.Functions.SetMetaData('hunger', 50)  -- This updates StateBag automatically

-- CLIENT SIDE - React to state changes
CreateThread(function()
    while true do
        Wait(1000)
        local hunger = LocalPlayer.state.hunger or 100
        local thirst = LocalPlayer.state.thirst or 100

        if hunger < 20 then
            -- Show hunger warning
            -- Apply debuffs
        end

        if thirst < 20 then
            -- Show thirst warning
            -- Apply debuffs
        end
    end
end)

-- Listen for login state
AddStateBagChangeHandler('isLoggedIn', nil, function(bagName, key, value)
    if bagName ~= ('player:%s'):format(GetPlayerServerId(PlayerId())) then return end

    if value then
        print('Player is now logged in')
        -- Initialize client-side systems
    else
        print('Player logged out')
        -- Clean up client-side systems
    end
end)

Benefits of StateBags

Performance: No need to constantly trigger events for metadata updates
Real-time: Changes sync instantly without event delays
Clean Code: Access data directly instead of callback hell

When to Use StateBags vs Events

Use StateBags for:
  • Frequently updated data (hunger, thirst, health)
  • Data that needs instant client access
  • Simple value syncing
Use Events for:
  • Complex data updates
  • Actions that need validation
  • One-time notifications

Example: Custom HUD Using StateBags

-- CLIENT SIDE
local function UpdateHUD()
    local hunger = LocalPlayer.state.hunger or 100
    local thirst = LocalPlayer.state.thirst or 100
    local health = LocalPlayer.state.health or 600
    local stress = LocalPlayer.state.stress or 0

    -- Send to NUI
    SendNUIMessage({
        action = 'updateHud',
        hunger = hunger,
        thirst = thirst,
        health = health,
        stress = stress
    })
end

-- Update HUD every second
CreateThread(function()
    while true do
        Wait(1000)
        if LocalPlayer.state.isLoggedIn then
            UpdateHUD()
        end
    end
end)

-- Also update immediately when states change
AddStateBagChangeHandler('hunger', nil, function(bagName, key, value)
    if bagName ~= ('player:%s'):format(GetPlayerServerId(PlayerId())) then return end
    UpdateHUD()
end)

AddStateBagChangeHandler('thirst', nil, function(bagName, key, value)
    if bagName ~= ('player:%s'):format(GetPlayerServerId(PlayerId())) then return end
    UpdateHUD()
end)

Complete Metadata Structure

The full metadata table structure in PlayerData:
metadata = {
    -- Basic Stats (synced via StateBags)
    health = 600,               -- Player health (0-600, RedM scale)
    hunger = 100,               -- Hunger level (0-100)
    thirst = 100,               -- Thirst level (0-100)
    cleanliness = 100,          -- Cleanliness level (0-100)
    stress = 0,                 -- Stress level (0-100)

    -- Status Flags
    isdead = false,             -- Is player dead
    ishandcuffed = false,       -- Is player handcuffed
    armor = 0,                  -- Armor value

    -- Jail System
    injail = 0,                 -- Jail time remaining (minutes)
    jailitems = {},             -- Items confiscated during arrest

    -- Character Info
    bloodtype = 'O+',           -- Blood type (randomly assigned)
    fingerprint = 'AB123CD456', -- Unique fingerprint ID
    walletid = 'WL789XY012',    -- Wallet ID
    callsign = 'NO CALLSIGN',   -- Job callsign (for law/medic)

    -- Criminal Record
    criminalrecord = {
        hasRecord = false,      -- Has criminal record
        date = nil              -- Date of last offense
    },

    -- Reputation System
    rep = {
        hunting = 0,            -- Hunting reputation
        fishing = 0,            -- Fishing reputation
        -- Add custom reputation types as needed
    },

    -- Custom Status Effects
    status = {},                -- Custom status effects table
}

Best Practices

Always Check Player Exists: Before accessing player data, always verify the player object exists to prevent errors
local Player = RSGCore.Functions.GetPlayer(source)
if not Player then return end

-- Now safe to use Player functions
Donโ€™t Modify PlayerData Directly: Always use Player.Functions to modify data - this ensures events fire and data syncs properly
-- Bad
Player.PlayerData.money.cash = 1000

-- Good
Player.Functions.SetMoney('cash', 1000, 'admin-set')
Use StateBags for HUDs: For frequently updated display data (hunger, thirst, health), read from StateBags instead of triggering events

Performance Tips

  1. Batch Updates: When setting multiple metadata values, group them to reduce database writes
  2. Cache Player Objects: Store the Player object in a variable instead of calling GetPlayer multiple times
  3. Use GetRep Sparingly: If checking reputation frequently, cache the value locally
  4. Avoid Save Spam: Donโ€™t call Player.Functions.Save() excessively - let auto-save handle it

Common Patterns

-- Get player with validation
local function GetValidPlayer(source)
    local Player = RSGCore.Functions.GetPlayer(source)
    if not Player then
        TriggerClientEvent('ox_lib:notify', source, {
            description = 'Player data not loaded',
            type = 'error'
        })
        return nil
    end
    return Player
end

-- Check job with grade
local function HasJobWithMinGrade(source, jobName, minGrade)
    local Player = RSGCore.Functions.GetPlayer(source)
    if not Player then return false end

    return Player.PlayerData.job.name == jobName
        and Player.PlayerData.job.grade.level >= minGrade
end

-- Check any law job
local function IsLawEnforcement(source)
    local Player = RSGCore.Functions.GetPlayer(source)
    if not Player then return false end

    return Player.PlayerData.job.type == 'leo'
end

Next Steps


Need help? Join the RSG Framework Discord!