Examples

Player Systems

Player data management, inventories, and progression systems
These examples are AI-generated and has not been reviewed for accuracy. Use them as a starting point and verify correctness before deploying in production.

Player Systems

Complete examples for managing player data in Garry's Mod.

Player Data System

A complete player data management system:

--[[
    Player Data System
    Handles player registration, loading, and saving
]]

local PlayerData = {}
PlayerData.client = nil
PlayerData.db = nil

-- Initialize the system
function PlayerData.Initialize(connectionString, dbName)
    PlayerData.client = MongoDB.Client(connectionString)
    if not PlayerData.client then
        print("[PlayerData] Failed to connect to MongoDB!")
        return false
    end

    PlayerData.db = PlayerData.client:Database(dbName)

    -- Setup indexes
    local players = PlayerData.db:Collection("players")
    players:CreateIndex({ steamid = 1 }, true, "steamid_unique")
    players:CreateIndex({ last_login = -1 }, false, "recent_login")

    print("[PlayerData] System initialized")
    return true
end

-- Get player collection
function PlayerData.GetCollection()
    return PlayerData.db:Collection("players")
end

-- Load player data
function PlayerData.Load(steamid, callback)
    local col = PlayerData.GetCollection()

    col:FindOneAsync({ steamid = steamid }, function(err, data)
        if err then
            print("[PlayerData] Load error:", err)
            callback(nil, err)
            return
        end

        if data then
            -- Update last login
            col:UpdateOneAsync(
                { steamid = steamid },
                { ["$set"] = { last_login = os.time() } },
                function() end
            )
            callback(data, nil)
        else
            -- Create new player
            local newPlayer = {
                steamid = steamid,
                level = 1,
                experience = 0,
                credits = 1000,
                playtime = 0,
                created_at = os.time(),
                last_login = os.time(),
                stats = {
                    kills = 0,
                    deaths = 0,
                    assists = 0
                },
                settings = {
                    notifications = true,
                    music = true
                },
                inventory = {}
            }

            col:InsertOneAsync(newPlayer, function(err, id)
                if err then
                    callback(nil, err)
                else
                    newPlayer._id = id
                    callback(newPlayer, nil)
                end
            end)
        end
    end)
end

-- Save player data
function PlayerData.Save(steamid, data, callback)
    local col = PlayerData.GetCollection()

    col:UpdateOneAsync(
        { steamid = steamid },
        { ["$set"] = data },
        function(err, modified)
            if callback then
                callback(err, modified > 0)
            end
        end
    )
end

-- Update specific fields
function PlayerData.Update(steamid, updates, callback)
    local col = PlayerData.GetCollection()

    col:UpdateOneAsync(
        { steamid = steamid },
        updates,
        function(err, modified)
            if callback then
                callback(err, modified > 0)
            end
        end
    )
end

-- Add experience and handle level up
function PlayerData.AddExperience(steamid, amount, callback)
    local col = PlayerData.GetCollection()

    -- Get current data
    col:FindOneAsync({ steamid = steamid }, function(err, player)
        if err or not player then
            if callback then callback(false, 0) end
            return
        end

        local newExp = player.experience + amount
        local newLevel = player.level
        local expRequired = newLevel * 1000

        -- Level up logic
        while newExp >= expRequired do
            newExp = newExp - expRequired
            newLevel = newLevel + 1
            expRequired = newLevel * 1000
        end

        local leveledUp = newLevel > player.level

        col:UpdateOneAsync(
            { steamid = steamid },
            {
                ["$set"] = {
                    experience = newExp,
                    level = newLevel
                }
            },
            function(err)
                if callback then
                    callback(leveledUp, newLevel)
                end
            end
        )
    end)
end

-- Get player stats
function PlayerData.GetStats(steamid, callback)
    local col = PlayerData.GetCollection()

    col:FindOneAsync(
        { steamid = steamid },
        function(err, player)
            if err or not player then
                callback(nil)
            else
                callback(player.stats)
            end
        end
    )
end

-- Increment stat
function PlayerData.IncrementStat(steamid, stat, amount)
    local col = PlayerData.GetCollection()

    col:UpdateOneAsync(
        { steamid = steamid },
        { ["$inc"] = { ["stats." .. stat] = amount or 1 } },
        function() end
    )
end

return PlayerData

Inventory System

--[[
    Player Inventory System
    Manages player items with stacking and limits
]]

local Inventory = {}
Inventory.MAX_SLOTS = 50
Inventory.MAX_STACK = 999

function Inventory.Initialize(db)
    Inventory.db = db
    Inventory.players = db:Collection("players")
end

-- Get player inventory
function Inventory.Get(steamid, callback)
    Inventory.players:FindOneAsync(
        { steamid = steamid },
        function(err, player)
            if err or not player then
                callback({})
            else
                callback(player.inventory or {})
            end
        end
    )
end

-- Add item to inventory
function Inventory.AddItem(steamid, itemId, itemName, quantity, callback)
    quantity = quantity or 1

    Inventory.players:FindOneAsync({ steamid = steamid }, function(err, player)
        if err or not player then
            if callback then callback(false, "Player not found") end
            return
        end

        local inventory = player.inventory or {}
        local added = false

        -- Try to stack with existing item
        for _, item in ipairs(inventory) do
            if item.item_id == itemId then
                local newQty = math.min(item.quantity + quantity, Inventory.MAX_STACK)
                local actualAdded = newQty - item.quantity
                item.quantity = newQty
                added = true
                quantity = quantity - actualAdded
                break
            end
        end

        -- Add as new item if not stacked (and we have remaining)
        if not added and quantity > 0 then
            if #inventory >= Inventory.MAX_SLOTS then
                if callback then callback(false, "Inventory full") end
                return
            end

            table.insert(inventory, {
                item_id = itemId,
                item_name = itemName,
                quantity = math.min(quantity, Inventory.MAX_STACK),
                added_at = os.time()
            })
        end

        -- Save inventory
        Inventory.players:UpdateOneAsync(
            { steamid = steamid },
            { ["$set"] = { inventory = inventory } },
            function(err)
                if callback then
                    callback(not err, err and "Save failed" or nil)
                end
            end
        )
    end)
end

-- Remove item from inventory
function Inventory.RemoveItem(steamid, itemId, quantity, callback)
    quantity = quantity or 1

    Inventory.players:FindOneAsync({ steamid = steamid }, function(err, player)
        if err or not player then
            if callback then callback(false, "Player not found") end
            return
        end

        local inventory = player.inventory or {}
        local removed = false

        for i, item in ipairs(inventory) do
            if item.item_id == itemId then
                if item.quantity <= quantity then
                    table.remove(inventory, i)
                else
                    item.quantity = item.quantity - quantity
                end
                removed = true
                break
            end
        end

        if not removed then
            if callback then callback(false, "Item not found") end
            return
        end

        Inventory.players:UpdateOneAsync(
            { steamid = steamid },
            { ["$set"] = { inventory = inventory } },
            function(err)
                if callback then
                    callback(not err, err and "Save failed" or nil)
                end
            end
        )
    end)
end

-- Check if player has item
function Inventory.HasItem(steamid, itemId, quantity, callback)
    quantity = quantity or 1

    Inventory.Get(steamid, function(inventory)
        for _, item in ipairs(inventory) do
            if item.item_id == itemId and item.quantity >= quantity then
                callback(true, item.quantity)
                return
            end
        end
        callback(false, 0)
    end)
end

-- Transfer item between players
function Inventory.Transfer(fromSteamid, toSteamid, itemId, quantity, callback)
    quantity = quantity or 1

    Inventory.HasItem(fromSteamid, itemId, quantity, function(has, currentQty)
        if not has then
            callback(false, "Insufficient items")
            return
        end

        -- Get item name
        Inventory.Get(fromSteamid, function(inv)
            local itemName = ""
            for _, item in ipairs(inv) do
                if item.item_id == itemId then
                    itemName = item.item_name
                    break
                end
            end

            -- Remove from source
            Inventory.RemoveItem(fromSteamid, itemId, quantity, function(success)
                if not success then
                    callback(false, "Failed to remove item")
                    return
                end

                -- Add to destination
                Inventory.AddItem(toSteamid, itemId, itemName, quantity, function(success, err)
                    if not success then
                        -- Rollback
                        Inventory.AddItem(fromSteamid, itemId, itemName, quantity)
                        callback(false, "Failed to add item: " .. (err or ""))
                    else
                        callback(true)
                    end
                end)
            end)
        end)
    end)
end

-- Get inventory statistics
function Inventory.GetStats(steamid, callback)
    Inventory.Get(steamid, function(inventory)
        local stats = {
            total_items = 0,
            unique_items = #inventory,
            slots_used = #inventory,
            slots_free = Inventory.MAX_SLOTS - #inventory
        }

        for _, item in ipairs(inventory) do
            stats.total_items = stats.total_items + item.quantity
        end

        callback(stats)
    end)
end

return Inventory

GMod Integration

--[[
    Garry's Mod Integration
    Hooks and commands for player data system
]]

require("mongo")

-- Initialize on server start
hook.Add("Initialize", "InitPlayerData", function()
    local success = PlayerData.Initialize(
        "mongodb://localhost:27017",
        "gmod_server"
    )

    if success then
        print("[Server] Player data system ready")
    else
        print("[Server] WARNING: Player data system failed!")
    end
end)

-- Load player data on join
hook.Add("PlayerInitialSpawn", "LoadPlayerData", function(ply)
    local steamid = ply:SteamID()

    PlayerData.Load(steamid, function(data, err)
        if err then
            print("[Server] Failed to load data for", ply:Nick())
            return
        end

        -- Store data on player
        ply.dbData = data

        -- Set networked variables
        ply:SetNWInt("Level", data.level)
        ply:SetNWInt("Credits", data.credits)

        -- Initialize inventory system
        Inventory.Initialize(PlayerData.db)

        print("[Server] Loaded data for", ply:Nick(), "Level:", data.level)
    end)
end)

-- Auto-save every 5 minutes
timer.Create("AutoSavePlayerData", 300, 0, function()
    for _, ply in ipairs(player.GetAll()) do
        if ply.dbData then
            local data = {
                credits = ply.dbData.credits,
                level = ply.dbData.level,
                experience = ply.dbData.experience,
                playtime = ply.dbData.playtime + 300,
                stats = ply.dbData.stats
            }

            PlayerData.Save(ply:SteamID(), data)
        end
    end
    print("[Server] Auto-save complete")
end)

-- Save on disconnect
hook.Add("PlayerDisconnected", "SavePlayerData", function(ply)
    if ply.dbData then
        PlayerData.Save(ply:SteamID(), {
            credits = ply.dbData.credits,
            level = ply.dbData.level,
            experience = ply.dbData.experience,
            stats = ply.dbData.stats,
            last_logout = os.time()
        })
        print("[Server] Saved data for disconnecting", ply:Nick())
    end
end)

-- Track kills
hook.Add("PlayerDeath", "TrackKillStats", function(victim, inflictor, attacker)
    if IsValid(victim) and victim:IsPlayer() then
        PlayerData.IncrementStat(victim:SteamID(), "deaths", 1)
    end

    if IsValid(attacker) and attacker:IsPlayer() and attacker ~= victim then
        PlayerData.IncrementStat(attacker:SteamID(), "kills", 1)

        -- Award experience
        PlayerData.AddExperience(attacker:SteamID(), 100, function(leveledUp, newLevel)
            if leveledUp then
                attacker:ChatPrint("Congratulations! You reached level " .. newLevel .. "!")
                attacker:SetNWInt("Level", newLevel)
            end
        end)
    end
end)

-- Console command to check stats
concommand.Add("stats", function(ply)
    if not IsValid(ply) then return end

    PlayerData.GetStats(ply:SteamID(), function(stats)
        if stats then
            ply:ChatPrint("--- Your Stats ---")
            ply:ChatPrint("Kills: " .. stats.kills)
            ply:ChatPrint("Deaths: " .. stats.deaths)
            ply:ChatPrint("Assists: " .. stats.assists)
        end
    end)
end)

Next Steps