Server Events
All character events fire on the server-side.Lifecycle Events
Created, Selected, Deleted
State Changes
Job changes, money changes, updates
Persistent
Save and load events
Hookable
Add custom logic to character operations
Lifecycle Events
mythic-characters:server:CharacterCreated
Fired when a new character is successfully created.AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
-- Handler code
end)
Player server ID who created the character
Newly created character object with all fields
-- Give starter items
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
-- Create inventory
Inventory:Create(character.SID)
-- Give starter items
Inventory:AddItem(character.SID, 'phone', 1, {
number = character.Phone
})
Inventory:AddItem(character.SID, 'water', 2)
Inventory:AddItem(character.SID, 'sandwich', 2)
Inventory:AddItem(character.SID, 'id_card', 1, {
name = character.First .. ' ' .. character.Last,
dob = character.DOB,
gender = character.Gender
})
Logger:Info('Characters', 'New character created', {
console = true,
file = true
}, {
player = source,
character = character.SID,
name = character.First .. ' ' .. character.Last
})
end)
-- Set default job
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
Jobs:GiveJob(character.SID, 'unemployed')
end)
-- Send welcome notification
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
TriggerClientEvent('mythic-notifications:client:Send', source, {
message = 'Welcome to the city, ' .. character.First .. '!',
type = 'success',
duration = 5000
})
end)
-- Create default bank account
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
Database.Game:insertOne({
collection = 'bank_accounts',
document = {
owner = character.SID,
type = 'checking',
balance = 5000, -- Starting balance
accountNumber = GenerateAccountNumber(),
createdAt = os.time()
}
})
end)
mythic-characters:server:CharacterSelected
Fired when a player selects and loads a character.AddEventHandler('mythic-characters:server:CharacterSelected', function(source, character)
-- Handler code
end)
Player server ID
Selected character object
-- Load character data for other systems
AddEventHandler('mythic-characters:server:CharacterSelected', function(source, character)
-- Inventory is loaded automatically by the Inventory component on character select
-- Load vehicles
Database.Game:find({
collection = 'vehicles',
query = { owner = character.SID }
}, function(success, vehicles)
if success then
TriggerClientEvent('mythic-vehicles:client:SetVehicles', source, vehicles)
end
end)
-- Load properties
Database.Game:find({
collection = 'properties',
query = { owner = character.SID }
}, function(success, properties)
if success then
TriggerClientEvent('mythic-properties:client:SetProperties', source, properties)
end
end)
-- Jobs are loaded automatically by the Jobs component on character select
end)
-- Log character selection
AddEventHandler('mythic-characters:server:CharacterSelected', function(source, character)
Logger:Info('Characters', 'Character selected', {
console = true,
file = true
}, {
player = source,
playerName = GetPlayerName(source),
character = character.SID,
characterName = character.First .. ' ' .. character.Last
})
-- Update last login
Characters:UpdateCharacter(character.SID, {
['metadata.lastLogin'] = os.time()
})
end)
-- Check for pending notifications
AddEventHandler('mythic-characters:server:CharacterSelected', function(source, character)
Database.Game:find({
collection = 'notifications',
query = { target = character.SID, read = false }
}, function(success, notifications)
if success and notifications and #notifications > 0 then
TriggerClientEvent('mythic-phone:client:ReceiveNotifications', source, notifications)
end
end)
end)
mythic-characters:server:CharacterDeleted
Fired when a character is deleted.AddEventHandler('mythic-characters:server:CharacterDeleted', function(source, characterId)
-- Handler code
end)
Player server ID who deleted the character
SID of the deleted character
-- Clean up character data
AddEventHandler('mythic-characters:server:CharacterDeleted', function(source, characterId)
-- Delete inventory
Inventory:Delete(characterId)
-- Delete all vehicles
Database.Game:delete({
collection = 'vehicles',
query = { owner = characterId }
})
-- Delete properties
Database.Game:delete({
collection = 'property_ownership',
query = { owner = characterId }
})
-- Delete phone data
Database.Game:delete({
collection = 'phone_messages',
query = {
['$or'] = {
{ sender = characterId },
{ receiver = characterId }
}
}
})
-- Delete bank accounts
Database.Game:delete({
collection = 'bank_accounts',
query = { owner = characterId }
})
Logger:Info('Characters', 'Character data cleaned up', {
console = true, file = true
}, { character = characterId })
end)
-- Log deletion for audit
AddEventHandler('mythic-characters:server:CharacterDeleted', function(source, characterId)
Database.Game:insertOne({
collection = 'audit_log',
document = {
type = 'character_deleted',
player = GetPlayerIdentifier(source, 0),
character = characterId,
timestamp = os.time()
}
})
end)
State Change Events
mythic-characters:server:JobChanged
Fired when a character’s job or job grade changes.AddEventHandler('mythic-characters:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
-- Handler code
end)
Character SID
New job identifier
New job grade
Previous job identifier
Previous job grade
-- Give job-specific items
AddEventHandler('mythic-characters:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
if newJob == 'police' then
-- Give police equipment
Inventory:AddItem(characterId, 'weapon_pistol', 1)
Inventory:AddItem(characterId, 'radio', 1)
Inventory:AddItem(characterId, 'handcuffs', 1)
elseif newJob == 'ems' then
-- Give EMS equipment
Inventory:AddItem(characterId, 'medkit', 5)
Inventory:AddItem(characterId, 'bandage', 10)
end
-- Remove old job items
if oldJob == 'police' then
Inventory.Items:Remove(characterId, 1, 'weapon_pistol', 999)
Inventory.Items:Remove(characterId, 1, 'handcuffs', 999)
end
end)
-- Update permissions
AddEventHandler('mythic-characters:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
local player = Fetch:SID(characterId)
if player then
local source = player:GetData('Source')
-- Update Discord roles or permissions
TriggerEvent('discord:updateRoles', source, newJob)
end
end)
-- Notify player
AddEventHandler('mythic-characters:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
local player = Fetch:SID(characterId)
if player then
local source = player:GetData('Source')
local jobLabel = newJob
local gradeLabel = Jobs:GetGradeLabel(newJob, newGrade)
TriggerClientEvent('mythic-notifications:client:Send', source, {
message = 'Job changed to ' .. jobLabel .. ' - ' .. gradeLabel,
type = 'info'
})
end
end)
mythic-characters:server:MoneyChanged
Fired when a character’s cash or bank balance changes.AddEventHandler('mythic-characters:server:MoneyChanged', function(characterId, account, amount, newBalance, reason)
-- Handler code
end)
Character SID
Account type:
'cash' or 'bank'Change amount (positive = added, negative = removed)
New balance after change
Transaction reason
-- Update client HUD
AddEventHandler('mythic-characters:server:MoneyChanged', function(characterId, account, amount, newBalance, reason)
local player = Fetch:SID(characterId)
if player then
local source = player:GetData('Source')
TriggerClientEvent('mythic-hud:client:UpdateMoney', source, {
account = account,
balance = newBalance,
change = amount
})
end
end)
-- Log large transactions
AddEventHandler('mythic-characters:server:MoneyChanged', function(characterId, account, amount, newBalance, reason)
if math.abs(amount) >= 10000 then -- Log transactions $10k+
Logger:Info('Economy', 'Large transaction', {
character = characterId,
account = account,
amount = amount,
newBalance = newBalance,
reason = reason
}, { file = true })
end
end)
-- Achievement tracking
AddEventHandler('mythic-characters:server:MoneyChanged', function(characterId, account, amount, newBalance, reason)
if account == 'bank' and newBalance >= 1000000 then
-- Unlock millionaire achievement
Achievements:Unlock(characterId, 'millionaire')
end
end)
-- Transaction log
AddEventHandler('mythic-characters:server:MoneyChanged', function(characterId, account, amount, newBalance, reason)
Database.Game:insertOne({
collection = 'transactions',
document = {
character = characterId,
account = account,
amount = amount,
balance = newBalance,
reason = reason,
timestamp = os.time()
}
})
end)
Client Events
mythic-characters:client:Spawned
Fired on the client when the character spawns in the world.AddEventHandler('mythic-characters:client:Spawned', function(character, location)
-- Handler code
end)
Character data
Spawn location
{x, y, z, heading}-- Client-side initialization
AddEventHandler('mythic-characters:client:Spawned', function(character, location)
-- Initialize HUD
SendNUIMessage({
type = 'INIT_CHARACTER',
character = {
name = character.First .. ' ' .. character.Last,
cash = character.Cash,
jobs = character.Jobs
}
})
-- Load player appearance
TriggerEvent('mythic-appearance:client:LoadAppearance', character.metadata.appearance)
-- Enable controls
FreezeEntityPosition(PlayerPedId(), false)
print('Spawned as', character.First, character.Last, 'at', location.x, location.y, location.z)
end)
-- Camera transition
AddEventHandler('mythic-characters:client:Spawned', function(character, location)
-- Fade out character selection screen
DoScreenFadeOut(500)
Wait(500)
-- Spawn player
SetEntityCoords(PlayerPedId(), location.x, location.y, location.z)
SetEntityHeading(PlayerPedId(), location.heading)
-- Fade in game world
Wait(1000)
DoScreenFadeIn(1000)
end)
mythic-characters:client:CharacterUpdated
Fired on the client when character data is updated.AddEventHandler('mythic-characters:client:CharacterUpdated', function(character)
-- Handler code
end)
-- Update HUD
AddEventHandler('mythic-characters:client:CharacterUpdated', function(character)
SendNUIMessage({
type = 'UPDATE_CHARACTER',
character = character
})
end)
Using Events for Custom Logic
Hook Into Character Creation
-- Add custom welcome package
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
-- Custom welcome logic
local welcomePackage = {
{ item = 'phone', count = 1 },
{ item = 'water', count = 5 },
{ item = 'sandwich', count = 5 },
{ item = 'starter_pack', count = 1 }
}
for _, item in ipairs(welcomePackage) do
Inventory:AddItem(character.SID, item.item, item.count)
end
-- Starting cash is set automatically via character defaults
end)
Track Playtime
local playtime = {}
-- Start tracking on character select
AddEventHandler('mythic-characters:server:CharacterSelected', function(source, character)
playtime[character.SID] = os.time()
end)
-- Save playtime on disconnect
AddEventHandler('playerDropped', function(reason)
local player = Fetch:Source(source)
if player then
local char = player:GetData('Character')
if char and playtime[char:GetData('SID')] then
local stateId = char:GetData('SID')
local sessionTime = os.time() - playtime[stateId]
local metadata = char:GetData('MetaData') or {}
local totalPlaytime = (metadata.playtime or 0) + sessionTime
-- Update database directly as player is disconnecting
Database.Game:updateOne({
collection = 'characters',
query = {SID = stateId},
update = {
['$set'] = {['MetaData.playtime'] = totalPlaytime}
}
})
playtime[stateId] = nil
end
end
end)
Custom Job Logic
-- Custom logic when becoming police
AddEventHandler('mythic-characters:server:JobChanged', function(characterId, newJob, newGrade, oldJob, oldGrade)
if newJob == 'police' then
-- Grant police access
local player = Fetch:SID(characterId)
if player then
local source = player:GetData('Source')
-- Give police permissions
ExecuteCommand('add_principal identifier.' .. GetPlayerIdentifier(source, 0) .. ' group.police')
-- Notify
TriggerClientEvent('mythic-notifications:client:Send', source, {
message = 'You are now a police officer',
type = 'success'
})
end
elseif oldJob == 'police' then
-- Remove police access when leaving
local player = Fetch:SID(characterId)
if player then
local source = player:GetData('Source')
ExecuteCommand('remove_principal identifier.' .. GetPlayerIdentifier(source, 0) .. ' group.police')
end
end
end)
Best Practices
Don't Block Event Handlers
Don't Block Event Handlers
❌ Bad:✅ Good:
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
-- Long synchronous operation
for i = 1, 1000000 do
-- Heavy work
end
})
AddEventHandler('mythic-characters:server:CharacterCreated', function(source, character)
-- Use thread for heavy work
CreateThread(function()
-- Heavy work
end)
})
Validate Character Exists
Validate Character Exists
AddEventHandler('mythic-characters:server:JobChanged', function(characterId, ...)
-- Validate character exists (check if online)
local player = Fetch:SID(characterId)
if not player then
Logger:Warn('Characters', 'JobChanged for offline character', {
console = true,
file = true
}, {
characterId = characterId
})
return
end
-- Process event
end)
Clean Up Event Data
Clean Up Event Data
local sessionData = {}
AddEventHandler('mythic-characters:server:CharacterSelected', function(source, character)
sessionData[character.SID] = {
loginTime = os.time(),
source = source
}
end)
AddEventHandler('playerDropped', function()
local player = Fetch:Source(source)
if player then
local char = player:GetData('Character')
if char then
sessionData[char:GetData('SID')] = nil -- Clean up
end
end
end)
Next Steps
Characters - Exports
Character management methods
Characters - Data Structure
Character data schema
Event System
Understanding the event system
Inventory Events
Inventory-related events
Event Timing: Character events fire in a specific order. Use this to your advantage - for example,
CharacterCreated fires before CharacterSelected, so you can set up initial data in the creation handler.