Skip to main content
Understanding the character data structure is essential for working with character data in Mythic Framework.

Character Object

The complete character object structure stored in MongoDB:
{
    _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
string
required
First nameValidation:
  • 2-20 characters
  • Letters only (may include spaces, hyphens, apostrophes)
Example: "John", "Mary-Jane", "O'Brien"
Last
string
required
Last nameValidation:
  • 2-30 characters
  • Letters only (may include spaces, hyphens, apostrophes)
Example: "Doe", "Van Der Linde", "O'Sullivan"
DOB
string
required
Date of birth in YYYY-MM-DD formatValidation:
  • Must be at least 18 years old
  • Format: YYYY-MM-DD
Example: "1990-01-15", "1985-12-31"
Gender
number
required
Gender (numeric value)Values:
  • 0 = Male
  • 1 = Female

Auto-Generated Fields

These fields are automatically set by the system:
SID
number
State ID - unique character identifierAuto-incremented starting from 1
User
number
Account ID of the player who owns this characterAutomatically set from the player’s AccountID
Phone
number
Phone number - unique 10-digit numberAuto-generated when character is created
createdAt
number
Unix timestamp of character creationSet automatically to os.time() on creation
updatedAt
number
Unix timestamp of last updateUpdated automatically on every save

Default Values

Fields with default values if not specified:
{
    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

The metadata field is a flexible object for storing additional character data:

Appearance

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

metadata.licenses = {
    "drivers_license",
    "weapon_license",
    "hunting_license",
    "fishing_license",
    "pilots_license"
}

Statistics

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

metadata.reputation = {
    police = 100,              -- Positive = good standing
    ems = 50,
    gang = -50,                -- Negative = bad reputation
    civilian = 75
}

Needs/Status

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

metadata.achievements = {
    "first_job",
    "first_vehicle",
    "millionaire",
    "police_veteran"
}

Custom Data

You can store any custom data in metadata:
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

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

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

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

local characters = COMPONENTS.Database:find('characters', {
    First = 'John',
    Last = 'Doe'
})

Find by User (Account ID)

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

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

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

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

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

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

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

✅ Good:
-- Store flexible data in metadata
metadata.customFeatures = {
    premium = true,
    vipLevel = 3,
    customSkin = 'skin_1'
}
❌ Bad:
-- Don't add new root-level fields
premium = true,           -- This pollutes the schema
vipLevel = 3,
customSkin = 'skin_1'
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
// 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

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.