forge/arma/server/docs/usage-examples.md
Jacob Schmidt d178e39164 Refactor client UI stores and normalize docs formatting
- Rework org and store UI state modules (rename/move store/getter files, add runtime and bridge wiring)
- Update store UI components and page structure (navbar/cart split, new StoreView flow)
- Apply broad markdown/YAML/HTML/CSS/JS formatting cleanup across docs, templates, and workflows
2026-03-10 19:13:30 -05:00

14 KiB
Raw Blame History

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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

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.