feat: Update build environment and add XEH PREP

This commit includes the following changes:

-   Updates the build environment in the GitHub Actions workflow to use `ubuntu-latest` instead of `ubuntu-22.04`.
-   Adds `playerGroup2Server` to the XEH_PREP.hpp file.
-   Updates the picture path in CfgMods.hpp to include the file extension.
This commit is contained in:
Jacob Schmidt 2025-03-28 09:46:08 -05:00
parent fbbfc031dc
commit bec0adcdbf
28 changed files with 727 additions and 2 deletions

View File

@ -16,7 +16,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4

1
addons/db/$PBOPREFIX$ Normal file
View File

@ -0,0 +1 @@
z\forge_server\addons\db

View File

@ -0,0 +1,19 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preStart));
};
};
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preInit));
serverInit = QUOTE(call COMPILE_FILE(XEH_preInit_server));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_postInit));
clientInit = QUOTE(call COMPILE_FILE(XEH_postInit_client));
};
};

13
addons/db/XEH_PREP.hpp Normal file
View File

@ -0,0 +1,13 @@
PREP(createStore);
PREP(getFromStore);
PREP(getStore);
PREP(initDB);
PREP(loadFromMission);
PREP(loadFromProfile);
PREP(loadGameState);
PREP(saveGameState);
PREP(saveToMission);
PREP(saveToProfile);
PREP(saveToStore);
PREP(saveToTemp);
PREP(verifyDB);

View File

@ -0,0 +1,3 @@
#include "script_component.hpp"
call FUNC(initDB);

View File

@ -0,0 +1 @@
#include "script_component.hpp"

33
addons/db/XEH_preInit.sqf Normal file
View File

@ -0,0 +1,33 @@
#include "script_component.hpp"
ADDON = false;
PREP_RECOMPILE_START;
#include "XEH_PREP.hpp"
PREP_RECOMPILE_END;
GVAR(playerOwners) = createHashMap;
GVAR(tempStores) = createHashMap;
// Process database requests
["forge_db_request", {
params ["_playerUID", "_requestType", "_data", "_requestID"];
TRACE_4("DB Request",_playerUID,_requestType,_data,_requestID);
private _result = [_requestType, _data] call FUNC(processDBRequest);
["forge_db_response", [_playerUID, _requestType, _result, _requestID]] call CBA_fnc_globalEvent;
}] call CBA_fnc_addEventHandler;
// Register clients for direct messaging
["forge_db_registerClient", {
params ["_playerUID", "_ownerID"];
GVAR(playerOwners) set [_playerUID, _ownerID];
}] call CBA_fnc_addEventHandler;
// Clean up on disconnect
addMissionEventHandler ["PlayerDisconnected", {
params ["_id", "_uid", "_name", "_jip", "_owner"];
GVAR(playerOwners) deleteAt _uid;
}];
ADDON = true;

View File

@ -0,0 +1 @@
#include "script_component.hpp"

View File

@ -0,0 +1,2 @@
#include "script_component.hpp"
#include "XEH_PREP.hpp"

16
addons/db/config.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "script_component.hpp"
class CfgPatches {
class ADDON {
name = COMPONENT_NAME;
units[] = {};
weapons[] = {};
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {"forge_server_main"};
authors[] = {"J. Schmidt", "Creedcoder"};
author = "J. Schmidt";
VERSION_CONFIG;
};
};
#include "CfgEventHandlers.hpp"

View File

@ -0,0 +1,26 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_createStore
* Author: J. Schmidt
*
* Description:
* Creates a new collection in the database
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _schema - Store schema <ARRAY>
*
* Return Value:
* Store object or nil if failed
*/
params [["_name", "", [""]], ["_schema", [], [[]]]];
if (_name isEqualTo "") exitWith {
ERROR_MSG("Store name cannot be empty");
nil
};
private _database = call FUNC(verifyDB);
_database call ["createStore", [_name, _schema]]

View File

@ -0,0 +1,39 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_getFromStore
* Author: J. Schmidt
*
* Description:
* Retrieves data from a store
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to retrieve (Optional, get entire store if empty) <STRING>
* 2: _default - Default value if key not found <ANY>
* 3: _namespace - Namespace to use (Optional, default: missionNamespace) <NAMESPACE>
*
* Return Value:
* Retrieved data or default value
*/
params [
["_name", "", [""]],
["_key", "", [""]],
["_default", nil],
["_namespace", missionNamespace, [missionNamespace]]
];
if (_name isEqualTo "") exitWith {
ERROR_MSG_1("Invalid store name: %1",_name);
_default
};
private _stores = _namespace getVariable [QGVAR(stores), createHashMap];
private _store = _stores getOrDefault [_name, createHashMap];
if (_key isEqualTo "") then {
_store
} else {
_store getOrDefault [_key, _default]
}

View File

@ -0,0 +1,25 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_getStore
* Author: J. Schmidt
*
* Description:
* Retrieves a store from the database
*
* Arguments:
* 0: _name - Store name <STRING>
*
* Return Value:
* Store object or nil if not found
*/
params [["_name", "", [""]]];
if (_name isEqualTo "") exitWith {
ERROR_MSG("Store name cannot be empty");
nil
};
private _database = GETMVAR(GVAR(store),nil);
_database call ["getStore", [_name]]

View File

@ -0,0 +1,98 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_initDB
* Author: J. Schmidt
*
* Description:
* Initializes the database system, creating the interface for other modules
*
* Arguments:
* None
*
* Return Value:
* Database interface object
*/
private _storeInterface = createHashMapObject [[
["#type", "IStore"],
["#create", {
private _storeRegistry = GETVAR(profileNamespace,FORGE_STORE_REG,createHashMap);
_self set ["stores", _storeRegistry];
}],
["createStore", {
params [["_name", "", [""]]];
if (_name == "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _stores = _self get "stores";
private _store = _stores getOrDefault [_name, nil];
if !(isNil "_store") exitWith { _store };
private _store = createHashMap;
_stores set [_name, _store];
_store
}],
["getStore", {
params [["_name", "", [""]]];
if (_name == "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _stores = _self get "stores";
_stores getOrDefault [_name, nil]
}],
["deleteStore", {
params [["_name", "", [""]]];
if (_name == "") exitWith { ERROR_MSG("Store name cannot be empty"); false };
private _stores = _self get "stores";
private _store = _stores getOrDefault [_name, nil];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found", _name); false };
_stores deleteAt _name;
true
}],
["set", {
params [["_name", "", [""]], ["_key", "", [""]], ["_value", nil]];
if (_name == "" || _key == "") exitWith { ERROR_MSG("Store name and, or key cannot be empty"); false };
private _stores = _self get "stores";
private _store = _stores getOrDefault [_name, nil];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found", _name); false };
_store set [_key, _value];
true
}],
["get", {
params [["_name", "", [""]], ["_key", "", [""]], ["_default", nil]];
if (_name == "" || _key == "") exitWith { ERROR_MSG("Store name and, or key cannot be empty"); _default };
private _store = _self call ["getStore", [_name]];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found", _name); _default };
_store getOrDefault [_key, _default]
}],
["delete", {
params [["_name", "", [""]], ["_key", "", [""]]];
if (_name == "" || _key == "") exitWith { ERROR_MSG("Store name and, or key cannot be empty"); false };
private _store = _self call ["getStore", [_name]];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found", _name); false };
_store deleteAt _key;
true
}]
]];
SETPVAR(missionNamespace,FORGE_STORE_REG,_storeInterface);
GETMVAR(FORGE_STORE_REG,nil)

View File

@ -0,0 +1,32 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadFromMission
* Author: J. Schmidt
*
* Description:
* Loads data from mission namespace using the database interface
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to retrieve <STRING>
* 2: _default - Default value if not found <ANY>
*
* Return Value:
* Retrieved data or default value
*/
params [
["_name", "", [""]],
["_key", "", [""]],
["_default", nil]
];
private _database = call FUNC(verifyDB);
private _store = _database call ["getStore", [_name]];
if (isNil "_store") exitWith {
_default
};
[_name, _key, _default, missionNamespace] call FUNC(getFromStore)

View File

@ -0,0 +1,32 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadFromProfile
* Author: J. Schmidt
*
* Description:
* Loads data from profile namespace
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to retrieve (Optional, get entire collection if empty) <STRING>
* 2: _default - Default value if key not found <ANY>
*
* Return Value:
* Retrieved data or default value
*/
params [
["_name", "", [""]],
["_key", "", [""]],
["_default", nil]
];
private _database = call FUNC(verifyDB);
private _store = _database call ["getStore", [_name]];
if (isNil "_store") exitWith {
_default
};
[_name, _key, _default, profileNamespace] call FUNC(getFromStore);

View File

@ -0,0 +1,64 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadGameState
* Author: J. Schmidt
*
* Description:
* Loads game state from mission namespace and sets appropriate variables
*
* Arguments:
* None
*
* Return Value:
* Success <BOOL>
*/
private _companyState = ["companyStore", "CompanyState", nil] call FUNC(loadFromMission);
if (!isNil "_companyState") then {
companyFunds = _companyState getOrDefault ["funds", 0];
companyRating = _companyState getOrDefault ["rating", 0];
companyGenerals = _companyState getOrDefault ["operations", []];
companyGarageUnlocks = _companyState getOrDefault ["garage_unlocks", []];
};
private _playerUID = getPlayerUID player;
private _playerState = ["playerStore", _playerUID, nil] call FUNC(loadFromMission);
if (!isNil "_playerState") then {
private _armory_unlocks = _playerState getOrDefault ["armory_unlocks", [[],[],[],[]]];
private _garage_unlocks = _playerState getOrDefault ["garage_unlocks", [[],[],[],[],[],[]]];
private _locker = _playerState getOrDefault ["locker", []];
private _garage = _playerState getOrDefault ["garage", []];
private _cash = _playerState getOrDefault ["cash", 0];
private _bank = _playerState getOrDefault ["bank", 0];
private _number = _playerState getOrDefault ["number", "unknown"];
private _email = _playerState getOrDefault ["email", "unknown@spearnet.mil"];
private _paygrade = _playerState getOrDefault ["paygrade", "E1"];
private _holster = _playerState getOrDefault ["holster", true];
EGVAR(arsenal,armory_unlocks) = _armory_unlocks;
EGVAR(arsenal,garage_unlocks) = _garage_unlocks;
SETPVAR(player,FORGE_Locker,_locker);
SETPVAR(player,FORGE_Garage,_garage);
SETPVAR(player,FORGE_Cash,_cash);
SETPVAR(player,FORGE_Bank,_bank);
SETPVAR(player,FORGE_Phone_Number,_number);
SETPVAR(player,FORGE_Email,_email);
SETPVAR(player,FORGE_Paygrade,_paygrade);
SETPVAR(player,FORGE_Holster_Weapon,_holster);
if (isNull objectParent player) then {
player setUnitLoadout (_playerState getOrDefault ["loadout", []]);
if (_playerState getOrDefault ["currentWeapon", ""] != "") then {
player selectWeapon (_playerState get "currentWeapon");
};
player setPosASL (_playerState getOrDefault ["position", getPosASL player]);
player setDir (_playerState getOrDefault ["direction", 0]);
if (_playerState getOrDefault ["stance", ""] != "") then {
[player, _playerState get "stance"] call ace_common_fnc_setStance;
};
};
};
true

View File

@ -0,0 +1,43 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_processDBRequest
* Author: J. Schmidt
*
* Description:
* Processes database requests from clients
*
* Arguments:
* 0: _type - Type of database operation <STRING>
* 1: _data - Data for the operation <ANY>
*
* Return Value:
* Operation result <ANY>
*/
params [["_type", "", [""]], ["_data", nil, []]];
switch (_type) do {
case "getStore": {
params ["_name"];
[_name] call FUNC(getStore);
};
case "saveTostore": {
_data params ["_name", "_data", "_key"];
[_name, _data, _key] call FUNC(saveToStore);
};
case "getFromstore": {
_data params ["_name", "_key"];
[_name, _key] call FUNC(getFromStore);
};
case "loadGameState": {
[] call FUNC(loadGameState);
};
case "saveGameState": {
[_data] call FUNC(saveGameState);
};
default {
WARNING_1("Unknown database request type: %1", _type);
nil
};
};

View File

@ -0,0 +1,78 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveGameState
* Author: J. Schmidt
*
* Description:
* Collects and saves the current game state to mission namespace
*
* Arguments:
* None
*
* Return Value:
* Success <BOOL>
*/
if (isNil "companyFunds") then { companyFunds = 0 };
if (isNil "companyRating") then { companyRating = 0 };
if (isNil "companyGenerals") then { companyGenerals = [] };
if (isNil "companyGarageUnlocks") then { companyGarageUnlocks = [] };
if (isNil EGVAR(arsenal,armory_unlocks)) then { EGVAR(arsenal,armory_unlocks) = [[],[],[],[]] };
if (isNil EGVAR(arsenal,garage_unlocks)) then { EGVAR(arsenal,garage_unlocks) = [[],[],[],[],[],[]] };
private _companyState = createHashMapFromArray [
["key", "CompanyState"],
["funds", companyFunds],
["rating", companyRating],
["operations", companyGenerals],
["garage_unlocks", companyGarageUnlocks]
];
["companyStore", _companyState, "CompanyState"] call FUNC(saveToMission);
{
if (alive _x) then {
private _playerState = createHashMapFromArray [
["key", getPlayerUID _x],
["armory_unlocks", EGVAR(arsenal,armory_unlocks)],
["garage_unlocks", EGVAR(arsenal,garage_unlocks)],
["locker", GETVAR(_x,FORGE_Locker,[])],
["garage", GETVAR(_x,FORGE_Garage,[])],
["cash", GETVAR(_x,FORGE_Cash,0)],
["bank", GETVAR(_x,FORGE_Bank,0)],
["number", GETVAR(_x,FORGE_Phone_Number,"unknown")],
["email", GETVAR(_x,FORGE_Email,"unknown@spearnet.mil")],
["paygrade", GETVAR(_x,FORGE_Paygrade,"E1")],
["reputation", rating _x],
["loadout", getUnitLoadout _x],
["holster", GETVAR(_x,FORGE_Holster_Weapon,true)],
["position", getPosASLVisual _x],
["direction", getDirVisual _x]
];
if (isNull objectParent _x) then {
_playerState set ["currentWeapon", currentMuzzle _x];
_playerState set ["stance", stance _x];
};
["playerStore", _playerState, getPlayerUID _x] call FUNC(saveToMission);
};
} forEach playableUnits;
private _vehicles = nearestObjects [player, ["LandVehicle"], 50];
{
if (alive _x) then {
private _vehicleState = createHashMapFromArray [
["key", netId _x],
["class", typeOf _x],
["position", getPosATL _x],
["direction", getDir _x],
["health", damage _x]
];
["vehicleStore", _vehicleState, netId _x] call FUNC(saveToMission);
};
} forEach _vehicles;
true

View File

@ -0,0 +1,34 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToMission
* Author: J. Schmidt
*
* Description:
* Saves data to mission namespace through the database interface
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _data - Data to save <HASHMAP, ARRAY, ANY>
* 2: _key - Key to save under <STRING>
*
* Return Value:
* Success <BOOL>
*/
params [
["_name", "", [""]],
["_data", nil, [createHashMap, [], "", 0, true]],
["_key", "", [""]]
];
private _database = call FUNC(verifyDB);
private _store = _database call ["getStore", [_name]];
if (isNil "_store") then {
_store = _database call ["createStore", [_name, []]];
};
[_name, _data, _key, missionNamespace] call FUNC(saveToStore);
true

View File

@ -0,0 +1,34 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToProfile
* Author: J. Schmidt
*
* Description:
* Saves data to profile namespace through the database interface
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _data - Data to save <HASHMAP, ARRAY, ANY>
* 2: _key - Key to save under <STRING>
*
* Return Value:
* Success <BOOL>
*/
params [
["_name", "", [""]],
["_data", nil, [createHashMap, [], "", 0, true]],
["_key", "", [""]]
];
private _database = call FUNC(verifyDB);
private _store = _database call ["getStore", [_name]];
if (isNil "_store") then {
_store = _database call ["createStore", [_name, []]];
};
[_name, _data, _key, profileNamespace] call FUNC(saveToStore);
true

View File

@ -0,0 +1,44 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToStore
* Author: J. Schmidt
*
* Description:
* Saves data to a store in the specified namespace
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _data - Data to save <HASHMAP, ARRAY, ANY>
* 2: _key - Key to save under <STRING>
* 3: _namespace - Namespace to use (Optional, default: missionNamespace) <NAMESPACE>
*
* Return Value:
* Success <BOOL>
*/
params [
["_name", "", [""]],
["_data", nil, [createHashMap, [], "", 0, true]],
["_key", "", [""]],
["_namespace", missionNamespace, [missionNamespace]]
];
if (_name isEqualTo "" || _key isEqualTo "") exitWith {
ERROR_MSG_2("Invalid store name or key: %1, %2", _name, _key);
false
};
private _stores = _namespace getVariable [QGVAR(stores), createHashMap];
private _store = _stores getOrDefault [_name, createHashMap];
_store set [_key, _data];
_stores set [_name, _store];
_namespace setVariable [QGVAR(stores), _stores];
switch (_namespace) do {
case (missionNamespace): { saveMissionProfileNamespace; };
case (profileNamespace): { saveProfileNamespace; };
};
true

View File

@ -0,0 +1,39 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToTemp
* Author: J. Schmidt
*
* Description:
* Saves data to temporary mission store (not persisted between sessions)
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _data - Data to save <HASHMAP, ARRAY, ANY>
* 2: _key - Key to save under <STRING>
*
* Return Value:
* Success <BOOL>
*/
params [
["_name", "", [""]],
["_data", nil, [createHashMap, [], "", 0, true]],
["_key", "", [""]]
];
private _database = call FUNC(verifyDB);
private _store = _database call ["getStore", [_name]];
if (isNil "_store") then {
_store = _database call ["createStore", [_name, []]];
};
private _tempStores = missionNamespace getVariable [QGVAR(tempStores), createHashMap];
private _tempStore = _tempStores getOrDefault [_name, createHashMap];
_tempStore set [_key, _data];
_tempStores set [_name, _tempStore];
missionNamespace setVariable [QGVAR(tempStores), _tempStores];
true

View File

@ -0,0 +1,22 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_verifyDB
* Author: J. Schmidt
*
* Description:
* Verifies the database is initialized and returns the database object
*
* Arguments:
* None
*
* Return Value:
* Database object <HASHMAP>
*/
private _store = GETMVAR(GVAR(store),nil);
if (isNil "_store") then {
_store = [] call FUNC(initDB);
};
_store

View File

@ -0,0 +1,15 @@
#define COMPONENT db
#define COMPONENT_BEAUTIFIED DB
#include "..\main\script_mod.hpp"
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
#ifdef DEBUG_ENABLED_DB
#define DEBUG_MODE_FULL
#endif
#ifdef DEBUG_SETTINGS_DB
#define DEBUG_SETTINGS DEBUG_SETTINGS_DB
#endif
#include "..\main\script_macros.hpp"

View File

@ -3,7 +3,7 @@ class CfgMods {
dir = "@forge_server";
name = "FORGE Server";
author = "IDSolutions";
picture = "A3\Ui_f\data\Logos\arma3_expansion_alpha_ca";
picture = "A3\Ui_f\data\Logos\arma3_expansion_alpha_ca.paa";
hideName = "false";
hidePicture = "false";
action = "https://innovativedevsolutions.org";

View File

@ -1,5 +1,6 @@
PREP(cargoToPairs);
PREP(playSound);
PREP(playerGroup2Server);
PREP(redirectClient2Server);
PREP(emailMessage);
PREP(textMessage);

View File

@ -0,0 +1,10 @@
#include "..\script_component.hpp"
params [["_ip", "127.0.0.1", [""]], ["_port", "2312", [""]], ["_password", "abc", [""]]];
private _units = units group player select { isPlayer _x };
private _machineIDs = _units apply { owner _x };
{
[_ip, _port, _password] remoteExecCall ["forge_server_misc_fnc_redirectClient2Server", _x];
} forEach _machineIDs;