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

438 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.