> ## Documentation Index
> Fetch the complete documentation index at: https://rsg.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 📊 Player Data

> Complete guide to accessing and modifying player data in RSG Framework

## 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`

<Info>
  Player data is automatically saved to the database at intervals defined in config (`UpdateInterval`), and on logout.
</Info>

***

## 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

```lua theme={null}
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)

```lua theme={null}
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

```lua theme={null}
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)

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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
```

<Info>
  **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.)
</Info>

### Player.Functions.RemoveRep

* Remove reputation points for a specific reputation type

```lua theme={null}
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

```lua theme={null}
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**:

```lua theme={null}
-- 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

```lua theme={null}
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

```lua theme={null}
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

```lua theme={null}
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)

```lua theme={null}
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

```lua theme={null}
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:

```lua theme={null}
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

```lua theme={null}
-- 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)

```lua theme={null}
-- 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

```lua theme={null}
-- 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

<Check>
  **Performance**: No need to constantly trigger events for metadata updates
</Check>

<Check>
  **Real-time**: Changes sync instantly without event delays
</Check>

<Check>
  **Clean Code**: Access data directly instead of callback hell
</Check>

### 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

```lua theme={null}
-- 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:

```lua theme={null}
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

<Tip>
  **Always Check Player Exists**: Before accessing player data, always verify the player object exists to prevent errors
</Tip>

```lua theme={null}
local Player = RSGCore.Functions.GetPlayer(source)
if not Player then return end

-- Now safe to use Player functions
```

<Warning>
  **Don't Modify PlayerData Directly**: Always use Player.Functions to modify data - this ensures events fire and data syncs properly
</Warning>

```lua theme={null}
-- Bad
Player.PlayerData.money.cash = 1000

-- Good
Player.Functions.SetMoney('cash', 1000, 'admin-set')
```

<Check>
  **Use StateBags for HUDs**: For frequently updated display data (hunger, thirst, health), read from StateBags instead of triggering events
</Check>

### 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

```lua theme={null}
-- 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

* [Server Functions Reference](/api-reference/endpoint/serverfunctionreference) - Complete server function list
* [Configuration Guide](/essentials/configuration) - Configure player defaults
* [Money System](/essentials/money-system) - Complete money system guide
* [StateBags System](/essentials/statebags) - Deep dive into state synchronization
* [Reputation System](/essentials/reputation) - Detailed reputation guide

***

Need help? Join the [RSG Framework Discord](https://discord.gg/rsg-redm-framework-914413479157448744)!
