Character Object
The complete character object structure stored in MongoDB:Copy
{
_id = ObjectId("..."), -- MongoDB document ID
SID = 1, -- State ID (Character ID)
User = 12345, -- Account ID of player who owns this character
First = "John", -- First name
Last = "Doe", -- Last name
DOB = "1990-01-15", -- Date of birth (YYYY-MM-DD)
Gender = 0, -- Gender (0 = Male, 1 = Female)
Phone = 5551234567, -- Phone number (unique)
Bio = "Character biography", -- Character bio/backstory
Origin = 1, -- Character origin
-- Job Information (Array of jobs)
Jobs = {
{
Id = "police", -- Job identifier
Name = "Los Santos Police Department",
Grade = {
Id = "officer",
Name = "Police Officer",
Level = 1
},
Workplace = {
Id = "lspd",
Name = "LSPD Mission Row"
}
}
},
-- Money
Cash = 5000, -- Cash on hand (note: capital C)
Bank = 25000, -- Bank balance (note: capital B)
-- Licenses
Licenses = {
Drivers = {
Active = true,
Points = 0,
Suspended = false
},
Weapons = {
Active = false,
Suspended = false
},
Hunting = {
Active = false,
Suspended = false
},
Fishing = {
Active = false,
Suspended = false
},
Pilot = {
Active = false,
Suspended = false
}
},
-- Last played timestamp
LastPlayed = 1705334400,
-- Position (stored separately, synced to character)
position = {
x = 123.45,
y = -678.90,
z = 21.00,
heading = 180.0
},
-- Metadata (flexible additional data)
metadata = {
-- Appearance
appearance = {
-- Freemode ped data
model = "mp_m_freemode_01",
headBlend = {...},
faceFeatures = {...},
headOverlays = {...},
components = {...},
props = {...}
},
-- Licenses
licenses = {
"drivers_license",
"weapon_license"
},
-- Stats
playtime = 36000, -- Total playtime in seconds
arrests = 5, -- Times arrested
kills = 10, -- Player kills
deaths = 2, -- Deaths
-- Reputation
reputation = {
police = 100,
gang = -50
},
-- Status effects
isDead = false,
inJail = false,
jailTime = 0,
-- Needs (hunger, thirst, etc.)
hunger = 100,
thirst = 100,
stress = 0,
-- Custom data
lastLogin = 1705334400,
tutorial_completed = true
},
-- Timestamps
createdAt = 1700000000, -- Character creation timestamp
updatedAt = 1705334400 -- Last update timestamp
}
Required Fields
These fields are required when creating a character:First nameValidation:
- 2-20 characters
- Letters only (may include spaces, hyphens, apostrophes)
"John", "Mary-Jane", "O'Brien"Last nameValidation:
- 2-30 characters
- Letters only (may include spaces, hyphens, apostrophes)
"Doe", "Van Der Linde", "O'Sullivan"Date of birth in YYYY-MM-DD formatValidation:
- Must be at least 18 years old
- Format:
YYYY-MM-DD
"1990-01-15", "1985-12-31"Gender (numeric value)Values:
0= Male1= Female
Auto-Generated Fields
These fields are automatically set by the system:State ID - unique character identifierAuto-incremented starting from 1
Account ID of the player who owns this characterAutomatically set from the player’s AccountID
Phone number - unique 10-digit numberAuto-generated when character is created
Unix timestamp of character creationSet automatically to
os.time() on creationUnix timestamp of last updateUpdated automatically on every save
Default Values
Fields with default values if not specified:Copy
{
Jobs = {}, -- Empty jobs array (unemployed)
Cash = 5000, -- Starting cash (5000 by default)
Bank = 0, -- No starting bank money
position = { -- Default spawn position
x = -258.211,
y = -293.738,
z = 21.6114,
heading = 206.0
},
metadata = {} -- Empty metadata
}
Metadata Structure
Themetadata field is a flexible object for storing additional character data:
Appearance
Copy
metadata.appearance = {
model = "mp_m_freemode_01", -- Or "mp_f_freemode_01" for female
-- Head blend (heritage)
headBlend = {
shapeFirst = 0,
shapeSecond = 0,
skinFirst = 0,
skinSecond = 0,
shapeMix = 0.5,
skinMix = 0.5
},
-- Face features
faceFeatures = {
noseWidth = 0.0,
nosePeakHeight = 0.0,
nosePeakLength = 0.0,
-- ... (20 total features)
},
-- Head overlays (tattoos, makeup, etc.)
headOverlays = {
{ -- Blemishes
style = 0,
opacity = 0.0,
color = 0
},
-- ... (13 total overlays)
},
-- Clothing components
components = {
{ drawable = 0, texture = 0, palette = 0 }, -- Face
{ drawable = 0, texture = 0, palette = 0 }, -- Mask
-- ... (12 total components)
},
-- Props (hats, glasses, etc.)
props = {
{ drawable = -1, texture = 0 }, -- Hats/helmets
{ drawable = -1, texture = 0 }, -- Glasses
-- ... (7 total props)
}
}
Licenses
Copy
metadata.licenses = {
"drivers_license",
"weapon_license",
"hunting_license",
"fishing_license",
"pilots_license"
}
Statistics
Copy
metadata.stats = {
playtime = 36000, -- Total playtime (seconds)
arrests = 5, -- Times arrested
kills = 10, -- PVP kills
deaths = 2, -- Times died
robberies = 3, -- Robberies completed
drugsDealt = 50, -- Drugs sold
vehiclesSold = 2, -- Vehicles sold
}
Reputation
Copy
metadata.reputation = {
police = 100, -- Positive = good standing
ems = 50,
gang = -50, -- Negative = bad reputation
civilian = 75
}
Needs/Status
Copy
metadata.needs = {
hunger = 100, -- 0-100
thirst = 100, -- 0-100
stress = 0, -- 0-100
energy = 100 -- 0-100
}
metadata.status = {
isDead = false,
inJail = false,
jailTime = 0, -- Remaining jail time (seconds)
injuryLevel = 0, -- 0-100
bleeding = false,
broken_bones = {}
}
Achievements
Copy
metadata.achievements = {
"first_job",
"first_vehicle",
"millionaire",
"police_veteran"
}
Custom Data
You can store any custom data in metadata:Copy
metadata.customData = {
tutorial_completed = true,
favorite_vehicle = "adder",
home_location = { x = 100, y = 200, z = 30 },
friends = { 2, 5, 10 }, -- Character SIDs
last_robbery = 1705334400,
settings = {
hud_visible = true,
minimap_zoom = 2
}
}
Working with Character Data
Accessing Fields
Copy
-- Get character using Fetch component
local player = Fetch:Source(source)
if not player then return end
local char = player:GetData('Character')
if not char then return end
-- Access fields using GetData()
local firstName = char:GetData('First')
local lastName = char:GetData('Last')
print(firstName, lastName) -- Name
local stateId = char:GetData('SID') -- Character ID
local job = char:GetData('Job') -- Job
local jobGrade = char:GetData('JobGrade')
local cash = char:GetData('Cash') -- Money
local bank = char:GetData('Bank')
-- Access metadata
local metadata = char:GetData('MetaData') or {}
print(metadata.playtime) -- Playtime
print(metadata.hunger) -- Hunger
Updating Fields
Copy
-- Update basic fields using SetData()
local currentCash = char:GetData('Cash')
char:SetData('Cash', 5000)
local currentBank = char:GetData('Bank')
char:SetData('Bank', 10000)
-- Update nested metadata
local metadata = char:GetData('MetaData') or {}
metadata.hunger = 75
metadata.thirst = 80
char:SetData('MetaData', metadata)
-- Update job
char:SetData('Job', 'police')
char:SetData('JobGrade', 2)
Adding to Metadata
Copy
-- Add license
local player = Fetch:Source(source)
local char = player:GetData('Character')
local metadata = char:GetData('MetaData') or {}
local licenses = metadata.licenses or {}
table.insert(licenses, 'pilots_license')
metadata.licenses = licenses
char:SetData('MetaData', metadata)
-- Increment stat
local metadata = char:GetData('MetaData') or {}
local arrests = (metadata.arrests or 0) + 1
metadata.arrests = arrests
char:SetData('MetaData', metadata)
Database Queries
Find by Name
Copy
local characters = COMPONENTS.Database:find('characters', {
First = 'John',
Last = 'Doe'
})
Find by User (Account ID)
Copy
local player = Fetch:Source(source)
local accountId = player:GetData('AccountID')
Database.Game:find({
collection = 'characters',
query = {
User = accountId
}
}, function(success, results)
if success and results then
-- Process characters
end
end)
Find by Job
Copy
-- Note: Jobs is an array, so you need to query using array operators
Database.Game:find({
collection = 'characters',
query = {
['Jobs.Id'] = 'police' -- Query for characters with police job
}
}, function(success, results)
if success and results then
-- Process police characters
end
end)
Find Rich Players
Copy
Database.Game:aggregate({
collection = 'characters',
aggregate = {
{
['$project'] = {
name = { ['$concat'] = { '$First', ' ', '$Last' } },
totalMoney = { ['$add'] = { '$Cash', '$Bank' } } -- Note: Capital C and B
}
},
{
['$match'] = {
totalMoney = { ['$gte'] = 1000000 } -- $1M+
}
},
{
['$sort'] = { totalMoney = -1 }
}
}
}, function(success, results)
if success and results then
-- Process rich players
end
end)
Validation
Name Validation
Copy
function ValidateName(name)
-- Length check
if #name < 2 or #name > 30 then
return false, 'Name must be 2-30 characters'
end
-- Character check (letters, spaces, hyphens, apostrophes only)
if not string.match(name, "^[a-zA-Z%s%-']+$") then
return false, 'Name contains invalid characters'
end
return true
end
DOB Validation
Copy
function ValidateDOB(dob)
-- Format check
local year, month, day = dob:match('(%d+)-(%d+)-(%d+)')
if not year or not month or not day then
return false, 'Invalid date format (use YYYY-MM-DD)'
end
-- Age check (must be 18+)
local birthYear = tonumber(year)
local currentYear = tonumber(os.date('%Y'))
local age = currentYear - birthYear
if age < 18 then
return false, 'Character must be at least 18 years old'
end
return true
end
Gender Validation
Copy
function ValidateGender(gender)
local genderNum = tonumber(gender)
if not genderNum or (genderNum ~= 0 and genderNum ~= 1) then
return false, 'Gender must be 0 (Male) or 1 (Female)'
end
return true
end
Example: Complete Character Query
Copy
-- Get character with all related data (for online character)
function GetCompleteCharacterData(source)
local player = Fetch:Source(source)
if not player then
return nil
end
local char = player:GetData('Character')
if not char then
return nil
end
local stateId = char:GetData('SID')
-- Get inventory
local inventory = COMPONENTS.Inventory:Get(stateId)
-- Get vehicles (database query)
local vehicles = Database.Game:findOne({
collection = 'vehicles',
query = {owner = stateId}
})
-- Get properties (database query)
local properties = Database.Game:findOne({
collection = 'property_ownership',
query = {owner = stateId}
})
-- Get bank accounts (database query)
local bankAccounts = Database.Game:findOne({
collection = 'bank_accounts',
query = {owner = stateId}
})
return {
character = {
SID = char:GetData('SID'),
First = char:GetData('First'),
Last = char:GetData('Last'),
Cash = char:GetData('Cash'),
Bank = char:GetData('Bank'),
Job = char:GetData('Job'),
JobGrade = char:GetData('JobGrade'),
-- Add other fields as needed
},
inventory = inventory,
vehicles = vehicles,
properties = properties,
bankAccounts = bankAccounts
}
end
-- For offline character (database query)
function GetCompleteOfflineCharacterData(stateId)
-- Query database directly
local char = Database.Game:findOne({
collection = 'characters',
query = {SID = stateId}
})
if not char then
return nil
end
-- Get related data
local inventory = COMPONENTS.Inventory:Get(stateId)
return {
character = char,
inventory = inventory
}
end
Best Practices
Use Metadata for Flexible Data
Use Metadata for Flexible Data
✅ Good:❌ Bad:
Copy
-- Store flexible data in metadata
metadata.customFeatures = {
premium = true,
vipLevel = 3,
customSkin = 'skin_1'
}
Copy
-- Don't add new root-level fields
premium = true, -- This pollutes the schema
vipLevel = 3,
customSkin = 'skin_1'
Validate Before Updates
Validate Before Updates
Copy
function UpdateCharacterMoney(source, account, amount)
-- Validate character exists
local player = Fetch:Source(source)
if not player then
return false, 'Player not found'
end
local char = player:GetData('Character')
if not char then
return false, 'Character not loaded'
end
-- Validate account
local accountField
if account == 'cash' then
accountField = 'Cash'
elseif account == 'bank' then
accountField = 'Bank'
else
return false, 'Invalid account (must be "cash" or "bank")'
end
-- Validate amount
if type(amount) ~= 'number' or amount < 0 then
return false, 'Amount must be a positive number'
end
-- Update using DataStore
char:SetData(accountField, amount)
return true
end
Index Commonly Queried Fields
Index Commonly Queried Fields
Copy
// MongoDB - Create indexes for performance
db.characters.createIndex({ SID: 1 }, { unique: true })
db.characters.createIndex({ Owner: 1 })
db.characters.createIndex({ job: 1 })
db.characters.createIndex({ Phone: 1 }, { unique: true })
Next Steps
Characters - Exports
Character management methods
Characters - Events
Character lifecycle events
Database API
Database operations
Inventory Data Structure
Inventory and item data
Metadata Flexibility: The metadata field is perfect for storing custom data without modifying the core schema. Use it for resource-specific data, stats, settings, and any dynamic information.