# Redis Client Usage Examples

Practical examples of using the **raw Redis client module** as a foundation for higher-level game modules. These examples show low-level Redis operations that would typically be wrapped by game-specific modules (actor, garage, locker, bank).

> **Note**: These examples show raw Redis responses. In practice, your game modules would wrap these calls and return structured JSON to SQF scripts.

## 🚀 Function Behavior

All Redis functions are **synchronous from SQF's perspective**:
- Functions **block** until Redis operation completes
- **No callbacks** or async handling needed in SQF
- **Direct return values** – either data or error strings
- **Thread-safe** – multiple scripts can call simultaneously

The extension handles all async complexity internally using a macro-based architecture.

## 🎮 Player Management

### Player Join/Leave Tracking
```sqf
// When player joins
_playerUID = getPlayerUID player;
_playerName = name player;

// Store player info in hash
"forge_server" callExtension ["redis:hash:set", [format ["player:%1", _playerUID], "name", _playerName]];
"forge_server" callExtension ["redis:hash:set", [format ["player:%1", _playerUID], "join_time", str time]];

// Add to online players set
"forge_server" callExtension ["redis:set:add", ["online_players", _playerUID]];

// When player leaves
"forge_server" callExtension ["redis:set:del", ["online_players", _playerUID]];
"forge_server" callExtension ["redis:hash:set", [format ["player:%1", _playerUID], "leave_time", str time]];
```

### Player Statistics System
```sqf
// Initialize player stats
fnc_initPlayerStats = {
    params ["_playerUID"];
    
    _playerKey = format ["stats:%1", _playerUID];
    "forge_server" callExtension ["redis:hash:mset", [_playerKey, [
        ["kills", "0"],
        ["deaths", "0"], 
        ["score", "0"],
        ["playtime", "0"]
    ]]];
};

// Update player kill
fnc_addPlayerKill = {
    params ["_playerUID"];
    
    _playerKey = format ["stats:%1", _playerUID];
    "forge_server" callExtension ["redis:hash:incr", [_playerKey, "kills", 1]];
    "forge_server" callExtension ["redis:hash:incr", [_playerKey, "score", 10]];
};

// Get player stats (raw response)
fnc_getPlayerStats = {
    params ["_playerUID"];
    
    _playerKey = format ["stats:%1", _playerUID];
    _rawResult = "forge_server" callExtension ["redis:hash:getall", [_playerKey]];
    // _rawResult is now "kills,15,deaths,3,score,150,playtime,7200"
    
    // Game modules would parse this into structured data
    // For now, return raw comma-separated response
    _rawResult select 0;
};
```

## 🏆 Leaderboards and Rankings

### Global Kill Leaderboard
```sqf
// Add score to sorted leaderboard (using list for simplicity)
fnc_updateLeaderboard = {
    params ["_playerName", "_kills"];
    
    // Store individual score
    "forge_server" callExtension ["redis:common:set", [format ["kills:%1", _playerName], str _kills]];
    
    // Add to leaderboard tracking
    "forge_server" callExtension ["redis:set:add", ["leaderboard_players", _playerName]];
};

// Get top 10 players (raw response handling)
fnc_getTopPlayers = {
    // Get all leaderboard players - returns comma-separated list
    _playersResult = "forge_server" callExtension ["redis:set:members", ["leaderboard_players"]];
    _rawPlayers = _playersResult select 0;
    
    // Check for error
    if (_rawPlayers find "Error:" == 0) exitWith { [] };
    
    // Split comma-separated player list
    _players = _rawPlayers splitString ",";
    _scoreArray = [];
    
    // Get scores for all players
    {
        _killsResult = "forge_server" callExtension ["redis:common:get", [format ["kills:%1", _x]]];
        _rawKills = _killsResult select 0;
        
        // Check for valid response (not an error)
        if (_rawKills find "Error:" != 0) then {
            _scoreArray pushBack [_x, parseNumber _rawKills];
        };
    } forEach _players;
    
    // Sort by score (highest first)
    _scoreArray sort false;
    _scoreArray resize (10 min (count _scoreArray)); // Top 10
    
    _scoreArray;
};
```

## 🎯 Mission State Management

### Objective System
```sqf
// Set mission objectives
fnc_initMissionObjectives = {
    "forge_server" callExtension ["redis:list:rpush", ["objectives", "Secure Alpha Base"]];
    "forge_server" callExtension ["redis:list:rpush", ["objectives", "Extract Intel"]];
    "forge_server" callExtension ["redis:list:rpush", ["objectives", "Eliminate HVT"]];
    
    // Set current objective pointer
    "forge_server" callExtension ["redis:common:set", ["current_objective", "0"]];
};

// Complete current objective
fnc_completeObjective = {
    // Get current objective index - returns raw string
    _indexResult = "forge_server" callExtension ["redis:common:get", ["current_objective"]];
    _rawIndex = _indexResult select 0;
    
    // Check for error
    if (_rawIndex find "Error:" == 0) exitWith {};
    
    _currentIndex = parseNumber _rawIndex;
    
    // Get objective name - returns raw string
    _objResult = "forge_server" callExtension ["redis:list:get", ["objectives", _currentIndex]];
    _objectiveName = _objResult select 0;
    
    // Check for valid response
    if (_objectiveName find "Error:" != 0) then {
        // Move to completed objectives - returns new list length
        "forge_server" callExtension ["redis:list:rpush", ["completed_objectives", _objectiveName]];
        
        // Move to next objective - returns "OK"
        "forge_server" callExtension ["redis:common:set", ["current_objective", str (_currentIndex + 1)]];
        
        // Broadcast completion
        [format ["Objective Complete: %1", _objectiveName]] remoteExec ["hint"];
    };
};

// Get mission progress - raw responses
fnc_getMissionProgress = {
    _totalResult = "forge_server" callExtension ["redis:list:len", ["objectives"]];
    _completedResult = "forge_server" callExtension ["redis:list:len", ["completed_objectives"]];
    
    _rawTotal = _totalResult select 0;
    _rawCompleted = _completedResult select 0;
    
    // Check for errors
    if (_rawTotal find "Error:" == 0 || _rawCompleted find "Error:" == 0) exitWith {
        "Mission Progress: Unknown";
    };
    
    _total = parseNumber _rawTotal;
    _completed = parseNumber _rawCompleted;
    
    format ["Mission Progress: %1/%2 objectives completed", _completed, _total];
};
```

## 🚁 Vehicle and Equipment Tracking

### Vehicle Pool System
```sqf
// Initialize vehicle pool
fnc_initVehiclePool = {
    params ["_vehicleClass", "_count"];
    
    for "_i" from 1 to _count do {
        _vehicleId = format ["%1_%2", _vehicleClass, _i];
        "forge_server" callExtension ["redis:set:add", ["available_vehicles", _vehicleId]];
        "forge_server" callExtension ["redis:hash:mset", [format ["vehicle:%1", _vehicleId], [
            ["class", _vehicleClass],
            ["status", "available"],
            ["condition", "100"]
        ]]];
    };
};

// Request vehicle
fnc_requestVehicle = {
    params ["_playerUID"];
    
    // Get random available vehicle
    _result = "forge_server" callExtension ["redis:set:pop", ["available_vehicles"]];
    _data = fromJSON (_result select 0);
    
    if ((_data select "status") == "success") then {
        _vehicleId = _data select "data";
        
        // Mark as in use
        "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "status", "in_use"]];
        "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "user", _playerUID]];
        "forge_server" callExtension ["redis:set:add", ["used_vehicles", _vehicleId]];
        
        _vehicleId;
    } else {
        ""; // No vehicles available
    };
};

// Return vehicle
fnc_returnVehicle = {
    params ["_vehicleId", "_condition"];
    
    // Update condition
    "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "condition", str _condition]];
    
    // Return to pool if condition is good
    if (_condition > 50) then {
        "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "status", "available"]];
        "forge_server" callExtension ["redis:hash:del", [format ["vehicle:%1", _vehicleId], "user"]];
        "forge_server" callExtension ["redis:set:del", ["used_vehicles", _vehicleId]];
        "forge_server" callExtension ["redis:set:add", ["available_vehicles", _vehicleId]];
    } else {
        "forge_server" callExtension ["redis:hash:set", [format ["vehicle:%1", _vehicleId], "status", "maintenance"]];
        "forge_server" callExtension ["redis:set:add", ["maintenance_vehicles", _vehicleId]];
    };
};
```

## 📊 Server Analytics

### Player Session Tracking
```sqf
// Track player session start
fnc_startPlayerSession = {
    params ["_playerUID"];
    
    _sessionId = format ["%1_%2", _playerUID, floor time];
    _sessionKey = format ["session:%1", _sessionId];
    
    "forge_server" callExtension ["redis:hash:mset", [_sessionKey, [
        ["player_uid", _playerUID],
        ["start_time", str time],
        ["server_id", serverName],
        ["player_count", str (count allPlayers)]
    ]]];
    
    // Store current session for player
    "forge_server" callExtension ["redis:common:set", [format ["current_session:%1", _playerUID], _sessionId]];
    
    _sessionId;
};

// End player session
fnc_endPlayerSession = {
    params ["_playerUID", "_sessionStats"];
    
    // Get current session
    _result = "forge_server" callExtension ["redis:common:get", [format ["current_session:%1", _playerUID]]];
    _data = fromJSON (_result select 0);
    
    if ((_data select "status") == "success") then {
        _sessionId = _data select "data";
        _sessionKey = format ["session:%1", _sessionId];
        
        // Update session with end data
        "forge_server" callExtension ["redis:hash:mset", [_sessionKey, [
            ["end_time", str time],
            ["duration", str (_sessionStats select "duration")],
            ["kills", str (_sessionStats select "kills")],
            ["deaths", str (_sessionStats select "deaths")]
        ]]];
        
        // Clean up current session tracking
        "forge_server" callExtension ["redis:common:del", [format ["current_session:%1", _playerUID]]];
    };
};
```

## 🔄 Cross-Server Communication

### Message Queue System
```sqf
// Send message to other servers
fnc_sendCrossServerMessage = {
    params ["_targetServer", "_messageType", "_messageData"];
    
    _message = createHashMap;
    _message set ["from_server", serverName];
    _message set ["type", _messageType];
    _message set ["data", _messageData];
    _message set ["timestamp", str time];
    
    _queueKey = format ["messages:%1", _targetServer];
    "forge_server" callExtension ["redis:list:rpush", [_queueKey, str _message]];
};

// Check for incoming messages
fnc_checkMessages = {
    _queueKey = format ["messages:%1", serverName];
    
    // Get next message
    _result = "forge_server" callExtension ["redis:list:lpop", [_queueKey, 1]];
    _data = fromJSON (_result select 0);
    
    if ((_data select "status") == "success") then {
        _messages = _data select "data";
        if (count _messages > 0) then {
            _messageStr = _messages select 0;
            _message = fromJSON _messageStr;
            
            // Process message based on type
            _type = _message select "type";
            _messageData = _message select "data";
            
            switch (_type) do {
                case "player_transfer": {
                    [_messageData] call fnc_handlePlayerTransfer;
                };
                case "server_status": {
                    [_messageData] call fnc_handleServerStatus;
                };
                case "admin_broadcast": {
                    [_messageData select "message"] remoteExec ["hint"];
                };
            };
        };
    };
};

// Run message checker periodically
[] spawn {
    while {true} do {
        call fnc_checkMessages;
        sleep 5; // Check every 5 seconds
    };
};
```

## 🛠️ Utility Functions

### Redis Helper Functions
```sqf
// Parse Redis response safely
fnc_parseRedisResponse = {
    params ["_response"];
    
    try {
        _data = fromJSON (_response select 0);
        if ((_data select "status") == "success") then {
            _data select "data";
        } else {
            diag_log format ["Redis Error: %1", _data select "error"];
            nil;
        };
    } catch {
        diag_log format ["JSON Parse Error: %1", _exception];
        nil;
    };
};

// Batch Redis operations
fnc_redisBatch = {
    params ["_operations"];
    
    _results = [];
    {
        _op = _x;
        _result = "forge_server" callExtension [_op select 0, _op select 1];
        _results pushBack (fromJSON (_result select 0));
    } forEach _operations;
    
    _results;
};

// Example batch usage:
_batchOps = [
    ["redis:common:set", ["key1", "value1"]],
    ["redis:common:set", ["key2", "value2"]],
    ["redis:common:get", ["key1"]]
];
_results = [_batchOps] call fnc_redisBatch;
```

## 🎯 Best Practices

### Error Handling Pattern
```sqf
fnc_safeRedisCall = {
    params ["_command", "_params", ["_defaultValue", nil]];
    
    try {
        _result = "forge_server" callExtension [_command, _params];
        _data = fromJSON (_result select 0);
        
        if ((_data select "status") == "success") then {
            _data select "data";
        } else {
            diag_log format ["Redis operation failed: %1 - %2", _command, _data select "error"];
            _defaultValue;
        };
    } catch {
        diag_log format ["Redis call exception: %1 - %2", _command, _exception];
        _defaultValue;
    };
};

// Usage:
_playerName = ["redis:common:get", ["player_name"], "Unknown"] call fnc_safeRedisCall;
```

These examples demonstrate real-world usage patterns for the Redis extension in Arma 3 environments, covering player management, mission state, analytics, and cross-server communication.