SkillCheck, Hacking & Lockpicking

The B2Lib minigame system provides three interactive minigames: skillcheck (timing-based), lockpicking, and hacking. These systems are perfect for adding interactive elements to your gameplay mechanics.

Dark Skillcheck
Dark Hacking Input Phase
Dark Hacking Memory Phase
Dark Lockpicking Minigame

skillcheck

Display a comprehensive timing-based circular skillcheck minigame with advanced difficulty scaling and customization options.

-- Client-side
B2Lib.UI.skillcheck(options)

-- Server-side (via exports)
exports.b2lib:showPlayerSkillcheck(playerId, options)

Parameters:

  • options (table, required): Comprehensive skillcheck configuration object

  • playerId (number, server-side only): Player server ID to show skillcheck to

Configuration Options:

  • id (string, optional): Unique skillcheck identifier for tracking and management (default: auto-generated UUID)

  • position (string, optional): Screen position ('center', 'top-left', 'top-right', 'bottom-left', 'bottom-right') (default: 'center')

  • difficulty (string, optional): Difficulty preset ('easy', 'medium', 'hard', 'expert', 'custom') (default: 'medium')

  • speed (number, optional): Indicator movement speed multiplier with precision control (default: 2.0)

  • targetAngle (number, optional): Target zone position in degrees with randomization support (default: 90)

  • targetSize (number, optional): Target zone size in degrees with adaptive scaling (default: 45)

  • perfectSize (number, optional): Perfect zone size in degrees for bonus scoring (default: 15)

  • indicatorSize (number, optional): Moving indicator size in degrees with visual clarity (default: 20)

  • maxAttempts (number, optional): Maximum attempts allowed with progressive difficulty (default: 3)

  • pattern (string, optional): Movement pattern ('continuous', 'bounce', 'accelerate', 'random') (default: 'continuous')

  • direction (number, optional): Initial direction (1 for clockwise, -1 for counter-clockwise) (default: 1)

  • key (string, optional): Primary interaction key with customizable bindings (default: 'Space')

  • alternativeKeys (table, optional): Alternative key bindings for accessibility

  • timeLimit (number, optional): Overall time limit in milliseconds (default: 30000)

  • zones (table, optional): Multiple target zones for advanced skillchecks

  • randomizeTarget (boolean, optional): Randomize target position each attempt (default: false)

  • progressiveSpeed (boolean, optional): Increase speed with each successful attempt (default: false)

  • vibration (boolean, optional): Controller vibration feedback (default: true)

  • soundProfile (string, optional): Audio profile ('default', 'mechanical', 'electronic', 'silent') (default: 'default')

Visual Customization:

  • size (string, optional): Circle diameter with responsive scaling (default: '200px')

  • strokeWidth (number, optional): Ring thickness with visual clarity optimization (default: 8)

  • centerSize (string, optional): Center button size with proportional scaling (default: '48px')

  • textSize (string, optional): Text font size with readability optimization (default: '1rem')

  • shadow (string, optional): Drop shadow intensity ('none', 'sm', 'md', 'lg', 'xl') (default: 'lg')

  • targetOpacity (number, optional): Target zone opacity with accessibility compliance (default: 0.8)

  • perfectOpacity (number, optional): Perfect zone opacity with visual distinction (default: 0.9)

  • showAttempts (boolean, optional): Display attempts counter with progress indication (default: true)

  • showTimer (boolean, optional): Display countdown timer with visual urgency (default: false)

  • showProgress (boolean, optional): Display overall progress indicator (default: false)

  • glowEffect (boolean, optional): Enable glow effects for enhanced visibility (default: true)

  • colorScheme (table, optional): Custom color scheme with theme integration

  • animations (table, optional): Custom animation settings and transitions

Returns: string|boolean - Skillcheck ID on success, false on failure

Controls:

  • Space (or configured key): Attempt to hit the target zone

  • Escape: Cancel skillcheck (if enabled)

  • Alternative Keys: Custom key bindings for accessibility

Example:

-- Basic skillcheck
local skillcheckId = B2Lib.UI.skillcheck({
    difficulty = 'medium'
})

-- Advanced skillcheck with comprehensive configuration
B2Lib.UI.skillcheck({
    id = 'crafting_skillcheck',
    position = 'center',
    difficulty = 'hard',
    speed = 2.5,
    targetSize = 30,
    perfectSize = 10,
    maxAttempts = 2,
    pattern = 'accelerate',
    timeLimit = 15000,
    randomizeTarget = true,
    progressiveSpeed = true,
    showTimer = true,
    showProgress = true,
    glowEffect = true,
    vibration = true,
    soundProfile = 'mechanical',
    colorScheme = {
        target = '#10b981',
        perfect = '#fbbf24',
        indicator = '#3b82f6',
        background = 'rgba(0,0,0,0.8)'
    },
    alternativeKeys = { 'Enter', 'F' }
})

-- Multi-zone skillcheck for complex interactions
B2Lib.UI.skillcheck({
    difficulty = 'expert',
    zones = {
        { angle = 45, size = 20, type = 'target', color = '#10b981' },
        { angle = 135, size = 15, type = 'perfect', color = '#fbbf24' },
        { angle = 225, size = 25, type = 'target', color = '#10b981' },
        { angle = 315, size = 10, type = 'perfect', color = '#fbbf24' }
    },
    pattern = 'random',
    maxAttempts = 1,
    timeLimit = 20000
})

-- Server-side skillcheck for validation
exports.b2lib:showPlayerSkillcheck(playerId, {
    id = 'server_validation_check',
    difficulty = 'medium',
    maxAttempts = 3,
    timeLimit = 30000,
    antiCheat = {
        validateTiming = true,
        maxDeviation = 50,
        logAttempts = true
    }
})

-- Accessibility-focused skillcheck
B2Lib.UI.skillcheck({
    difficulty = 'easy',
    size = '300px',
    strokeWidth = 12,
    targetSize = 60,
    perfectSize = 30,
    maxAttempts = 5,
    speed = 1.5,
    glowEffect = true,
    showTimer = true,
    showProgress = true,
    alternativeKeys = { 'Enter', 'F', 'E' },
    colorScheme = {
        target = '#22c55e',      -- High contrast green
        perfect = '#eab308',     -- High contrast yellow
        indicator = '#3b82f6',   -- High contrast blue
        text = '#ffffff'         -- High contrast text
    }
})

-- Progressive difficulty skillcheck
B2Lib.UI.skillcheck({
    difficulty = 'custom',
    speed = 1.8,
    targetSize = 40,
    perfectSize = 15,
    maxAttempts = 3,
    progressiveSpeed = true,
    pattern = 'continuous',
    timeLimit = 45000,
    zones = {
        { angle = 90, size = 40, type = 'target' }
    },
    callbacks = {
        onAttempt = function(attempt, success, perfect)
            if success and perfect then
                -- Bonus for perfect hits
                TriggerEvent('skillcheck:perfectBonus')
            end
        end,
        onComplete = function(success, attempts, totalTime)
            -- Track performance metrics
            TriggerServerEvent('skillcheck:logPerformance', {
                success = success,
                attempts = attempts,
                time = totalTime,
                difficulty = 'custom'
            })
        end
    }
})

hideSkillcheck

Hide the currently active skillcheck minigame with optional cleanup and result handling.

-- Client-side
B2Lib.UI.hideSkillcheck(options)

-- Server-side (via exports)
exports.b2lib:hidePlayerSkillcheck(playerId, options)

Parameters:

  • options (table, optional): Hide operation configuration

  • playerId (number, server-side only): Player server ID to hide skillcheck for

Configuration Options:

  • reason (string, optional): Hide reason ('completed', 'cancelled', 'timeout', 'manual') (default: 'manual')

  • animate (boolean, optional): Animate hide transition (default: true)

  • cleanup (boolean, optional): Clean up event handlers and resources (default: true)

  • triggerEvent (boolean, optional): Trigger skillcheck-closed event (default: true)

Returns: boolean - Success status indicating skillcheck hide completion

Example:

-- Basic hide
local success = B2Lib.UI.hideSkillcheck()

-- Hide with specific reason
B2Lib.UI.hideSkillcheck({
    reason = 'timeout',
    animate = true,
    cleanup = true
})

-- Server-side hide
exports.b2lib:hidePlayerSkillcheck(playerId, {
    reason = 'cancelled',
    triggerEvent = true
})

-- Hide without animation for immediate cleanup
B2Lib.UI.hideSkillcheck({
    animate = false,
    cleanup = true,
    reason = 'manual'
})

lockpicking

Display a comprehensive tension-based lockpicking minigame with realistic mechanics and advanced customization options.

-- Client-side
B2Lib.UI.lockpicking(options)

-- Server-side (via exports)
exports.b2lib:showPlayerLockpicking(playerId, options)

Parameters:

  • options (table, required): Comprehensive lockpicking configuration object

  • playerId (number, server-side only): Player server ID to show lockpicking to

Configuration Options:

  • id (string, optional): Unique lockpicking identifier for tracking and management (default: auto-generated UUID)

  • name (string, optional): Lock name/description for context display (default: 'Lock')

  • position (string, optional): Screen position with responsive placement (default: 'center')

  • difficulty (string, optional): Difficulty preset ('easy', 'medium', 'hard', 'expert', 'custom') (default: 'medium')

  • timeLimit (number, optional): Time limit in milliseconds with visual countdown (default: 60000)

  • pins (table, optional): Custom pin configurations for advanced locks

  • lockType (string, optional): Lock mechanism type ('standard', 'deadbolt', 'electronic', 'masterwork') (default: 'standard')

  • tensionSensitivity (number, optional): Tension control sensitivity (0.1-2.0) (default: 1.0)

  • breakChance (number, optional): Lockpick break probability on failure (0-1) (default: 0.1)

  • sweetSpotSize (number, optional): Sweet spot size for successful picking (default: 15)

  • rotationSpeed (number, optional): Lockpick rotation speed multiplier (default: 1.0)

  • maxAttempts (number, optional): Maximum picking attempts before failure (default: 5)

  • vibrationFeedback (boolean, optional): Controller vibration for tension feedback (default: true)

  • audioFeedback (boolean, optional): Audio cues for picking feedback (default: true)

  • visualFeedback (boolean, optional): Visual tension and feedback indicators (default: true)

  • progressiveBreaking (boolean, optional): Progressive lockpick wear and breaking (default: true)

  • realismMode (boolean, optional): Enhanced realism with complex mechanics (default: false)

Advanced Options:

  • customPins (table, optional): Detailed pin configuration array

  • lockpickQuality (string, optional): Lockpick quality affecting durability ('poor', 'standard', 'professional', 'masterwork') (default: 'standard')

  • environmentalFactors (table, optional): Environmental effects on difficulty

  • skillModifiers (table, optional): Player skill-based modifications

  • antiCheat (table, optional): Server-side validation and anti-cheat measures

Returns: string|boolean - Lockpicking ID on success, false on failure

Controls:

  • A/D Keys: Rotate lockpick left/right with precision control

  • W/S Keys: Apply tension up/down with feedback

  • Enter: Attempt to pick the lock at current position

  • Escape: Cancel lockpicking operation

  • Shift: Fine adjustment mode for precise control

  • Space: Reset lockpick position to center

Example:

-- Basic lockpicking
local lockpickId = B2Lib.UI.lockpicking({
    name = 'Safe Lock',
    difficulty = 'hard',
    timeLimit = 45000
})

-- Advanced lockpicking with comprehensive configuration
B2Lib.UI.lockpicking({
    id = 'vault_lock_001',
    name = 'Bank Vault Lock',
    position = 'center',
    difficulty = 'expert',
    lockType = 'masterwork',
    timeLimit = 120000,
    tensionSensitivity = 0.8,
    breakChance = 0.25,
    sweetSpotSize = 8,
    maxAttempts = 3,
    vibrationFeedback = true,
    audioFeedback = true,
    realismMode = true,
    customPins = {
        { position = 45, difficulty = 0.8, sweetSpot = 8 },
        { position = 120, difficulty = 0.9, sweetSpot = 6 },
        { position = 200, difficulty = 0.7, sweetSpot = 10 },
        { position = 280, difficulty = 0.95, sweetSpot = 5 },
        { position = 340, difficulty = 0.85, sweetSpot = 7 }
    },
    lockpickQuality = 'professional',
    environmentalFactors = {
        lighting = 0.8,     -- Poor lighting increases difficulty
        noise = 0.9,        -- Ambient noise affects audio cues
        temperature = 1.0   -- Normal temperature
    },
    skillModifiers = {
        experience = getPlayerLockpickingSkill(), -- Custom function
        dexterity = getPlayerDexterity(),
        tools = hasLockpickingTools()
    }
})

-- Server-side lockpicking with validation
exports.b2lib:showPlayerLockpicking(playerId, {
    id = 'server_validated_lock',
    name = 'Secure Container',
    difficulty = 'hard',
    timeLimit = 60000,
    antiCheat = {
        validateTiming = true,
        maxDeviation = 100,
        logAttempts = true,
        requireServerValidation = true
    }
})

-- Electronic lock with different mechanics
B2Lib.UI.lockpicking({
    name = 'Electronic Lock',
    lockType = 'electronic',
    difficulty = 'medium',
    timeLimit = 45000,
    customPins = {
        { type = 'digital', sequence = [1, 3, 7, 2], timing = 1500 },
        { type = 'digital', sequence = [5, 9, 4, 6], timing = 1200 }
    },
    visualFeedback = true,
    audioFeedback = false, -- Silent electronic lock
    breakChance = 0.05     -- Lower break chance for electronic picks
})

-- Beginner-friendly lockpicking
B2Lib.UI.lockpicking({
    name = 'Practice Lock',
    difficulty = 'easy',
    timeLimit = 90000,
    sweetSpotSize = 25,
    breakChance = 0.02,
    maxAttempts = 10,
    tensionSensitivity = 1.5,
    vibrationFeedback = true,
    visualFeedback = true,
    progressiveBreaking = false
})

hideLockpicking()

Hide the currently active lockpicking minigame.

Returns: boolean - Success status

Example:

-- Hide lockpicking
local success = B2Lib.UI.hideLockpicking()

hacking

Display a comprehensive sequence-based hacking minigame with terminal-style interface and advanced customization options.

-- Client-side
B2Lib.UI.hacking(options)

-- Server-side (via exports)
exports.b2lib:showPlayerHacking(playerId, options)

Parameters:

  • options (table, required): Comprehensive hacking configuration object

  • playerId (number, server-side only): Player server ID to show hacking to

Configuration Options:

  • id (string, optional): Unique hacking identifier for tracking and management (default: auto-generated UUID)

  • name (string, optional): Terminal name/description for context display (default: 'Terminal')

  • position (string, optional): Screen position with responsive placement (default: 'center')

  • difficulty (string, optional): Difficulty preset ('easy', 'medium', 'hard', 'expert', 'custom') (default: 'medium')

  • timeLimit (number, optional): Time limit in milliseconds with visual countdown (default: 30000)

  • sequences (table, optional): Array of sequence configurations for multi-stage hacking

  • hackType (string, optional): Hacking mechanism type ('sequence', 'matrix', 'cipher', 'network') (default: 'sequence')

  • codeLength (number, optional): Individual code length for sequence generation (default: 2)

  • sequenceCount (number, optional): Number of sequences to complete (default: 2)

  • showTime (number, optional): Time to display each sequence in milliseconds (default: 2000)

  • inputTime (number, optional): Time allowed for input after sequence display (default: 5000)

  • caseSensitive (boolean, optional): Whether codes are case-sensitive (default: false)

  • allowPartialInput (boolean, optional): Allow partial sequence input (default: true)

  • progressiveSpeed (boolean, optional): Increase speed with each successful sequence (default: false)

  • randomizeSequences (boolean, optional): Randomize sequence order and content (default: true)

  • visualEffects (boolean, optional): Enable terminal-style visual effects (default: true)

  • audioFeedback (boolean, optional): Audio cues for typing and feedback (default: true)

  • accessLevel (string, optional): Required access level ('user', 'admin', 'root', 'system') (default: 'user')

Advanced Options:

  • customSequences (table, optional): Predefined sequence configurations

  • matrixSize (table, optional): Matrix dimensions for matrix-type hacking

  • networkNodes (table, optional): Network node configuration for network-type hacking

  • encryptionLevel (string, optional): Encryption complexity ('basic', 'advanced', 'military') (default: 'basic')

  • antiCheat (table, optional): Server-side validation and anti-cheat measures

  • skillModifiers (table, optional): Player skill-based modifications

Returns: string|boolean - Hacking ID on success, false on failure

Controls:

  • Alphanumeric Keys: Input sequence codes with real-time validation

  • Enter: Confirm sequence input and proceed to next stage

  • Backspace: Delete last character in current input

  • Escape: Cancel hacking operation (if enabled)

  • Tab: Switch between input fields in matrix mode

  • Arrow Keys: Navigate network nodes in network mode

Example:

-- Basic hacking
local hackId = B2Lib.UI.hacking({
    name = 'Security Terminal',
    difficulty = 'medium',
    timeLimit = 30000
})

-- Advanced hacking with comprehensive configuration
B2Lib.UI.hacking({
    id = 'database_hack_001',
    name = 'Corporate Database',
    position = 'center',
    difficulty = 'hard',
    hackType = 'sequence',
    timeLimit = 45000,
    sequenceCount = 3,
    codeLength = 3,
    showTime = 1800,
    inputTime = 4000,
    caseSensitive = true,
    progressiveSpeed = true,
    randomizeSequences = true,
    visualEffects = true,
    audioFeedback = true,
    accessLevel = 'admin',
    customSequences = {
        { codes = ['A7F', 'B2X', 'C9K'], showTime = 2000, inputTime = 5000 },
        { codes = ['D4M', 'E8P', 'F1Q'], showTime = 1500, inputTime = 4000 },
        { codes = ['G6R', 'H3S', 'I7T'], showTime = 1200, inputTime = 3500 }
    },
    encryptionLevel = 'advanced',
    skillModifiers = {
        hackingSkill = getPlayerHackingSkill(),
        intelligence = getPlayerIntelligence(),
        equipment = hasHackingEquipment()
    }
})

-- Matrix-type hacking for complex systems
B2Lib.UI.hacking({
    name = 'Security Matrix',
    hackType = 'matrix',
    difficulty = 'expert',
    timeLimit = 60000,
    matrixSize = { width = 8, height = 6 },
    sequenceCount = 4,
    visualEffects = true,
    encryptionLevel = 'military',
    accessLevel = 'root'
})

-- Network-type hacking for distributed systems
B2Lib.UI.hacking({
    name = 'Network Infrastructure',
    hackType = 'network',
    difficulty = 'expert',
    timeLimit = 90000,
    networkNodes = {
        { id = 'firewall', type = 'security', difficulty = 0.8 },
        { id = 'database', type = 'data', difficulty = 0.9 },
        { id = 'admin', type = 'control', difficulty = 0.95 }
    },
    accessLevel = 'system'
})

-- Server-side hacking with validation
exports.b2lib:showPlayerHacking(playerId, {
    id = 'server_validated_hack',
    name = 'Secure Terminal',
    difficulty = 'hard',
    timeLimit = 30000,
    antiCheat = {
        validateTiming = true,
        maxInputSpeed = 200,
        logAttempts = true,
        requireServerValidation = true
    }
})

-- Cipher-type hacking for encryption challenges
B2Lib.UI.hacking({
    name = 'Encrypted Communications',
    hackType = 'cipher',
    difficulty = 'expert',
    timeLimit = 120000,
    encryptionLevel = 'military',
    codeLength = 4,
    sequenceCount = 5,
    caseSensitive = true,
    allowPartialInput = false,
    visualEffects = true,
    audioFeedback = false -- Silent operation
})

hideHacking

Hide the currently active hacking minigame with optional cleanup and result handling.

-- Client-side
B2Lib.UI.hideHacking(options)

-- Server-side (via exports)
exports.b2lib:hidePlayerHacking(playerId, options)

Parameters:

  • options (table, optional): Hide operation configuration

  • playerId (number, server-side only): Player server ID to hide hacking for

Configuration Options:

  • reason (string, optional): Hide reason ('completed', 'cancelled', 'timeout', 'detected', 'manual') (default: 'manual')

  • animate (boolean, optional): Animate hide transition with terminal effects (default: true)

  • cleanup (boolean, optional): Clean up event handlers and resources (default: true)

  • triggerEvent (boolean, optional): Trigger hacking-closed event (default: true)

  • securityAlert (boolean, optional): Trigger security alert on forced closure (default: false)

Returns: boolean - Success status indicating hacking hide completion

Example:

-- Basic hide
local success = B2Lib.UI.hideHacking()

-- Hide with security alert
B2Lib.UI.hideHacking({
    reason = 'detected',
    securityAlert = true,
    animate = true,
    cleanup = true
})

-- Server-side hide
exports.b2lib:hidePlayerHacking(playerId, {
    reason = 'timeout',
    triggerEvent = true
})

-- Emergency hide without animation
B2Lib.UI.hideHacking({
    animate = false,
    cleanup = true,
    reason = 'manual'
})

Server-Side Usage

The Skillcheck module provides comprehensive server-side integration for centralized minigame management, player validation, and anti-cheat protection. Server-side functions enable administrators to control minigame access, validate results, and maintain game integrity across all connected players.

Server-Side Functions

All client-side skillcheck functions have corresponding server-side exports that take a playerId as the first parameter:

-- Show skillcheck to specific player
exports.b2lib:showPlayerSkillcheck(playerId, options)

-- Show lockpicking to specific player  
exports.b2lib:showPlayerLockpicking(playerId, options)

-- Show hacking to specific player
exports.b2lib:showPlayerHacking(playerId, options)

-- Hide minigames for specific player
exports.b2lib:hidePlayerSkillcheck(playerId, options)
exports.b2lib:hidePlayerLockpicking(playerId, options)
exports.b2lib:hidePlayerHacking(playerId, options)

-- Get player minigame status
exports.b2lib:getPlayerMinigameStatus(playerId)

-- Validate minigame results (anti-cheat)
exports.b2lib:validateMinigameResult(playerId, resultData)

-- Get player minigame statistics
exports.b2lib:getPlayerMinigameStats(playerId, minigameType)

-- Reset player minigame cooldowns
exports.b2lib:resetPlayerMinigameCooldowns(playerId)

Server-Side Events

Handle minigame results and player actions on the server:

-- Skillcheck result validation
RegisterNetEvent('b2lib:server:skillcheck-result', function(data)
    local playerId = source
    
    -- Validate result authenticity
    local isValid = exports.b2lib:validateMinigameResult(playerId, data)
    
    if isValid and data.success then
        -- Grant rewards or progress
        if data.skillcheckId == 'crafting_check' then
            TriggerEvent('crafting:completeItem', playerId, data.itemId)
        elseif data.skillcheckId == 'repair_check' then
            TriggerEvent('vehicle:completeRepair', playerId, data.vehicleId)
        end
        
        -- Update player statistics
        exports.b2lib:updatePlayerMinigameStats(playerId, 'skillcheck', {
            success = true,
            attempts = data.attempts,
            perfect = data.perfect,
            time = data.timeElapsed
        })
    else
        -- Handle failure or cheating attempt
        if not isValid then
            print(('Player %d attempted to cheat skillcheck: %s'):format(playerId, json.encode(data)))
            -- Optionally kick or ban player
        end
    end
end)

-- Lockpicking result validation
RegisterNetEvent('b2lib:server:lockpicking-result', function(data)
    local playerId = source
    
    if exports.b2lib:validateMinigameResult(playerId, data) and data.success then
        -- Unlock door or container
        if data.lockId then
            TriggerEvent('doors:unlock', data.lockId, playerId)
        end
        
        -- Consume lockpick durability
        exports.inventory:removeItemDurability(playerId, 'lockpick', 10)
        
        -- Add experience
        exports.skills:addExperience(playerId, 'lockpicking', 25)
    end
end)

-- Hacking result validation
RegisterNetEvent('b2lib:server:hacking-result', function(data)
    local playerId = source
    
    if exports.b2lib:validateMinigameResult(playerId, data) and data.success then
        -- Grant system access
        if data.accessLevel == 'admin' then
            TriggerEvent('computer:grantAccess', playerId, data.terminalId, 'admin')
        elseif data.accessLevel == 'root' then
            TriggerEvent('computer:grantAccess', playerId, data.terminalId, 'root')
        end
        
        -- Log security breach
        exports.logging:logSecurityBreach(playerId, data.terminalId, data.accessLevel)
    end
end)

Anti-Cheat Integration

Implement comprehensive anti-cheat measures for minigame integrity:

-- Configure anti-cheat settings
Config.AntiCheat = {
    skillcheck = {
        enabled = true,
        maxDeviation = 50,          -- Maximum timing deviation in ms
        minAttemptTime = 200,       -- Minimum time between attempts
        maxSuccessRate = 0.95,      -- Maximum success rate threshold
        logSuspiciousActivity = true
    },
    lockpicking = {
        enabled = true,
        maxRotationSpeed = 180,     -- Maximum rotation speed per second
        minPickTime = 1000,         -- Minimum time to pick a lock
        validateTension = true,     -- Validate tension mechanics
        logAttempts = true
    },
    hacking = {
        enabled = true,
        maxInputSpeed = 200,        -- Maximum characters per minute
        validateSequences = true,   -- Validate sequence accuracy
        checkTimingPatterns = true, -- Detect inhuman timing patterns
        requireServerValidation = true
    }
}

-- Server-side validation function
function ValidateMinigameResult(playerId, minigameType, resultData)
    local config = Config.AntiCheat[minigameType]
    
    if not config or not config.enabled then
        return true
    end
    
    -- Check timing patterns
    if resultData.timingData then
        local avgTime = 0
        for _, time in ipairs(resultData.timingData) do
            avgTime = avgTime + time
        end
        avgTime = avgTime / #resultData.timingData
        
        if avgTime < config.minAttemptTime then
            exports.logging:logAntiCheat(playerId, minigameType, 'timing_too_fast', {
                avgTime = avgTime,
                minRequired = config.minAttemptTime
            })
            return false
        end
    end
    
    -- Check success rate patterns
    local playerStats = exports.b2lib:getPlayerMinigameStats(playerId, minigameType)
    if playerStats.successRate > config.maxSuccessRate and playerStats.totalAttempts > 10 then
        exports.logging:logAntiCheat(playerId, minigameType, 'success_rate_too_high', {
            successRate = playerStats.successRate,
            maxAllowed = config.maxSuccessRate
        })
        return false
    end
    
    return true
end

-- Export validation function
exports('validateMinigameResult', ValidateMinigameResult)

Player Management

Manage player minigame access and restrictions:

-- Check if player can access minigame
function CanPlayerAccessMinigame(playerId, minigameType)
    local playerData = GetPlayerData(playerId)
    
    -- Check cooldowns
    local lastAttempt = playerData.minigameCooldowns[minigameType]
    if lastAttempt and (GetGameTimer() - lastAttempt) < Config.MinigameCooldowns[minigameType] then
        return false, 'cooldown'
    end
    
    -- Check permissions
    if minigameType == 'hacking' and not playerData.hasHackingSkill then
        return false, 'no_skill'
    end
    
    -- Check equipment
    if minigameType == 'lockpicking' and not exports.inventory:hasItem(playerId, 'lockpick') then
        return false, 'no_equipment'
    end
    
    return true
end

-- Set minigame cooldown
function SetMinigameCooldown(playerId, minigameType)
    local playerData = GetPlayerData(playerId)
    playerData.minigameCooldowns[minigameType] = GetGameTimer()
    SavePlayerData(playerId, playerData)
end

-- Command to show skillcheck to player (admin only)
RegisterCommand('giveskillcheck', function(source, args)
    if not IsPlayerAceAllowed(source, 'admin') then
        return
    end
    
    local targetId = tonumber(args[1])
    local difficulty = args[2] or 'medium'
    
    if targetId and GetPlayerName(targetId) then
        exports.b2lib:showPlayerSkillcheck(targetId, {
            difficulty = difficulty,
            timeLimit = 30000,
            maxAttempts = 3
        })
        
        TriggerClientEvent('chat:addMessage', source, {
            args = {'System', ('Skillcheck sent to player %d'):format(targetId)}
        })
    end
end, true)

Events

The Skillcheck module provides a comprehensive event system for handling minigame results, progress tracking, and integration with other game systems. Events are triggered both client-side and server-side for maximum flexibility and security.

Client-Side Events

b2lib:skillcheck-result

Triggered when skillcheck minigame completes with comprehensive result data.

AddEventHandler('b2lib:skillcheck-result', function(data)
    -- data.success (boolean): Whether skillcheck was successful
    -- data.perfect (boolean): Whether hit was in perfect zone
    -- data.attempts (number): Number of attempts used
    -- data.angle (number): Final indicator angle when attempted
    -- data.skillcheckId (string): Unique skillcheck identifier
    -- data.timeElapsed (number): Total time taken in milliseconds
    -- data.difficulty (string): Difficulty level used
    -- data.targetAngle (number): Target zone center angle
    -- data.targetSize (number): Target zone size in degrees
    -- data.perfectSize (number): Perfect zone size in degrees
    -- data.timingData (table): Array of attempt timing data
    
    if data.success then
        print(('Skillcheck passed! Perfect: %s, Attempts: %d, Time: %.1fs'):format(
            data.perfect and 'Yes' or 'No', 
            data.attempts, 
            data.timeElapsed / 1000
        ))
        
        -- Handle perfect hits with bonus rewards
        if data.perfect then
            TriggerEvent('player:addExperience', 'precision', 50)
            B2Lib.UI.notify({
                type = 'success',
                title = 'Perfect!',
                message = 'Bonus experience gained!'
            })
        end
        
        -- Progress tracking for crafting systems
        if data.skillcheckId:match('crafting_') then
            TriggerEvent('crafting:skillcheckComplete', data.skillcheckId, data)
        end
    else
        print(('Skillcheck failed after %d attempts (%.1fs)'):format(
            data.attempts, 
            data.timeElapsed / 1000
        ))
        
        -- Handle failure consequences
        if data.skillcheckId:match('repair_') then
            TriggerEvent('vehicle:repairFailed', data.skillcheckId)
        end
    end
end)

b2lib:skillcheck-opened

Triggered when skillcheck minigame opens.

AddEventHandler('b2lib:skillcheck-opened', function(data)
    -- data.skillcheckId (string): Unique identifier
    -- data.difficulty (string): Difficulty level
    -- data.position (string): Screen position
    -- data.timeLimit (number): Time limit in milliseconds
    
    print(('Skillcheck opened: %s (Difficulty: %s)'):format(data.skillcheckId, data.difficulty))
    
    -- Disable other UI elements during skillcheck
    TriggerEvent('hud:setVisible', false)
    TriggerEvent('chat:setVisible', false)
end)

b2lib:skillcheck-closed

Triggered when skillcheck minigame closes.

AddEventHandler('b2lib:skillcheck-closed', function(data)
    -- data.skillcheckId (string): Unique identifier
    -- data.reason (string): Close reason ('completed', 'cancelled', 'timeout')
    -- data.wasSuccessful (boolean): Whether skillcheck was successful
    
    print(('Skillcheck closed: %s (Reason: %s)'):format(data.skillcheckId, data.reason))
    
    -- Re-enable UI elements
    TriggerEvent('hud:setVisible', true)
    TriggerEvent('chat:setVisible', true)
end)

b2lib:lockpicking-result

Triggered when lockpicking minigame completes with detailed result information.

AddEventHandler('b2lib:lockpicking-result', function(data)
    -- data.success (boolean): Whether lockpicking was successful
    -- data.timeElapsed (number): Time taken in milliseconds
    -- data.finalRotation (number): Final lockpick rotation angle
    -- data.attempts (number): Number of picking attempts
    -- data.reason (string): Result reason ('success', 'timeout', 'failed', 'cancelled', 'broken')
    -- data.difficulty (string): Difficulty level
    -- data.lockId (string): Lock identifier
    -- data.lockType (string): Type of lock picked
    -- data.tensionData (table): Tension application data
    -- data.rotationData (table): Rotation movement data
    -- data.breakageOccurred (boolean): Whether lockpick broke
    
    if data.success then
        print(('Lock picked successfully! Time: %.1fs, Attempts: %d'):format(
            data.timeElapsed / 1000, 
            data.attempts
        ))
        
        -- Award experience based on difficulty and time
        local expGain = math.floor(100 * (data.difficulty == 'expert' and 2 or 1) * (30000 / data.timeElapsed))
        TriggerEvent('player:addExperience', 'lockpicking', expGain)
        
        -- Handle specific lock types
        if data.lockType == 'safe' then
            TriggerEvent('safe:unlock', data.lockId)
        elseif data.lockType == 'door' then
            TriggerEvent('door:unlock', data.lockId)
        end
    else
        local reasonText = {
            timeout = 'Time ran out',
            failed = 'Failed to pick',
            cancelled = 'Cancelled',
            broken = 'Lockpick broke'
        }
        
        print(('Lockpicking failed: %s after %d attempts'):format(
            reasonText[data.reason] or data.reason, 
            data.attempts
        ))
        
        -- Handle lockpick breakage
        if data.breakageOccurred then
            TriggerServerEvent('inventory:removeItem', 'lockpick', 1)
            B2Lib.UI.notify({
                type = 'error',
                title = 'Lockpick Broken',
                message = 'Your lockpick snapped!'
            })
        end
    end
end)

b2lib:lockpicking-opened

Triggered when lockpicking minigame opens.

AddEventHandler('b2lib:lockpicking-opened', function(data)
    -- data.lockId (string): Lock identifier
    -- data.lockType (string): Type of lock
    -- data.difficulty (string): Difficulty level
    -- data.timeLimit (number): Time limit
    
    print(('Lockpicking started: %s (%s difficulty)'):format(data.lockType, data.difficulty))
    
    -- Start lockpicking audio ambience
    TriggerEvent('audio:playAmbience', 'lockpicking')
end)

b2lib:hacking-result

Triggered when hacking minigame completes with comprehensive access information.

AddEventHandler('b2lib:hacking-result', function(data)
    -- data.success (boolean): Whether hacking was successful
    -- data.accessLevel (string): Achieved access level
    -- data.timeElapsed (number): Time taken in milliseconds
    -- data.sequencesCompleted (number): Number of sequences completed
    -- data.totalSequences (number): Total sequences required
    -- data.detected (boolean): Whether security detected the intrusion
    -- data.terminalId (string): Terminal identifier
    -- data.hackType (string): Type of hack performed
    -- data.encryptionLevel (string): Encryption level overcome
    -- data.inputAccuracy (number): Input accuracy percentage
    -- data.securityAlerts (number): Number of security alerts triggered
    
    if data.success then
        print(('Hacking successful! Access level: %s, Time: %.1fs'):format(
            data.accessLevel, 
            data.timeElapsed / 1000
        ))
        
        -- Handle different access levels
        if data.accessLevel == 'admin' then
            TriggerEvent('computer:grantAdminAccess', data.terminalId)
            B2Lib.UI.notify({
                type = 'success',
                title = 'Admin Access Granted',
                message = 'Full system access achieved!'
            })
        elseif data.accessLevel == 'root' then
            TriggerEvent('computer:grantRootAccess', data.terminalId)
            B2Lib.UI.alert({
                type = 'success',
                title = 'ROOT ACCESS',
                message = 'Complete system control achieved!'
            })
        end
        
        -- Award experience based on encryption level
        local expMultiplier = {
            basic = 1,
            advanced = 1.5,
            military = 2.0
        }
        local expGain = math.floor(150 * (expMultiplier[data.encryptionLevel] or 1))
        TriggerEvent('player:addExperience', 'hacking', expGain)
    else
        print(('Hacking failed! Sequences: %d/%d, Detected: %s'):format(
            data.sequencesCompleted, 
            data.totalSequences,
            data.detected and 'Yes' or 'No'
        ))
        
        -- Handle security detection
        if data.detected then
            TriggerEvent('security:alertTriggered', data.terminalId)
            B2Lib.UI.alert({
                type = 'error',
                title = 'SECURITY ALERT',
                message = 'Intrusion detected! Security has been notified!'
            })
            
            -- Start security response
            TriggerEvent('police:dispatch', 'hacking', GetEntityCoords(PlayerPedId()))
        end
    end
end)

b2lib:hacking-opened

Triggered when hacking minigame opens.

AddEventHandler('b2lib:hacking-opened', function(data)
    -- data.terminalId (string): Terminal identifier
    -- data.hackType (string): Type of hack
    -- data.encryptionLevel (string): Encryption level
    -- data.accessLevel (string): Target access level
    
    print(('Hacking initiated: %s terminal (%s encryption)'):format(
        data.hackType, 
        data.encryptionLevel
    ))
    
    -- Start hacking visual effects
    TriggerEvent('effects:startHacking')
end)

Server-Side Events

b2lib:server:minigame-completed

Triggered on server when any minigame completes for centralized logging.

RegisterNetEvent('b2lib:server:minigame-completed', function(minigameType, data)
    local playerId = source
    
    -- Log minigame completion
    exports.logging:logMinigameCompletion(playerId, minigameType, data)
    
    -- Update player statistics
    exports.b2lib:updatePlayerMinigameStats(playerId, minigameType, data)
    
    -- Handle specific minigame types
    if minigameType == 'skillcheck' and data.success then
        -- Award general experience for successful skillchecks
        exports.experience:addExperience(playerId, 'general', 10)
    elseif minigameType == 'lockpicking' and data.success then
        -- Award lockpicking experience
        exports.experience:addExperience(playerId, 'lockpicking', 25)
    elseif minigameType == 'hacking' and data.success then
        -- Award hacking experience
        exports.experience:addExperience(playerId, 'hacking', 50)
    end
end)

Configuration

The Skillcheck module provides extensive configuration options for customizing minigame behavior, difficulty scaling, visual appearance, and integration settings. Configuration is managed through multiple config sections for maximum flexibility and organization.

Core Minigame Configuration

Configure default minigame settings and behavior in Config.lua:

-- Skillcheck configuration
Config.Skillcheck = {
    position = 'center',                -- Default screen position
    showBackground = true,              -- Show background overlay
    enableSound = true,                 -- Enable audio feedback
    enableVibration = true,             -- Enable controller vibration
    defaultDifficulty = 'medium',       -- Default difficulty preset
    timeLimit = 30000,                  -- Default time limit (ms)
    maxAttempts = 3,                    -- Default maximum attempts
    randomizeTarget = false,            -- Randomize target position
    progressiveSpeed = false,           -- Increase speed on success
    showTimer = false,                  -- Display countdown timer
    showProgress = false,               -- Display progress indicator
    glowEffect = true,                  -- Enable glow effects
    
    -- Difficulty presets with comprehensive settings
    difficulties = {
        easy = {
            speed = 1.5,                -- Slow indicator movement
            targetSize = 60,            -- Large target zone (degrees)
            perfectSize = 20,           -- Medium perfect zone (degrees)
            attempts = 5,               -- More attempts allowed
            timeLimit = 45000,          -- Extended time limit
            pattern = 'continuous',     -- Simple movement pattern
            indicatorSize = 25,         -- Larger indicator
            randomizeTarget = false,    -- Fixed target position
            progressiveSpeed = false    -- No speed increase
        },
        medium = {
            speed = 2.0,                -- Medium indicator movement
            targetSize = 40,            -- Medium target zone
            perfectSize = 15,           -- Small perfect zone
            attempts = 3,               -- Standard attempts
            timeLimit = 30000,          -- Standard time limit
            pattern = 'continuous',     -- Standard movement
            indicatorSize = 20,         -- Standard indicator
            randomizeTarget = false,    -- Fixed target position
            progressiveSpeed = false    -- No speed increase
        },
        hard = {
            speed = 3.0,                -- Fast indicator movement
            targetSize = 25,            -- Small target zone
            perfectSize = 8,            -- Tiny perfect zone
            attempts = 2,               -- Fewer attempts
            timeLimit = 20000,          -- Reduced time limit
            pattern = 'accelerate',     -- Accelerating movement
            indicatorSize = 15,         -- Smaller indicator
            randomizeTarget = true,     -- Random target position
            progressiveSpeed = true     -- Speed increases on success
        },
        expert = {
            speed = 4.0,                -- Very fast movement
            targetSize = 15,            -- Very small target zone
            perfectSize = 5,            -- Minimal perfect zone
            attempts = 1,               -- Single attempt only
            timeLimit = 15000,          -- Short time limit
            pattern = 'random',         -- Random movement pattern
            indicatorSize = 10,         -- Minimal indicator
            randomizeTarget = true,     -- Random target position
            progressiveSpeed = true     -- Speed increases rapidly
        }
    },
    
    -- Audio configuration
    audio = {
        enabled = true,                 -- Enable audio system
        volume = 0.5,                   -- Master volume (0-1)
        profiles = {
            default = {
                tick = 'skillcheck_tick',
                success = 'skillcheck_success',
                failure = 'skillcheck_failure',
                perfect = 'skillcheck_perfect'
            },
            mechanical = {
                tick = 'mechanical_tick',
                success = 'mechanical_success',
                failure = 'mechanical_failure',
                perfect = 'mechanical_perfect'
            },
            electronic = {
                tick = 'electronic_beep',
                success = 'electronic_success',
                failure = 'electronic_failure',
                perfect = 'electronic_perfect'
            }
        }
    }
}

-- Lockpicking configuration
Config.Lockpicking = {
    position = 'center',                -- Default screen position
    enableSound = true,                 -- Enable audio feedback
    enableVibration = true,             -- Enable haptic feedback
    defaultDifficulty = 'medium',       -- Default difficulty preset
    timeLimit = 60000,                  -- Default time limit (ms)
    maxAttempts = 5,                    -- Default maximum attempts
    tensionSensitivity = 1.0,           -- Default tension sensitivity
    breakChance = 0.1,                  -- Default break probability
    sweetSpotSize = 15,                 -- Default sweet spot size
    rotationSpeed = 1.0,                -- Default rotation speed
    realismMode = false,                -- Enhanced realism by default
    progressiveBreaking = true,         -- Progressive lockpick wear
    
    -- Difficulty presets
    difficulties = {
        easy = {
            tensionSensitivity = 1.5,   -- High sensitivity
            breakChance = 0.05,         -- Low break chance
            sweetSpotSize = 25,         -- Large sweet spot
            timeLimit = 90000,          -- Extended time
            maxAttempts = 10,           -- Many attempts
            pins = 3                    -- Fewer pins
        },
        medium = {
            tensionSensitivity = 1.0,   -- Normal sensitivity
            breakChance = 0.1,          -- Normal break chance
            sweetSpotSize = 15,         -- Medium sweet spot
            timeLimit = 60000,          -- Standard time
            maxAttempts = 5,            -- Standard attempts
            pins = 4                    -- Standard pins
        },
        hard = {
            tensionSensitivity = 0.7,   -- Low sensitivity
            breakChance = 0.2,          -- High break chance
            sweetSpotSize = 8,          -- Small sweet spot
            timeLimit = 45000,          -- Reduced time
            maxAttempts = 3,            -- Fewer attempts
            pins = 5                    -- More pins
        },
        expert = {
            tensionSensitivity = 0.5,   -- Very low sensitivity
            breakChance = 0.3,          -- Very high break chance
            sweetSpotSize = 5,          -- Tiny sweet spot
            timeLimit = 30000,          -- Short time
            maxAttempts = 2,            -- Minimal attempts
            pins = 6                    -- Many pins
        }
    },
    
    -- Lock type configurations
    lockTypes = {
        standard = {
            name = 'Standard Lock',
            difficulty = 'medium',
            pins = 4,
            breakChance = 0.1
        },
        deadbolt = {
            name = 'Deadbolt Lock',
            difficulty = 'hard',
            pins = 5,
            breakChance = 0.15
        },
        electronic = {
            name = 'Electronic Lock',
            difficulty = 'expert',
            pins = 6,
            breakChance = 0.05,
            requiresSpecialTools = true
        },
        masterwork = {
            name = 'Masterwork Lock',
            difficulty = 'expert',
            pins = 8,
            breakChance = 0.25,
            requiresMasterTools = true
        }
    }
}

-- Hacking configuration
Config.Hacking = {
    position = 'center',                -- Default screen position
    enableSound = true,                 -- Enable audio feedback
    defaultDifficulty = 'medium',       -- Default difficulty preset
    timeLimit = 30000,                  -- Default time limit (ms)
    sequenceCount = 2,                  -- Default sequence count
    codeLength = 2,                     -- Default code length
    showTime = 2000,                    -- Default sequence display time
    inputTime = 5000,                   -- Default input time
    caseSensitive = false,              -- Case sensitivity by default
    allowPartialInput = true,           -- Allow partial sequences
    progressiveSpeed = false,           -- Speed progression
    randomizeSequences = true,          -- Randomize sequences
    visualEffects = true,               -- Terminal effects
    
    -- Difficulty presets
    difficulties = {
        easy = {
            sequenceCount = 2,          -- Few sequences
            codeLength = 2,             -- Short codes
            showTime = 3000,            -- Long display time
            inputTime = 8000,           -- Long input time
            timeLimit = 45000,          -- Extended time limit
            caseSensitive = false,      -- Not case sensitive
            allowPartialInput = true    -- Allow partial input
        },
        medium = {
            sequenceCount = 3,          -- Standard sequences
            codeLength = 2,             -- Standard codes
            showTime = 2000,            -- Standard display time
            inputTime = 5000,           -- Standard input time
            timeLimit = 30000,          -- Standard time limit
            caseSensitive = false,      -- Not case sensitive
            allowPartialInput = true    -- Allow partial input
        },
        hard = {
            sequenceCount = 4,          -- More sequences
            codeLength = 3,             -- Longer codes
            showTime = 1500,            -- Shorter display time
            inputTime = 4000,           -- Shorter input time
            timeLimit = 25000,          -- Reduced time limit
            caseSensitive = true,       -- Case sensitive
            allowPartialInput = false   -- No partial input
        },
        expert = {
            sequenceCount = 5,          -- Many sequences
            codeLength = 4,             -- Long codes
            showTime = 1000,            -- Very short display
            inputTime = 3000,           -- Very short input
            timeLimit = 20000,          -- Short time limit
            caseSensitive = true,       -- Case sensitive
            allowPartialInput = false   -- No partial input
        }
    },
    
    -- Hack type configurations
    hackTypes = {
        sequence = {
            name = 'Sequence Hack',
            description = 'Memorize and input code sequences',
            difficulty = 'medium'
        },
        matrix = {
            name = 'Matrix Hack',
            description = 'Navigate through code matrix',
            difficulty = 'hard'
        },
        cipher = {
            name = 'Cipher Hack',
            description = 'Decrypt encoded messages',
            difficulty = 'expert'
        },
        network = {
            name = 'Network Hack',
            description = 'Traverse network nodes',
            difficulty = 'expert'
        }
    }
}

-- Anti-cheat configuration
Config.AntiCheat = {
    enabled = true,                     -- Enable anti-cheat system
    logSuspiciousActivity = true,       -- Log suspicious behavior
    
    skillcheck = {
        enabled = true,
        maxDeviation = 50,              -- Max timing deviation (ms)
        minAttemptTime = 200,           -- Min time between attempts
        maxSuccessRate = 0.95,          -- Max success rate threshold
        validateTiming = true,          -- Validate attempt timing
        logAttempts = true              -- Log all attempts
    },
    
    lockpicking = {
        enabled = true,
        maxRotationSpeed = 180,         -- Max rotation speed (deg/s)
        minPickTime = 1000,             -- Min time to pick lock
        validateTension = true,         -- Validate tension mechanics
        maxSuccessRate = 0.90,          -- Max success rate threshold
        logAttempts = true              -- Log all attempts
    },
    
    hacking = {
        enabled = true,
        maxInputSpeed = 200,            -- Max input speed (chars/min)
        validateSequences = true,       -- Validate sequence accuracy
        checkTimingPatterns = true,     -- Check for inhuman timing
        requireServerValidation = true, -- Require server validation
        maxSuccessRate = 0.85,          -- Max success rate threshold
        logAttempts = true              -- Log all attempts
    }
}

Visual Styling Configuration

Customize the appearance and animations of minigame interfaces:

-- Skillcheck visual styling
Config.ComponentStyles.skillcheck = {
    size = '200px',                     -- Circle diameter
    strokeWidth = 8,                    -- Ring thickness
    centerSize = '48px',                -- Center button size
    textSize = '1rem',                  -- Text font size
    shadow = 'lg',                      -- Drop shadow intensity
    targetOpacity = 0.8,                -- Target zone opacity
    perfectOpacity = 0.9,               -- Perfect zone opacity
    indicatorOpacity = 1.0,             -- Indicator opacity
    showAttempts = true,                -- Show attempts counter
    showTimer = false,                  -- Show countdown timer
    showProgress = false,               -- Show progress indicator
    glowEffect = true,                  -- Enable glow effects
    animationDuration = 300,            -- Animation duration (ms)
    
    -- Color scheme
    colors = {
        background = 'rgba(0, 0, 0, 0.8)',
        ring = '#374151',
        target = '#10b981',
        perfect = '#fbbf24',
        indicator = '#3b82f6',
        text = '#ffffff',
        success = '#22c55e',
        failure = '#ef4444'
    },
    
    -- Animation presets
    animations = {
        fadeIn = { duration = 300, easing = 'ease-out' },
        fadeOut = { duration = 200, easing = 'ease-in' },
        success = { duration = 500, easing = 'ease-out' },
        failure = { duration = 300, easing = 'ease-in' }
    }
}

-- Lockpicking visual styling
Config.ComponentStyles.lockpicking = {
    width = '300px',                    -- Interface width
    height = '300px',                   -- Interface height
    padding = '20px',                   -- Internal padding
    borderRadius = '16px',              -- Corner rounding
    shadow = 'xl',                      -- Drop shadow
    lockSize = '192px',                 -- Lock circle size
    outerRingWidth = '4px',             -- Outer ring thickness
    innerRingWidth = '2px',             -- Inner ring thickness
    handleWidth = '4px',                -- Handle arm thickness
    centerSize = '24px',                -- Center hub size
    sweetSpotWidth = '3px',             -- Sweet spot indicator width
    feedbackBarWidth = '12px',          -- Feedback bar width
    timeBarWidth = '200px',             -- Time bar width
    tensionBarHeight = '8px',           -- Tension bar height
    
    -- Color scheme
    colors = {
        background = 'rgba(0, 0, 0, 0.9)',
        lock = '#6b7280',
        handle = '#374151',
        sweetSpot = '#10b981',
        tension = '#3b82f6',
        feedback = '#fbbf24',
        success = '#22c55e',
        failure = '#ef4444',
        text = '#ffffff'
    }
}

-- Hacking visual styling
Config.ComponentStyles.hacking = {
    width = '500px',                    -- Interface width
    height = '400px',                   -- Interface height
    padding = '24px',                   -- Internal padding
    borderRadius = 'lg',                -- Border radius
    shadow = 'xl',                      -- Shadow style
    fontFamily = 'monospace',           -- Terminal font
    fontSize = '14px',                  -- Base font size
    lineHeight = '1.5',                 -- Line height
    
    -- Color scheme (terminal style)
    colors = {
        background = 'rgba(0, 20, 0, 0.95)',
        text = '#00ff00',
        accent = '#00ffff',
        success = '#00ff00',
        failure = '#ff0000',
        warning = '#ffff00',
        border = '#00ff00'
    },
    
    -- Matrix-specific styling
    matrix = {
        cellSize = '32px',
        cellSpacing = '2px',
        highlightColor = '#00ffff',
        selectedColor = '#ffff00'
    },
    
    -- Network-specific styling
    network = {
        nodeSize = '40px',
        connectionWidth = '2px',
        nodeColor = '#00ff00',
        connectionColor = '#00ffff'
    }
}

Advanced Examples

Lockpicking System

-- Complete lockpicking system for doors
local function attemptLockpick(doorId)
    local doorData = GetDoorData(doorId)
    
    B2Lib.UI.lockpicking({
        name = doorData.name or 'Door Lock',
        difficulty = doorData.difficulty or 'medium',
        timeLimit = 45000,
        position = 'center'
    })
end

AddEventHandler('b2lib:lockpicking-result', function(data)
    if data.success then
        -- Unlock the door
        TriggerServerEvent('door:unlock', currentDoorId)
        
        B2Lib.UI.notify({
            type = 'success',
            title = 'Success',
            message = string.format('Door unlocked! (%.1fs, %d attempts)', data.timeElapsed / 1000, data.attempts)
        })
    else
        local reason = data.reason == 'timeout' and 'Time ran out' or 
                      data.reason == 'cancelled' and 'Cancelled' or 'Failed'
        
        B2Lib.UI.notify({
            type = 'error',
            title = 'Lockpicking Failed',
            message = string.format('%s after %d attempts', reason, data.attempts)
        })
    end
end)

Hacking System

-- Computer hacking system
local function hackComputer(computerId)
    local computerData = GetComputerData(computerId)
    
    B2Lib.UI.hacking({
        name = computerData.name or 'Security Terminal',
        difficulty = computerData.securityLevel or 'medium',
        timeLimit = 30000,
        sequences = computerData.sequences or {
            {
                codes = {'A7', 'F2', 'B9', 'E1'},
                showTime = 2000
            },
            {
                codes = {'C3', 'D8', '5F', '9A'},
                showTime = 2000
            }
        }
    })
end

AddEventHandler('b2lib:hacking-result', function(data)
    if data.success then
        -- Grant access to computer
        TriggerServerEvent('computer:access', currentComputerId, data.accessLevel)
        
        B2Lib.UI.alert({
            type = 'success',
            title = 'Access Granted',
            message = 'You have successfully hacked the terminal.'
        })
    else
        if data.detected then
            -- Security alert triggered
            TriggerServerEvent('security:alert', currentComputerId)
            
            B2Lib.UI.alert({
                type = 'error',
                title = 'Security Alert',
                message = 'Intrusion detected! Security has been alerted.'
            })
        else
            B2Lib.UI.notify({
                type = 'warning',
                title = 'Access Denied',
                message = 'Hacking attempt failed.'
            })
        end
    end
end)

Crafting System with Skillchecks

-- Crafting system using skillchecks
local function craftItem(recipe)
    local skillchecksRequired = recipe.skillchecks or 3
    local currentSkillcheck = 1
    local craftingSuccess = true
    
    local function performNextSkillcheck()
        if currentSkillcheck <= skillchecksRequired then
            B2Lib.UI.skillcheck({
                difficulty = recipe.difficulty or 'medium',
                position = 'center',
                perfectSize = recipe.precision or 15,
                maxAttempts = recipe.attempts or 3
            })
        else
            -- All skillchecks completed
            if craftingSuccess then
                TriggerServerEvent('crafting:complete', recipe.id)
                
                B2Lib.UI.notify({
                    type = 'success',
                    title = 'Crafting Complete',
                    message = string.format('Successfully crafted %s!', recipe.name)
                })
            end
        end
    end
    
    AddEventHandler('b2lib:skillcheck-result', function(data)
        if data.success then
            currentSkillcheck = currentSkillcheck + 1
            
            B2Lib.UI.notify({
                type = 'success',
                title = 'Skillcheck Passed',
                message = string.format('Progress: %d/%d (Perfect: %s)', 
                    currentSkillcheck - 1, skillchecksRequired, 
                    data.perfect and 'Yes' or 'No')
            })
            
            Citizen.Wait(1000)
            performNextSkillcheck()
        else
            craftingSuccess = false
            
            B2Lib.UI.notify({
                type = 'error',
                title = 'Crafting Failed',
                message = string.format('Skillcheck failed after %d attempts', data.attempts)
            })
        end
    end)
    
    -- Start first skillcheck
    performNextSkillcheck()
end

Best Practices

Follow these comprehensive guidelines to ensure optimal minigame implementation and user experience:

1. Context-Appropriate Minigame Selection

Choose minigame types that match the narrative and mechanical context of the action being performed.

-- Good: Use lockpicking for doors and containers
B2Lib.UI.lockpicking({ name = 'Safe Lock', difficulty = 'hard' })

-- Good: Use hacking for computer terminals
B2Lib.UI.hacking({ name = 'Security Terminal', hackType = 'sequence' })

-- Good: Use skillchecks for general skill-based actions
B2Lib.UI.skillcheck({ difficulty = 'medium' }) -- For crafting, repairs, etc.

2. Balanced Difficulty Progression

Implement progressive difficulty that scales with player skill and context importance.

-- Scale difficulty based on player level or item value
local playerLevel = getPlayerSkillLevel('lockpicking')
local difficulty = playerLevel < 25 and 'easy' or 
                  playerLevel < 50 and 'medium' or 
                  playerLevel < 75 and 'hard' or 'expert'

B2Lib.UI.lockpicking({ difficulty = difficulty })

3. Comprehensive Feedback Systems

Provide immediate, clear, and meaningful feedback for all minigame outcomes.

AddEventHandler('b2lib:skillcheck-result', function(data)
    if data.success then
        -- Positive feedback with context
        B2Lib.UI.notify({
            type = 'success',
            title = data.perfect and 'Perfect!' or 'Success!',
            message = data.perfect and 'Bonus experience gained!' or 'Task completed successfully!'
        })
        
        -- Award appropriate rewards
        if data.perfect then
            TriggerEvent('player:addExperience', 'precision', 50)
        end
    else
        -- Constructive failure feedback
        B2Lib.UI.notify({
            type = 'error',
            title = 'Failed',
            message = string.format('Try again! (%d/%d attempts used)', data.attempts, data.maxAttempts)
        })
    end
end)

4. Graceful Failure Handling

Design failure consequences that encourage learning rather than punish experimentation.

-- Implement progressive consequences
local function handleLockpickingFailure(data)
    if data.reason == 'broken' then
        -- Consume item but provide learning opportunity
        TriggerServerEvent('inventory:removeItem', 'lockpick', 1)
        B2Lib.UI.alert({
            type = 'info',
            title = 'Lockpick Broken',
            message = 'Practice makes perfect! Try applying less tension next time.'
        })
    elseif data.reason == 'timeout' then
        -- Encourage patience and observation
        B2Lib.UI.notify({
            type = 'warning',
            title = 'Time\'s Up',
            message = 'Take your time to feel for the sweet spot.'
        })
    end
end

5. Enhanced Audio-Visual Experience

Leverage sound effects and visual feedback to create immersive experiences.

-- Use appropriate audio profiles for different contexts
B2Lib.UI.skillcheck({
    soundProfile = 'mechanical', -- For vehicle repairs
    glowEffect = true,
    visualEffects = true
})

B2Lib.UI.hacking({
    soundProfile = 'electronic', -- For computer systems
    visualEffects = true,
    audioFeedback = true
})

6. Accessibility and Inclusivity

Provide options for players with different abilities and preferences.

-- Accessibility-focused configuration
B2Lib.UI.skillcheck({
    size = '300px',              -- Larger interface
    strokeWidth = 12,            -- Thicker visual elements
    targetSize = 60,             -- Larger target zones
    glowEffect = true,           -- Enhanced visibility
    alternativeKeys = { 'Enter', 'F', 'E' }, -- Multiple key options
    colorScheme = {
        target = '#22c55e',      -- High contrast colors
        perfect = '#eab308',
        indicator = '#3b82f6'
    }
})

7. Performance Optimization

Implement efficient resource management and cleanup procedures.

-- Clean up event handlers when no longer needed
local skillcheckHandler = AddEventHandler('b2lib:skillcheck-result', function(data)
    -- Handle result
    handleSkillcheckResult(data)
    
    -- Clean up if this was a one-time use
    if data.skillcheckId:match('oneshot_') then
        RemoveEventHandler(skillcheckHandler)
    end
end)

-- Use reasonable time limits to prevent resource waste
B2Lib.UI.skillcheck({
    timeLimit = 30000, -- 30 seconds maximum
    maxAttempts = 3    -- Reasonable attempt limit
})

8. Server-Side Validation and Security

Always validate minigame results server-side to prevent cheating.

-- Server-side validation
RegisterNetEvent('b2lib:server:skillcheck-result', function(data)
    local playerId = source
    
    -- Validate result authenticity
    if not exports.b2lib:validateMinigameResult(playerId, data) then
        -- Handle potential cheating
        exports.logging:logSuspiciousActivity(playerId, 'skillcheck_cheat', data)
        return
    end
    
    -- Process legitimate result
    if data.success then
        -- Grant rewards securely
        exports.inventory:addItem(playerId, data.rewardItem, data.rewardAmount)
    end
end)

9. Integration with Game Systems

Design minigames to integrate seamlessly with existing game mechanics.

-- Integration with crafting system
local function startCraftingSkillcheck(recipe)
    local playerSkill = getPlayerCraftingSkill()
    local difficulty = recipe.difficulty
    
    -- Adjust difficulty based on player skill
    if playerSkill >= recipe.recommendedSkill then
        difficulty = 'easy'
    elseif playerSkill >= recipe.minimumSkill then
        difficulty = 'medium'
    else
        difficulty = 'hard'
    end
    
    B2Lib.UI.skillcheck({
        id = 'crafting_' .. recipe.id,
        difficulty = difficulty,
        maxAttempts = math.floor(playerSkill / 20) + 1
    })
end

10. User Experience Consistency

Maintain consistent behavior and visual design across all minigame implementations.

-- Standardized minigame wrapper
function ShowContextualMinigame(context, options)
    local defaults = {
        position = 'center',
        showTimer = context.timeLimit and context.timeLimit < 60000,
        glowEffect = true,
        vibration = true
    }
    
    local config = table.merge(defaults, options)
    
    if context.type == 'repair' then
        B2Lib.UI.skillcheck(config)
    elseif context.type == 'hack' then
        B2Lib.UI.hacking(config)
    elseif context.type == 'lockpick' then
        B2Lib.UI.lockpicking(config)
    end
end

Troubleshooting

Comprehensive troubleshooting guide for common minigame issues and their solutions:

Minigame Interface Issues

Minigame Not Displaying

Symptoms: Minigame function called but no interface appears on screen.

Diagnostic Steps:

  1. Check B2Lib Initialization:

    -- Verify B2Lib is properly loaded
    if not B2Lib or not B2Lib.UI then
        print('B2Lib not properly initialized')
        return
    end
  2. Verify No Conflicting UI:

    -- Check for other active modal dialogs
    if B2Lib.UI.isModalActive() then
        print('Another modal dialog is active')
        B2Lib.UI.closeAllModals()
    end
  3. Console Error Checking:

    -- Enable debug mode for detailed logging
    B2Lib.Debug.enabled = true
    B2Lib.Debug.level = 'verbose'
  4. Position Validation:

    -- Ensure valid position parameter
    local validPositions = {'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right'}
    if not table.contains(validPositions, options.position) then
        options.position = 'center'
    end

Visual Rendering Problems

Symptoms: Minigame appears but with visual glitches or incorrect styling.

Solutions:

  1. Reset Component Styles:

    -- Reset to default styling
    B2Lib.UI.resetComponentStyles('skillcheck')
  2. Check Theme Compatibility:

    -- Verify current theme supports minigames
    local currentTheme = B2Lib.UI.getTheme()
    if not currentTheme.supportsMinigames then
        B2Lib.UI.setTheme('dark') -- Fallback to compatible theme
    end
  3. Clear Style Cache:

    -- Clear cached styles
    TriggerEvent('b2lib:clearStyleCache')

Input and Control Issues

Controls Not Responding

Symptoms: Minigame displays but key presses are not registered.

Diagnostic Steps:

  1. NUI Focus Verification:

    -- Check NUI focus state
    if not HasNuiFocus() then
        print('NUI focus not properly set')
        SetNuiFocus(true, false) -- Enable keyboard input only
    end
  2. Key Binding Conflicts:

    -- Check for conflicting key bindings
    local conflictingKeys = GetConflictingKeyBinds('Space')
    if #conflictingKeys > 0 then
        print('Key binding conflicts detected:', json.encode(conflictingKeys))
    end
  3. Resource Interference:

    -- Temporarily disable other input-heavy resources
    StopResource('other_ui_resource')
    -- Test minigame
    StartResource('other_ui_resource')

Inconsistent Input Timing

Symptoms: Input registration varies or feels delayed.

Solutions:

  1. Frame Rate Optimization:

    -- Optimize frame rate during minigames
    SetTimecycleModifier('minigame_performance')
  2. Input Debouncing:

    -- Implement input debouncing
    local lastInputTime = 0
    local inputCooldown = 100 -- ms
    
    function HandleMinigameInput(key)
        local currentTime = GetGameTimer()
        if currentTime - lastInputTime < inputCooldown then
            return false
        end
        lastInputTime = currentTime
        return true
    end

Event System Issues

Events Not Triggering

Symptoms: Minigame completes but result events are not fired.

Diagnostic Steps:

  1. Event Handler Registration:

    -- Verify event handlers are registered before minigame starts
    local handlerRegistered = false
    
    AddEventHandler('b2lib:skillcheck-result', function(data)
        handlerRegistered = true
        -- Handle result
    end)
    
    -- Check registration
    Citizen.Wait(100)
    if not handlerRegistered then
        print('Event handler not properly registered')
    end
  2. Event Name Verification:

    -- Use exact event names
    local validEvents = {
        'b2lib:skillcheck-result',
        'b2lib:lockpicking-result',
        'b2lib:hacking-result'
    }
  3. Handler Function Errors:

    -- Wrap event handlers in error handling
    AddEventHandler('b2lib:skillcheck-result', function(data)
        local success, error = pcall(function()
            -- Your event handling code
            handleSkillcheckResult(data)
        end)
        
        if not success then
            print('Event handler error:', error)
        end
    end)

Duplicate Event Firing

Symptoms: Result events fire multiple times for single minigame completion.

Solutions:

  1. Event Handler Cleanup:

    -- Store handler references for cleanup
    local eventHandlers = {}
    
    eventHandlers.skillcheck = AddEventHandler('b2lib:skillcheck-result', function(data)
        -- Handle once and cleanup
        handleSkillcheckResult(data)
        RemoveEventHandler(eventHandlers.skillcheck)
        eventHandlers.skillcheck = nil
    end)
  2. Result ID Tracking:

    -- Track processed results
    local processedResults = {}
    
    AddEventHandler('b2lib:skillcheck-result', function(data)
        if processedResults[data.skillcheckId] then
            return -- Already processed
        end
        
        processedResults[data.skillcheckId] = true
        handleSkillcheckResult(data)
    end)

Performance and Resource Issues

Frame Rate Drops During Minigames

Symptoms: Significant FPS reduction when minigames are active.

Solutions:

  1. Resource Monitoring:

    -- Monitor resource usage
    local function monitorPerformance()
        local memBefore = collectgarbage('count')
        -- Start minigame
        B2Lib.UI.skillcheck(options)
        
        Citizen.Wait(1000)
        local memAfter = collectgarbage('count')
        print('Memory usage increase:', memAfter - memBefore, 'KB')
    end
  2. Optimization Settings:

    -- Use performance-optimized settings
    B2Lib.UI.skillcheck({
        glowEffect = false,        -- Disable expensive effects
        animationDuration = 100,   -- Reduce animation time
        visualEffects = false      -- Disable visual effects
    })

Memory Leaks

Symptoms: Increasing memory usage over time with repeated minigame use.

Solutions:

  1. Explicit Cleanup:

    -- Force cleanup after minigame completion
    AddEventHandler('b2lib:skillcheck-closed', function(data)
        collectgarbage('collect')
        TriggerEvent('b2lib:cleanup-resources')
    end)
  2. Resource Limits:

    -- Implement usage limits
    local minigameCount = 0
    local maxMinigames = 50
    
    function showSkillcheck(options)
        if minigameCount >= maxMinigames then
            -- Force resource restart
            RestartResource(GetCurrentResourceName())
            return
        end
        
        minigameCount = minigameCount + 1
        B2Lib.UI.skillcheck(options)
    end

Anti-Cheat and Validation Issues

False Positive Cheat Detection

Symptoms: Legitimate players flagged for cheating.

Solutions:

  1. Adjust Validation Thresholds:

    -- Increase tolerance for timing validation
    Config.AntiCheat.skillcheck.maxDeviation = 100 -- Increase from 50ms
    Config.AntiCheat.skillcheck.maxSuccessRate = 0.98 -- Increase from 0.95
  2. Player Skill Consideration:

    -- Adjust thresholds based on player skill
    function getValidationThresholds(playerId)
        local playerSkill = getPlayerSkillLevel(playerId)
        return {
            maxDeviation = 50 + (playerSkill * 2),
            maxSuccessRate = 0.85 + (playerSkill * 0.002)
        }
    end

Validation Not Working

Symptoms: Cheaters not being detected or blocked.

Solutions:

  1. Enable Comprehensive Logging:

    -- Enable detailed anti-cheat logging
    Config.AntiCheat.logSuspiciousActivity = true
    Config.AntiCheat.skillcheck.logAttempts = true
  2. Server-Side Enforcement:

    -- Ensure server-side validation is active
    RegisterNetEvent('b2lib:server:skillcheck-result', function(data)
        local playerId = source
        
        if not exports.b2lib:validateMinigameResult(playerId, data) then
            -- Take action against cheater
            DropPlayer(playerId, 'Minigame validation failed')
            return
        end
        
        -- Process legitimate result
    end)

Last updated