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.




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 objectplayerId
(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 accessibilitytimeLimit
(number, optional): Overall time limit in milliseconds (default: 30000)zones
(table, optional): Multiple target zones for advanced skillchecksrandomizeTarget
(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 integrationanimations
(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 configurationplayerId
(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 objectplayerId
(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 lockslockType
(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 arraylockpickQuality
(string, optional): Lockpick quality affecting durability ('poor', 'standard', 'professional', 'masterwork') (default: 'standard')environmentalFactors
(table, optional): Environmental effects on difficultyskillModifiers
(table, optional): Player skill-based modificationsantiCheat
(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 objectplayerId
(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 hackinghackType
(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 configurationsmatrixSize
(table, optional): Matrix dimensions for matrix-type hackingnetworkNodes
(table, optional): Network node configuration for network-type hackingencryptionLevel
(string, optional): Encryption complexity ('basic', 'advanced', 'military') (default: 'basic')antiCheat
(table, optional): Server-side validation and anti-cheat measuresskillModifiers
(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 configurationplayerId
(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:
Check B2Lib Initialization:
-- Verify B2Lib is properly loaded if not B2Lib or not B2Lib.UI then print('B2Lib not properly initialized') return end
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
Console Error Checking:
-- Enable debug mode for detailed logging B2Lib.Debug.enabled = true B2Lib.Debug.level = 'verbose'
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:
Reset Component Styles:
-- Reset to default styling B2Lib.UI.resetComponentStyles('skillcheck')
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
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:
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
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
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:
Frame Rate Optimization:
-- Optimize frame rate during minigames SetTimecycleModifier('minigame_performance')
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:
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
Event Name Verification:
-- Use exact event names local validEvents = { 'b2lib:skillcheck-result', 'b2lib:lockpicking-result', 'b2lib:hacking-result' }
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:
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)
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:
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
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:
Explicit Cleanup:
-- Force cleanup after minigame completion AddEventHandler('b2lib:skillcheck-closed', function(data) collectgarbage('collect') TriggerEvent('b2lib:cleanup-resources') end)
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:
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
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:
Enable Comprehensive Logging:
-- Enable detailed anti-cheat logging Config.AntiCheat.logSuspiciousActivity = true Config.AntiCheat.skillcheck.logAttempts = true
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