- 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
14 KiB
14 KiB
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.