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)
    -- Note: Bank balances are stored in a separate 'bank_accounts' collection, NOT on the character

    -- 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)
    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 jobs = char:GetData('Jobs')      -- Jobs array (table of job objects)
local cash = char:GetData('Cash')      -- Cash on hand

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

-- Update nested metadata
local metadata = char:GetData('MetaData') or {}
metadata.hunger = 75
metadata.thirst = 80
char:SetData('MetaData', metadata)

-- Note: Jobs are managed through the Jobs component, not SetData
-- Use Jobs:GiveJob(stateId, jobId, workplaceId, gradeId) to assign jobs

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

Database.Game:find({
    collection = 'characters',
    query = { First = 'John', Last = 'Doe' }
}, function(success, characters)
    -- process characters
end)

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 Characters with Most Cash

Database.Game:aggregate({
    collection = 'characters',
    aggregate = {
        {
            ['$project'] = {
                name = { ['$concat'] = { '$First', ' ', '$Last' } },
                Cash = 1
            }
        },
        {
            ['$match'] = {
                Cash = { ['$gte'] = 100000 }  -- $100k+ cash
            }
        },
        {
            ['$sort'] = { Cash = -1 }
        }
    }
}, function(success, results)
    if success and results then
        -- Process results
    end
end)
Bank balances are stored in the bank_accounts collection, not on the character document. Query that collection separately to find bank balance data.

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 item counts
    local itemCounts = Inventory.Items:GetCounts(stateId, 1)

    -- 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'),
            Jobs = char:GetData('Jobs'),  -- Array of job objects
            Phone = char:GetData('Phone'),
        },
        inventory = itemCounts,
        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

    return {
        character = char,
    }
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 UpdateCharacterCash(source, 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 amount
    if type(amount) ~= 'number' then
        return false, 'Amount must be a number'
    end

    local currentCash = char:GetData('Cash')
    local newCash = currentCash + amount

    if newCash < 0 then
        return false, 'Insufficient funds'
    end

    -- Update cash using DataStore
    char:SetData('Cash', newCash)

    return true
end
-- Note: For bank operations, use the Banking component API
// MongoDB - Create indexes for performance
db.characters.createIndex({ SID: 1 }, { unique: true })
db.characters.createIndex({ Owner: 1 })
db.characters.createIndex({ 'Jobs.Id': 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.