Skip to main content

Overview

The WarnPlayer function allows staff members to issue warnings to players for rule violations. All warnings are automatically logged and can trigger Discord webhook notifications if configured.

WarnPlayer

Issue a warning to a player for rule violations.

Syntax

local success, status = exports['zyrix_admin']:WarnPlayer(staffId, targetId, reason)

Parameters

  • staffId (number) - Server ID of the staff member issuing the warning
  • targetId (number) - Server ID of the player receiving the warning
  • reason (string) - Reason for the warning (minimum 3 characters)

Returns

  • success (boolean) - Whether the warning was successfully issued
  • status (string) - Status code: 'success', 'no_permission', 'player_not_found', 'invalid_target', 'invalid_reason'

Example

-- Issue a warning for RDM
local success, status = exports['zyrix_admin']:WarnPlayer(source, targetId, "Random Death Match - Killing without RP reason")

if success then
    TriggerClientEvent('chat:addMessage', source, {
        color = {255, 165, 0},
        multiline = false,
        args = {"[Warning]", string.format("Warning issued to %s", GetPlayerName(targetId))}
    })
    
    TriggerClientEvent('chat:addMessage', targetId, {
        color = {255, 0, 0},
        multiline = false,
        args = {"[Warning]", "You have been warned: " .. reason}
    })
else
    print("Warning failed:", status)
end

Advanced Examples

Warning System with Escalation

local warningSystem = {
    thresholds = {
        {warnings = 3, action = "kick", message = "3 warnings reached - kicked"},
        {warnings = 5, action = "tempban", duration = 3600, message = "5 warnings reached - 1 hour ban"},
        {warnings = 8, action = "ban", duration = 86400, message = "8 warnings reached - 24 hour ban"}
    }
}

local function issueWarningWithEscalation(staffId, targetId, reason)
    -- Issue the warning
    local success, status = exports['zyrix_admin']:WarnPlayer(staffId, targetId, reason)
    
    if not success then
        return false, status
    end
    
    -- Get player's total warnings
    local warnSuccess, warnStatus, warnings = exports['zyrix_admin']:FetchWarnings(targetId)
    
    if warnSuccess and warnings then
        local totalWarnings = #warnings
        
        -- Check escalation thresholds
        for _, threshold in ipairs(warningSystem.thresholds) do
            if totalWarnings >= threshold.warnings then
                TriggerClientEvent('notification', targetId, threshold.message)
                
                if threshold.action == "kick" then
                    exports['zyrix_admin']:KickPlayer(staffId, targetId, threshold.message)
                elseif threshold.action == "tempban" or threshold.action == "ban" then
                    exports['zyrix_admin']:BanPlayer(staffId, targetId, threshold.duration, threshold.message)
                end
                
                -- Notify staff
                TriggerClientEvent('notification', staffId, 
                    string.format("Auto-escalation: %s %s after %d warnings", 
                    threshold.action, GetPlayerName(targetId), totalWarnings)
                )
                break
            end
        end
    end
    
    return true, "success"
end

-- Command with escalation
RegisterCommand('warn', function(source, args)
    if #args < 2 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/warn <player_id> <reason>"}
        })
        return
    end
    
    local targetId = tonumber(args[1])
    local reason = table.concat(args, " ", 2)
    
    if not targetId or not GetPlayerName(targetId) then
        TriggerClientEvent('notification', source, "Player not found")
        return
    end
    
    issueWarningWithEscalation(source, targetId, reason)
end, true)

Warning Categories System

local warningCategories = {
    ["rdm"] = {
        fullName = "Random Death Match",
        defaultReason = "Killing players without proper roleplay reason",
        severity = 3
    },
    ["vdm"] = {
        fullName = "Vehicle Death Match", 
        defaultReason = "Using vehicles to kill players without RP reason",
        severity = 3
    },
    ["failrp"] = {
        fullName = "Fail Roleplay",
        defaultReason = "Breaking character or unrealistic behavior",
        severity = 2
    },
    ["metagaming"] = {
        fullName = "Metagaming",
        defaultReason = "Using out-of-character information in roleplay",
        severity = 2
    },
    ["powergaming"] = {
        fullName = "Powergaming",
        defaultReason = "Forcing roleplay scenarios on other players",
        severity = 2
    },
    ["micspam"] = {
        fullName = "Microphone Spam",
        defaultReason = "Spamming music or excessive noise through microphone",
        severity = 1
    }
}

local function issueTypedWarning(staffId, targetId, category, customReason)
    local warningType = warningCategories[category:lower()]
    
    if not warningType then
        return false, "Invalid warning category"
    end
    
    local reason = customReason or warningType.defaultReason
    local fullReason = string.format("[%s] %s", warningType.fullName, reason)
    
    local success, status = exports['zyrix_admin']:WarnPlayer(staffId, targetId, fullReason)
    
    if success then
        -- Additional logging based on severity
        if warningType.severity >= 3 then
            print(string.format("[HIGH SEVERITY WARNING] %s warned %s for %s", 
                  GetPlayerName(staffId), GetPlayerName(targetId), warningType.fullName))
        end
    end
    
    return success, status
end

-- Command for categorized warnings
RegisterCommand('cwarn', function(source, args)
    if #args < 2 then
        local categories = {}
        for category, data in pairs(warningCategories) do
            table.insert(categories, string.format("%s (%s)", category, data.fullName))
        end
        
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/cwarn <player_id> <category> [custom_reason]"}
        })
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Categories]", table.concat(categories, ", ")}
        })
        return
    end
    
    local targetId = tonumber(args[1])
    local category = args[2]
    local customReason = #args > 2 and table.concat(args, " ", 3) or nil
    
    if not targetId or not GetPlayerName(targetId) then
        TriggerClientEvent('notification', source, "Player not found")
        return
    end
    
    local success, status = issueTypedWarning(source, targetId, category, customReason)
    
    if success then
        TriggerClientEvent('notification', source, 
            string.format("Issued %s warning to %s", category:upper(), GetPlayerName(targetId))
        )
    else
        TriggerClientEvent('notification', source, "Warning failed: " .. status)
    end
end, true)

Warning History Analysis

local warningAnalytics = {}

-- Analyze player warning patterns
function warningAnalytics.analyzePlayer(playerId)
    local success, status, warnings = exports['zyrix_admin']:FetchWarnings(playerId)
    
    if not success or not warnings then
        return {
            total = 0,
            categories = {},
            recentCount = 0,
            averageInterval = 0,
            riskLevel = "low"
        }
    end
    
    local analysis = {
        total = #warnings,
        categories = {},
        recentCount = 0,
        averageInterval = 0,
        riskLevel = "low"
    }
    
    local currentTime = os.time()
    local categoryCount = {}
    local timestamps = {}
    
    for _, warning in ipairs(warnings) do
        -- Count categories
        local category = warning.reason:match("%[(.-)%]") or "uncategorized"
        categoryCount[category] = (categoryCount[category] or 0) + 1
        
        -- Count recent warnings (last 24 hours)
        if warning.timestamp and (currentTime - warning.timestamp) <= 86400 then
            analysis.recentCount = analysis.recentCount + 1
        end
        
        -- Store timestamps for interval calculation
        if warning.timestamp then
            table.insert(timestamps, warning.timestamp)
        end
    end
    
    -- Calculate average interval between warnings
    if #timestamps > 1 then
        table.sort(timestamps)
        local totalInterval = timestamps[#timestamps] - timestamps[1]
        analysis.averageInterval = totalInterval / (#timestamps - 1)
    end
    
    -- Determine risk level
    if analysis.total >= 8 or analysis.recentCount >= 3 then
        analysis.riskLevel = "high"
    elseif analysis.total >= 4 or analysis.recentCount >= 2 then
        analysis.riskLevel = "medium"
    else
        analysis.riskLevel = "low"
    end
    
    analysis.categories = categoryCount
    
    return analysis
end

-- Get server warning statistics
function warningAnalytics.getServerStats()
    local players = GetPlayers()
    local stats = {
        totalWarnings = 0,
        totalPlayers = #players,
        playersWithWarnings = 0,
        categoryBreakdown = {},
        riskDistribution = {high = 0, medium = 0, low = 0}
    }
    
    for _, playerId in ipairs(players) do
        local analysis = warningAnalytics.analyzePlayer(playerId)
        
        if analysis.total > 0 then
            stats.playersWithWarnings = stats.playersWithWarnings + 1
            stats.totalWarnings = stats.totalWarnings + analysis.total
            stats.riskDistribution[analysis.riskLevel] = stats.riskDistribution[analysis.riskLevel] + 1
            
            -- Merge category data
            for category, count in pairs(analysis.categories) do
                stats.categoryBreakdown[category] = (stats.categoryBreakdown[category] or 0) + count
            end
        else
            stats.riskDistribution.low = stats.riskDistribution.low + 1
        end
    end
    
    return stats
end

-- Command to view warning analytics
RegisterCommand('warnstats', function(source, args)
    if #args == 1 then
        -- Player-specific stats
        local targetId = tonumber(args[1])
        if targetId and GetPlayerName(targetId) then
            local analysis = warningAnalytics.analyzePlayer(targetId)
            
            TriggerClientEvent('chat:addMessage', source, {
                args = {"[Warning Stats]", string.format("%s - Total: %d, Recent: %d, Risk: %s", 
                       GetPlayerName(targetId), analysis.total, analysis.recentCount, analysis.riskLevel:upper())}
            })
            
            if next(analysis.categories) then
                local categoryText = {}
                for category, count in pairs(analysis.categories) do
                    table.insert(categoryText, string.format("%s: %d", category, count))
                end
                TriggerClientEvent('chat:addMessage', source, {
                    args = {"[Categories]", table.concat(categoryText, ", ")}
                })
            end
        else
            TriggerClientEvent('notification', source, "Player not found")
        end
    else
        -- Server-wide stats
        local stats = warningAnalytics.getServerStats()
        
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Server Warning Stats]", string.format("Total: %d warnings across %d players", 
                   stats.totalWarnings, stats.playersWithWarnings)}
        })
        
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Risk Distribution]", string.format("High: %d, Medium: %d, Low: %d", 
                   stats.riskDistribution.high, stats.riskDistribution.medium, stats.riskDistribution.low)}
        })
    end
end, true)

Bulk Warning System

local function bulkWarnPlayers(staffId, playerIds, reason, excludeStaff)
    local results = {
        success = {},
        failed = {},
        total = 0
    }
    
    for _, playerId in ipairs(playerIds) do
        -- Skip staff members if requested
        if excludeStaff and exports['zyrix_admin']:IsStaffMember(playerId) then
            table.insert(results.failed, {id = playerId, reason = "staff_excluded"})
        else
            local success, status = exports['zyrix_admin']:WarnPlayer(staffId, playerId, reason)
            
            if success then
                table.insert(results.success, playerId)
                TriggerClientEvent('notification', playerId, 
                    "You have received a server-wide warning: " .. reason
                )
            else
                table.insert(results.failed, {id = playerId, reason = status})
            end
        end
        
        results.total = results.total + 1
    end
    
    -- Notify staff member
    TriggerClientEvent('notification', staffId, 
        string.format("Bulk warning: %d success, %d failed out of %d total", 
        #results.success, #results.failed, results.total)
    )
    
    -- Server announcement
    if #results.success > 0 then
        TriggerClientEvent('chat:addMessage', -1, {
            color = {255, 165, 0},
            multiline = false,
            args = {"[Server Warning]", reason}
        })
    end
    
    return results
end

-- Command for server-wide warnings
RegisterCommand('serverwarn', function(source, args)
    if #args < 1 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/serverwarn <reason>"}
        })
        return
    end
    
    local reason = table.concat(args, " ")
    local players = GetPlayers()
    
    -- Remove issuing staff from list
    for i, playerId in ipairs(players) do
        if playerId == source then
            table.remove(players, i)
            break
        end
    end
    
    bulkWarnPlayers(source, players, reason, true) -- Exclude other staff
end, true)

Error Handling

Handle common scenarios when issuing warnings:
local function safeWarnPlayer(staffId, targetId, reason)
    -- Validate reason
    if not reason or string.len(reason) < 3 then
        return false, "Warning reason must be at least 3 characters"
    end
    
    if string.len(reason) > 500 then
        return false, "Warning reason too long (max 500 characters)"
    end
    
    -- Check if player exists
    if not GetPlayerName(targetId) then
        return false, "Target player not found"
    end
    
    -- Prevent self-warning
    if staffId == targetId then
        return false, "Cannot warn yourself"
    end
    
    -- Check staff permissions
    if not exports['zyrix_admin']:HasPermission(staffId, 'warn') then
        return false, "Insufficient permissions to warn players"
    end
    
    -- Issue warning
    local success, status = exports['zyrix_admin']:WarnPlayer(staffId, targetId, reason)
    
    if not success then
        local errorMessages = {
            no_permission = "You don't have permission to warn players",
            player_not_found = "Target player not found",
            invalid_target = "Invalid player ID",
            invalid_reason = "Invalid warning reason"
        }
        
        local message = errorMessages[status] or ("Warning failed: " .. status)
        TriggerClientEvent('notification', staffId, message)
    else
        -- Log successful warning
        print(string.format("[WARNING] %s warned %s: %s", 
              GetPlayerName(staffId), GetPlayerName(targetId), reason))
    end
    
    return success, status
end
Warnings are permanently stored and can be viewed by staff members. Use clear, specific reasons that explain the rule violation.
Consider implementing warning categories and escalation systems to maintain consistency in your server’s moderation approach.