How to access and manage player characters using the Fetch component and DataStore pattern
The Characters system manages player characters in Mythic Framework, including creation, selection, loading, and data persistence. Character data is accessed through the Fetch component using the DataStore pattern.
Critical: Do NOT use Characters:GetCharacter() - this method does not exist.Always use the Fetch component:
local player = Fetch:Source(source)local char = player:GetData('Character')
The primary way to access character data is through the Fetch component.Pattern:
local player = Fetch:Source(source)if not player then return endlocal char = player:GetData('Character')if not char then return end-- Access character datalocal stateId = char:GetData('SID')local firstName = char:GetData('First')
Example in Event Handler:
-- Server sideRegisterNetEvent('myresource:server:doSomething', function() local src = source local player = Fetch:Source(src) if not player then print('[ERROR] Player not found') return end local char = player:GetData('Character') if not char then TriggerClientEvent('mythic-notifications:client:Send', src, { message = 'You must be logged in as a character', type = 'error' }) return end -- Safe to access character data local stateId = char:GetData('SID') local name = string.format('%s %s', char:GetData('First'), char:GetData('Last')) local cash = char:GetData('Cash') local jobs = char:GetData('Jobs') -- Array of job objects print('Character:', name, 'SID:', stateId, 'Cash:', cash)end)
Character SetData automatically syncs to client with Characters:Client:SetData event.
Examples:
-- Update cashlocal currentCash = char:GetData('Cash')char:SetData('Cash', currentCash + 500)-- Note: Bank balances are managed via the Banking component, not character SetData-- Note: Jobs are managed via the Jobs component: Jobs:GiveJob(stateId, jobId, workplaceId, gradeId)-- Update phone numberchar:SetData('Phone', '555-1234')-- Update nested metadatalocal metadata = char:GetData('MetaData')metadata.hunger = 80metadata.thirst = 90char:SetData('MetaData', metadata)
Get character by player server ID (most common).Example:
-- Get character from sourcelocal player = Fetch:Source(source)if player then local char = player:GetData('Character') if char then print('Found character:', char:GetData('First'), char:GetData('Last')) endend
-- Find character by SIDlocal player = Fetch:SID(123)if player then local char = player:GetData('Character') local source = player:GetData('Source') -- Send notification TriggerClientEvent('mythic-notifications:client:Send', source, { message = 'You received a message', type = 'info' })end
Find player by any character field (phone, name, etc).Example:
-- Find by phone numberlocal player = Fetch:CharacterData('Phone', '555-0123')if player then local char = player:GetData('Character') print('Found:', char:GetData('First'), char:GetData('Last'))end-- Find by SID using CharacterDatalocal player = Fetch:CharacterData('SID', 123)
Get specific data from offline character (database query).Example:
-- Get offline character data (blocking)local phone = Fetch:GetOfflineData(123, 'Phone')local cash = Fetch:GetOfflineData(123, 'Cash')print('Offline character phone:', phone)
GetOfflineData is synchronous and blocks the thread. Use sparingly and only when player is offline.
RegisterNetEvent('myresource:server:action', function() local src = source local player = Fetch:Source(src) if not player then print('[ERROR] Player not found') return end local char = player:GetData('Character') if not char then TriggerClientEvent('mythic-notifications:client:Send', src, { message = 'You must be logged in as a character', type = 'error' }) return end -- Safe to use char now local stateId = char:GetData('SID')end)
local player = Fetch:Source(source)local char = player:GetData('Character')local price = 250local currentCash = char:GetData('Cash')if currentCash >= price then char:SetData('Cash', currentCash - price) Notification:Success(source, string.format('Paid $%d', price))else Notification:Error(source, 'Insufficient funds')end
Checking Cash:
function CanAfford(source, amount) local player = Fetch:Source(source) if not player then return false end local char = player:GetData('Character') if not char then return false end local cash = char:GetData('Cash') return cash >= amountend
Bank balances are managed through the Banking component (Banking.Balance:Get/Deposit/Withdraw/Charge), not through character SetData. See the Banking API for bank operations.
-- Get all online playerslocal players = Fetch:All()for source, player in pairs(players) do local char = player:GetData('Character') if char then local name = string.format('%s %s', char:GetData('First'), char:GetData('Last')) local stateId = char:GetData('SID') print(source, name, stateId) -- Send server-wide event TriggerClientEvent('myresource:client:serverEvent', source, eventData) endendprint('Total characters online:', Fetch:CountCharacters())
-- You have a character SID, need the player sourcefunction GetSourceFromSID(stateId) local player = Fetch:SID(stateId) if player then return player:GetData('Source') end return nilend-- Usagelocal targetSource = GetSourceFromSID(123)if targetSource then TriggerClientEvent('myresource:client:event', targetSource, data)end
Event:Characters:Server:PlayerDroppedTriggered when player disconnects.Parameters:
source (number) - Player server ID
Example:
AddEventHandler('Characters:Server:PlayerDropped', function(source) local player = Fetch:Source(source) if player then local char = player:GetData('Character') if char then print('Character logged out:', char:GetData('SID')) -- Save data TriggerEvent('inventory:server:saveInventory', source) end endend)
local player = Fetch:Source(source)if not player then return endlocal char = player:GetData('Character')if not char then return end
❌ Wrong:
local char = Characters:GetCharacter(source) -- DOESN'T EXIST!
Use SID for Database Operations
Character SID is the primary key for database queries:
-- ✅ Good: Use SIDlocal char = player:GetData('Character')local stateId = char:GetData('SID')Database.Game:findOne({ collection = 'vehicles', query = {owner = stateId}}, callback)-- ❌ Bad: Don't use sourceDatabase.Game:findOne({ collection = 'vehicles', query = {owner = source} -- Wrong! Source is player ID, not character ID}, callback)
DataStore Pattern
Use GetData/SetData methods, not direct table access:
-- ✅ Good: Use DataStore methodslocal firstName = char:GetData('First')char:SetData('Cash', 1000)-- ❌ Bad: Direct access (may not work)local firstName = char.Firstchar.Cash = 1000
Check if Character is Online
function IsCharacterOnline(stateId) return Fetch:SID(stateId) ~= nilend-- Usageif IsCharacterOnline(123) then -- Character is online, use Fetch local player = Fetch:SID(123) local char = player:GetData('Character')else -- Character is offline, use database or GetOfflineData local phone = Fetch:GetOfflineData(123, 'Phone')end
Validate Before Modifying Cash
function ModifyCash(source, amount, reason) local player = Fetch:Source(source) if not player then return false end local char = player:GetData('Character') if not char then return false end local currentCash = char:GetData('Cash') if amount < 0 and currentCash < math.abs(amount) then return false -- Insufficient funds end char:SetData('Cash', currentCash + amount) -- Log transaction Logger:Info('Economy', 'Cash modified', { console = true, file = true, database = true }, { characterSID = char:GetData('SID'), amount = amount, reason = reason }) return trueend-- Note: For bank operations, use Banking.Balance:Deposit/Withdraw/Charge
-- Server sideAddEventHandler('jobs:server:processPaycheck', function() local players = Fetch:All() for source, player in pairs(players) do local char = player:GetData('Character') if char then local jobs = char:GetData('Jobs') -- Array of job objects if jobs and #jobs > 0 then for _, job in ipairs(jobs) do -- Calculate pay based on job local payAmount = GetJobPayAmount(job.Id, job.GradeLevel) if payAmount > 0 then -- Add to cash local currentCash = char:GetData('Cash') char:SetData('Cash', currentCash + payAmount) -- Notify player Notification:Success(source, string.format('Paycheck: $%d received', payAmount)) end end end end endend)
Critical Pattern: Always use Fetch:Source(source) to get the player, then player:GetData('Character') to get the character. This is the ONLY correct way to access character data.