Skip to main content

Overview

The BanPlayer function allows staff members to ban players temporarily or permanently. All bans are stored in the database with detailed information and can trigger webhook notifications.

BanPlayer

Ban a player for a specified duration with a detailed reason.

Syntax

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

Parameters

  • staffId (number) - Server ID of the staff member issuing the ban
  • targetId (number) - Server ID of the player to ban
  • duration (number) - Ban duration in seconds (0 for permanent ban)
  • reason (string) - Detailed reason for the ban (minimum 5 characters)

Returns

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

Example

-- Temporary ban (1 hour)
local duration = 3600 -- 1 hour in seconds
local success, status = exports['zyrix_admin']:BanPlayer(source, targetId, duration, "Cheating - Aimbot detected")

if success then
    TriggerClientEvent('chat:addMessage', -1, {
        color = {255, 0, 0},
        multiline = false,
        args = {"[Ban]", string.format("%s was banned for %d minutes", GetPlayerName(targetId), duration / 60)}
    })
else
    TriggerClientEvent('notification', source, "Ban failed: " .. status)
end

-- Permanent ban
local success, status = exports['zyrix_admin']:BanPlayer(source, targetId, 0, "Multiple rule violations - Permanent removal")

Duration Helpers

Common ban duration constants for consistency:
local BanDurations = {
    -- Minutes
    FIVE_MINUTES = 300,
    FIFTEEN_MINUTES = 900,
    THIRTY_MINUTES = 1800,
    
    -- Hours  
    ONE_HOUR = 3600,
    THREE_HOURS = 10800,
    SIX_HOURS = 21600,
    TWELVE_HOURS = 43200,
    
    -- Days
    ONE_DAY = 86400,
    THREE_DAYS = 259200,
    ONE_WEEK = 604800,
    TWO_WEEKS = 1209600,
    ONE_MONTH = 2592000,
    
    -- Permanent
    PERMANENT = 0
}

-- Example usage
local success = exports['zyrix_admin']:BanPlayer(source, targetId, BanDurations.ONE_DAY, "RDM - 24 hour ban")

Advanced Examples

Progressive Ban System

local progressiveBans = {
    durations = {
        [1] = BanDurations.ONE_HOUR,      -- First ban: 1 hour
        [2] = BanDurations.SIX_HOURS,     -- Second ban: 6 hours  
        [3] = BanDurations.ONE_DAY,       -- Third ban: 1 day
        [4] = BanDurations.THREE_DAYS,    -- Fourth ban: 3 days
        [5] = BanDurations.ONE_WEEK,      -- Fifth ban: 1 week
        [6] = BanDurations.PERMANENT      -- Sixth ban: permanent
    }
}

local function getPlayerBanHistory(playerId)
    -- This would integrate with your ban history system
    -- Returns count of previous bans
    return exports['zyrix_admin']:GetPlayerBanCount(playerId) or 0
end

local function progressiveBan(staffId, targetId, reason)
    local banCount = getPlayerBanHistory(targetId)
    local nextBanNumber = banCount + 1
    local duration = progressiveBans.durations[nextBanNumber] or BanDurations.PERMANENT
    
    local banType = duration == 0 and "permanent" or string.format("%d minutes", duration / 60)
    local fullReason = string.format("[Progressive Ban #%d] %s", nextBanNumber, reason)
    
    local success, status = exports['zyrix_admin']:BanPlayer(staffId, targetId, duration, fullReason)
    
    if success then
        TriggerClientEvent('notification', staffId, 
            string.format("Progressive ban issued: %s (%s)", GetPlayerName(targetId), banType)
        )
        
        -- Log escalation
        print(string.format("[PROGRESSIVE BAN] %s issued ban #%d to %s: %s", 
              GetPlayerName(staffId), nextBanNumber, GetPlayerName(targetId), reason))
    end
    
    return success, status
end

-- Command for progressive bans
RegisterCommand('pban', function(source, args)
    if #args < 2 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/pban <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
    
    progressiveBan(source, targetId, reason)
end, true)

Ban Appeal System

local banAppeals = {}

-- Submit ban appeal
local function submitBanAppeal(playerId, steamId, appealText)
    local appealId = string.format("%s_%d", steamId, os.time())
    
    banAppeals[appealId] = {
        playerId = playerId,
        steamId = steamId,
        playerName = GetPlayerName(playerId),
        appealText = appealText,
        submitTime = os.time(),
        status = "pending", -- pending, approved, denied
        reviewedBy = nil,
        reviewTime = nil,
        reviewNotes = nil
    }
    
    -- Notify staff about new appeal
    local staff = GetStaffOnline()
    for _, staffId in ipairs(staff) do
        TriggerClientEvent('notification', staffId, 
            string.format("New ban appeal from %s (ID: %s)", GetPlayerName(playerId), appealId)
        )
    end
    
    return appealId
end

-- Review ban appeal
local function reviewBanAppeal(staffId, appealId, decision, notes)
    local appeal = banAppeals[appealId]
    if not appeal then
        return false, "Appeal not found"
    end
    
    if appeal.status ~= "pending" then
        return false, "Appeal already reviewed"
    end
    
    appeal.status = decision -- "approved" or "denied"
    appeal.reviewedBy = GetPlayerName(staffId)
    appeal.reviewTime = os.time()
    appeal.reviewNotes = notes
    
    -- If approved, unban the player
    if decision == "approved" then
        local unbanSuccess = exports['zyrix_admin']:UnbanPlayer(staffId, appeal.steamId)
        if unbanSuccess then
            print(string.format("[APPEAL APPROVED] %s unbanned %s via appeal", 
                  GetPlayerName(staffId), appeal.playerName))
        else
            return false, "Failed to unban player"
        end
    end
    
    -- Log the review
    print(string.format("[APPEAL REVIEWED] %s %s appeal from %s: %s", 
          GetPlayerName(staffId), decision, appeal.playerName, notes or "No notes"))
    
    return true, "Appeal reviewed successfully"
end

-- Commands for appeal system
RegisterCommand('submitappeal', function(source, args)
    if #args < 1 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/submitappeal <appeal_text>"}
        })
        return
    end
    
    local appealText = table.concat(args, " ")
    local steamId = GetPlayerIdentifier(source, 0) -- Get Steam ID
    
    if string.len(appealText) < 20 then
        TriggerClientEvent('notification', source, "Appeal must be at least 20 characters long")
        return
    end
    
    local appealId = submitBanAppeal(source, steamId, appealText)
    TriggerClientEvent('notification', source, 
        string.format("Ban appeal submitted. Appeal ID: %s", appealId)
    )
end, false) -- Allow for all players

RegisterCommand('reviewappeal', function(source, args)
    if #args < 2 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/reviewappeal <appeal_id> <approve/deny> [notes]"}
        })
        return
    end
    
    local appealId = args[1]
    local decision = args[2]:lower()
    local notes = #args > 2 and table.concat(args, " ", 3) or nil
    
    if decision ~= "approve" and decision ~= "deny" then
        TriggerClientEvent('notification', source, "Decision must be 'approve' or 'deny'")
        return
    end
    
    local success, message = reviewBanAppeal(source, appealId, decision == "approve" and "approved" or "denied", notes)
    TriggerClientEvent('notification', source, message)
end, true)

RegisterCommand('listappeals', function(source, args)
    local pendingAppeals = {}
    for appealId, appeal in pairs(banAppeals) do
        if appeal.status == "pending" then
            table.insert(pendingAppeals, string.format("%s: %s", appealId, appeal.playerName))
        end
    end
    
    if #pendingAppeals > 0 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Pending Appeals]", table.concat(pendingAppeals, ", ")}
        })
    else
        TriggerClientEvent('notification', source, "No pending appeals")
    end
end, true)

Scheduled Ban System

local scheduledBans = {}

local function scheduleBan(staffId, targetId, delaySeconds, duration, reason, notifyPlayer)
    local banId = string.format("%d_%d", targetId, os.time())
    
    -- Notify target player if requested
    if notifyPlayer then
        TriggerClientEvent('chat:addMessage', targetId, {
            color = {255, 0, 0},
            multiline = false,
            args = {"[Warning]", string.format("You will be banned in %d seconds: %s", delaySeconds, reason)}
        })
    end
    
    -- Store scheduled ban info
    scheduledBans[banId] = {
        staffId = staffId,
        targetId = targetId,
        duration = duration,
        reason = reason,
        scheduleTime = os.time(),
        delaySeconds = delaySeconds
    }
    
    -- Schedule the ban
    SetTimeout(delaySeconds * 1000, function()
        -- Check if player is still online and ban is still scheduled
        if GetPlayerName(targetId) and scheduledBans[banId] then
            local success, status = exports['zyrix_admin']:BanPlayer(staffId, targetId, duration, reason)
            
            if success then
                print(string.format("[SCHEDULED BAN] %s banned %s after %d second delay", 
                      GetPlayerName(staffId), GetPlayerName(targetId), delaySeconds))
            end
            
            scheduledBans[banId] = nil
        end
    end)
    
    -- Notify staff member
    TriggerClientEvent('notification', staffId, 
        string.format("Ban scheduled for %s in %d seconds", GetPlayerName(targetId), delaySeconds)
    )
    
    return banId
end

RegisterCommand('sban', function(source, args)
    if #args < 4 then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/sban <player_id> <delay_seconds> <duration_minutes> <reason>"}
        })
        return
    end
    
    local targetId = tonumber(args[1])
    local delay = tonumber(args[2])
    local durationMinutes = tonumber(args[3])
    local reason = table.concat(args, " ", 4)
    
    if not targetId or not GetPlayerName(targetId) then
        TriggerClientEvent('notification', source, "Player not found")
        return
    end
    
    if not delay or delay <= 0 or delay > 600 then
        TriggerClientEvent('notification', source, "Delay must be between 1-600 seconds")
        return
    end
    
    if not durationMinutes or durationMinutes < 0 then
        TriggerClientEvent('notification', source, "Duration must be 0 or greater (0 = permanent)")
        return
    end
    
    local duration = durationMinutes == 0 and 0 or (durationMinutes * 60)
    scheduleBan(source, targetId, delay, duration, reason, true)
end, true)

Ban Category System

local banCategories = {
    ["cheating"] = {
        name = "Cheating/Exploiting",
        minDuration = BanDurations.ONE_DAY,
        maxDuration = BanDurations.PERMANENT,
        defaultDuration = BanDurations.ONE_WEEK,
        requiresEvidence = true,
        severity = 5
    },
    ["rdm"] = {
        name = "Random Death Match",
        minDuration = BanDurations.ONE_HOUR,
        maxDuration = BanDurations.THREE_DAYS,
        defaultDuration = BanDurations.SIX_HOURS,
        requiresEvidence = false,
        severity = 3
    },
    ["harassment"] = {
        name = "Harassment",
        minDuration = BanDurations.THREE_HOURS,
        maxDuration = BanDurations.PERMANENT,
        defaultDuration = BanDurations.ONE_DAY,
        requiresEvidence = true,
        severity = 4
    },
    ["griefing"] = {
        name = "Griefing",
        minDuration = BanDurations.ONE_HOUR,
        maxDuration = BanDurations.ONE_WEEK,
        defaultDuration = BanDurations.TWELVE_HOURS,
        requiresEvidence = false,
        severity = 2
    }
}

local function categorizedBan(staffId, targetId, category, customDuration, customReason, evidence)
    local banCategory = banCategories[category:lower()]
    if not banCategory then
        return false, "Invalid ban category"
    end
    
    -- Validate evidence requirement
    if banCategory.requiresEvidence and (not evidence or evidence == "") then
        return false, "This ban category requires evidence"
    end
    
    -- Determine duration
    local duration = customDuration or banCategory.defaultDuration
    
    -- Validate duration within category limits
    if duration < banCategory.minDuration or (banCategory.maxDuration > 0 and duration > banCategory.maxDuration) then
        return false, string.format("Duration must be between %d and %d seconds for %s", 
               banCategory.minDuration, banCategory.maxDuration, banCategory.name)
    end
    
    -- Format reason
    local reason = customReason or banCategory.name
    local fullReason = string.format("[%s] %s", banCategory.name:upper(), reason)
    
    if evidence then
        fullReason = fullReason .. " | Evidence: " .. evidence
    end
    
    -- Execute ban
    local success, status = exports['zyrix_admin']:BanPlayer(staffId, targetId, duration, fullReason)
    
    if success then
        -- Log with severity
        local severityText = {"LOW", "MINOR", "MODERATE", "SERIOUS", "CRITICAL"}
        print(string.format("[%s SEVERITY BAN] %s banned %s for %s", 
              severityText[banCategory.severity], GetPlayerName(staffId), 
              GetPlayerName(targetId), banCategory.name))
    end
    
    return success, status
end

RegisterCommand('cban', function(source, args)
    if #args < 3 then
        local categories = {}
        for category, data in pairs(banCategories) do
            table.insert(categories, string.format("%s (%s)", category, data.name))
        end
        
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Usage]", "/cban <player_id> <category> <reason> [evidence]"}
        })
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Categories]", table.concat(categories, ", ")}
        })
        return
    end
    
    local targetId = tonumber(args[1])
    local category = args[2]
    local reason = args[3]
    local evidence = #args > 3 and table.concat(args, " ", 4) or nil
    
    if not targetId or not GetPlayerName(targetId) then
        TriggerClientEvent('notification', source, "Player not found")
        return
    end
    
    local success, status = categorizedBan(source, targetId, category, nil, reason, evidence)
    
    if success then
        TriggerClientEvent('notification', source, 
            string.format("Issued %s ban to %s", category:upper(), GetPlayerName(targetId))
        )
    else
        TriggerClientEvent('notification', source, "Ban failed: " .. status)
    end
end, true)

Ban Statistics and Analytics

local banStatistics = {
    totalBans = 0,
    temporaryBans = 0,
    permanentBans = 0,
    categoryStats = {},
    staffStats = {},
    dailyStats = {}
}

-- Record ban statistics
local function recordBanStatistics(staffId, targetId, duration, reason, category)
    banStatistics.totalBans = banStatistics.totalBans + 1
    
    if duration == 0 then
        banStatistics.permanentBans = banStatistics.permanentBans + 1
    else
        banStatistics.temporaryBans = banStatistics.temporaryBans + 1
    end
    
    -- Category statistics
    if category then
        if not banStatistics.categoryStats[category] then
            banStatistics.categoryStats[category] = 0
        end
        banStatistics.categoryStats[category] = banStatistics.categoryStats[category] + 1
    end
    
    -- Staff statistics
    local staffName = GetPlayerName(staffId)
    if not banStatistics.staffStats[staffName] then
        banStatistics.staffStats[staffName] = 0
    end
    banStatistics.staffStats[staffName] = banStatistics.staffStats[staffName] + 1
    
    -- Daily statistics
    local today = os.date("%Y-%m-%d")
    if not banStatistics.dailyStats[today] then
        banStatistics.dailyStats[today] = 0
    end
    banStatistics.dailyStats[today] = banStatistics.dailyStats[today] + 1
end

-- Generate ban report
RegisterCommand('banstats', function(source, args)
    TriggerClientEvent('chat:addMessage', source, {
        args = {"[Ban Statistics]", string.format("Total: %d | Temporary: %d | Permanent: %d", 
               banStatistics.totalBans, banStatistics.temporaryBans, banStatistics.permanentBans)}
    })
    
    -- Show today's statistics
    local today = os.date("%Y-%m-%d")
    local todayCount = banStatistics.dailyStats[today] or 0
    TriggerClientEvent('chat:addMessage', source, {
        args = {"[Today]", string.format("%d bans issued", todayCount)}
    })
    
    -- Show top staff
    local topStaff = {}
    for staff, count in pairs(banStatistics.staffStats) do
        table.insert(topStaff, {name = staff, count = count})
    end
    table.sort(topStaff, function(a, b) return a.count > b.count end)
    
    if topStaff[1] then
        TriggerClientEvent('chat:addMessage', source, {
            args = {"[Most Active Staff]", string.format("%s: %d bans", topStaff[1].name, topStaff[1].count)}
        })
    end
end, true)

Error Handling

Handle common scenarios when banning players:
local function safeBanPlayer(staffId, targetId, duration, reason)
    -- Validate duration
    if duration < 0 then
        return false, "Duration cannot be negative"
    end
    
    if duration > 31536000 then -- 1 year
        return false, "Duration cannot exceed 1 year (31536000 seconds)"
    end
    
    -- Validate reason
    if not reason or string.len(reason) < 5 then
        return false, "Ban reason must be at least 5 characters"
    end
    
    if string.len(reason) > 1000 then
        return false, "Ban reason too long (max 1000 characters)"
    end
    
    -- Check if player exists
    if not GetPlayerName(targetId) then
        return false, "Target player not found"
    end
    
    -- Prevent self-ban
    if staffId == targetId then
        return false, "Cannot ban yourself"
    end
    
    -- Check if already banned
    local isBanned = exports['zyrix_admin']:CheckBan(targetId)
    if isBanned then
        return false, "Player is already banned"
    end
    
    -- Attempt ban
    local success, status = exports['zyrix_admin']:BanPlayer(staffId, targetId, duration, reason)
    
    if not success then
        local errorMessages = {
            no_permission = "You don't have permission to ban players",
            player_not_found = "Target player not found",
            already_banned = "Player is already banned",
            invalid_duration = "Invalid ban duration",
            invalid_reason = "Invalid ban reason"
        }
        
        local message = errorMessages[status] or ("Ban failed: " .. status)
        TriggerClientEvent('notification', staffId, message)
    else
        -- Log successful ban
        local durationText = duration == 0 and "permanent" or string.format("%d seconds", duration)
        print(string.format("[BAN] %s banned %s for %s: %s", 
              GetPlayerName(staffId), GetPlayerName(targetId), durationText, reason))
    end
    
    return success, status
end
Permanent bans (duration = 0) should be used carefully and only for serious violations. Always document the reason thoroughly.
Implement progressive ban systems and appeal processes to maintain fair moderation while protecting your server from repeat offenders.