- Remove in-SQF registry mirroring for actor, bank, CAD, org, and task state - Add validation harness and persistence warnings for hot-state flows - Treat CAD and task operational state as restart-scoped
299 lines
11 KiB
Plaintext
299 lines
11 KiB
Plaintext
#include "..\script_component.hpp"
|
|
|
|
/*
|
|
* File: fnc_initGroupRepository.sqf
|
|
* Author: IDSolutions
|
|
* Date: 2026-03-30
|
|
* Public: No
|
|
*
|
|
* Description:
|
|
* Initializes the CAD group repository for live group state, roles,
|
|
* and dispatcher/leader-managed group profiles.
|
|
*
|
|
* Arguments:
|
|
* None
|
|
*
|
|
* Return Value:
|
|
* CAD group repository object [HASHMAP OBJECT]
|
|
*
|
|
* Example:
|
|
* call forge_server_cad_fnc_initGroupRepository
|
|
*/
|
|
|
|
#pragma hemtt ignore_variables ["_self"]
|
|
GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|
["#type", "CadGroupRepositoryBaseClass"],
|
|
["#create", compileFinal {
|
|
_self set ["validStatuses", [
|
|
"available",
|
|
"en_route",
|
|
"on_task",
|
|
"holding",
|
|
"danger",
|
|
"unavailable"
|
|
]];
|
|
_self set ["validRoles", [
|
|
"infantry",
|
|
"recon",
|
|
"armor",
|
|
"air",
|
|
"logistics",
|
|
"support"
|
|
]];
|
|
}],
|
|
["resolveGroupId", compileFinal {
|
|
params [["_group", grpNull, [grpNull]]];
|
|
|
|
if (isNull _group) exitWith { "" };
|
|
|
|
private _leader = leader _group;
|
|
private _leaderUid = if (isNull _leader) then { "" } else { getPlayerUID _leader };
|
|
if (_leaderUid isNotEqualTo "") exitWith { format ["group:%1", _leaderUid] };
|
|
|
|
private _groupLabel = groupId _group;
|
|
if (_groupLabel isNotEqualTo "") exitWith { format ["group:%1", _groupLabel] };
|
|
|
|
str _group
|
|
}],
|
|
["getCurrentTaskIdForGroup", compileFinal {
|
|
params [["_groupID", "", [""]]];
|
|
|
|
if (_groupID isEqualTo "") exitWith { "" };
|
|
|
|
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
|
if (_assignmentRepository isEqualTo createHashMap) exitWith { "" };
|
|
|
|
_assignmentRepository call ["getCurrentTaskIdForGroup", [_groupID]]
|
|
}],
|
|
["syncGroups", compileFinal {
|
|
private _liveGroups = [];
|
|
|
|
{
|
|
private _group = _x;
|
|
if (side _group isNotEqualTo west) then { continue; };
|
|
|
|
private _members = allPlayers select { group _x isEqualTo _group };
|
|
if (_members isEqualTo []) then { continue; };
|
|
|
|
private _leader = leader _group;
|
|
if (isNull _leader || { !isPlayer _leader }) then {
|
|
_leader = _members # 0;
|
|
};
|
|
|
|
private _groupID = _self call ["resolveGroupId", [_group]];
|
|
if (_groupID isEqualTo "") then { continue; };
|
|
|
|
private _leaderUid = getPlayerUID _leader;
|
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_leaderUid]];
|
|
private _memberUids = [];
|
|
private _memberRoster = [];
|
|
{
|
|
private _memberUid = getPlayerUID _x;
|
|
private _memberState = toLowerANSI (lifeState _x);
|
|
|
|
if (_memberUid isNotEqualTo "") then {
|
|
_memberUids pushBack _memberUid;
|
|
};
|
|
|
|
_memberRoster pushBack (createHashMapFromArray [
|
|
["uid", _memberUid],
|
|
["name", name _x],
|
|
["lifeState", _memberState],
|
|
["isLeader", _x isEqualTo _leader]
|
|
]);
|
|
} forEach _members;
|
|
|
|
_liveGroups pushBack (createHashMapFromArray [
|
|
["groupId", _groupID],
|
|
["callsign", [groupId _group, _groupID] select ((groupId _group) isEqualTo "")],
|
|
["leaderUid", _leaderUid],
|
|
["leaderName", name _leader],
|
|
["memberUids", _memberUids],
|
|
["members", _memberRoster],
|
|
["orgId", _orgID],
|
|
["role", "infantry"],
|
|
["status", "available"],
|
|
["position", getPosATL _leader],
|
|
["currentTaskId", _self call ["getCurrentTaskIdForGroup", [_groupID]]],
|
|
["lastUpdate", serverTime]
|
|
]);
|
|
} forEach allGroups;
|
|
|
|
private _mergedGroups = _liveGroups;
|
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
|
if (_persistenceService isNotEqualTo createHashMap) then {
|
|
private _buildResult = _persistenceService call ["buildGroups", [_liveGroups]];
|
|
if (_buildResult getOrDefault ["success", false]) then {
|
|
_mergedGroups = +(_buildResult getOrDefault ["data", _liveGroups]);
|
|
};
|
|
};
|
|
|
|
private _nextRegistry = createHashMap;
|
|
{
|
|
if !(_x isEqualType createHashMap) then { continue; };
|
|
private _groupID = _x getOrDefault ["groupId", ""];
|
|
if (_groupID isEqualTo "") then { continue; };
|
|
|
|
private _groupRecord = +_x;
|
|
_nextRegistry set [_groupID, _groupRecord];
|
|
} forEach _mergedGroups;
|
|
|
|
_nextRegistry
|
|
}],
|
|
["getGroupRecord", compileFinal {
|
|
params [["_groupID", "", [""]]];
|
|
|
|
if (_groupID isEqualTo "") exitWith { createHashMap };
|
|
|
|
private _groupRegistry = _self call ["syncGroups", []];
|
|
+(_groupRegistry getOrDefault [_groupID, createHashMap])
|
|
}],
|
|
["getPlayerGroupId", compileFinal {
|
|
params [["_uid", "", [""]]];
|
|
|
|
if (_uid isEqualTo "") exitWith { "" };
|
|
|
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
|
if (_player isEqualTo objNull) exitWith { "" };
|
|
|
|
_self call ["resolveGroupId", [group _player]]
|
|
}],
|
|
["isGroupLeader", compileFinal {
|
|
params [["_uid", "", [""]], ["_groupID", "", [""]]];
|
|
|
|
if (_uid isEqualTo "" || { _groupID isEqualTo "" }) exitWith { false };
|
|
|
|
private _groupRecord = _self call ["getGroupRecord", [_groupID]];
|
|
(_groupRecord getOrDefault ["leaderUid", ""]) isEqualTo _uid
|
|
}],
|
|
["buildGroups", compileFinal {
|
|
private _groupRegistry = _self call ["syncGroups", []];
|
|
private _groups = [];
|
|
|
|
{
|
|
_groups pushBack +_y;
|
|
} forEach _groupRegistry;
|
|
|
|
_groups
|
|
}],
|
|
["applyGroupProfileUpdate", compileFinal {
|
|
params [
|
|
["_requesterUid", "", [""]],
|
|
["_groupID", "", [""]],
|
|
["_status", "", [""]],
|
|
["_role", "", [""]],
|
|
["_mode", "profile", [""]]
|
|
];
|
|
|
|
private _result = createHashMapFromArray [
|
|
["success", false],
|
|
["message", "Unable to update group profile."],
|
|
["changed", false],
|
|
["group", createHashMap]
|
|
];
|
|
|
|
private _finalStatus = toLowerANSI _status;
|
|
private _finalRole = toLowerANSI _role;
|
|
private _hasStatus = _finalStatus isNotEqualTo "";
|
|
private _hasRole = _finalRole isNotEqualTo "";
|
|
|
|
if (_mode isEqualTo "status" && !_hasStatus) exitWith {
|
|
_result set ["message", "Invalid group status."];
|
|
_result
|
|
};
|
|
|
|
if (_mode isEqualTo "role" && !_hasRole) exitWith {
|
|
_result set ["message", "Invalid group role."];
|
|
_result
|
|
};
|
|
|
|
if (_mode isEqualTo "profile" && !(_hasStatus || _hasRole)) exitWith {
|
|
_result set ["message", "No group changes were provided."];
|
|
_result
|
|
};
|
|
|
|
if (_hasStatus && !(_finalStatus in (_self getOrDefault ["validStatuses", []]))) exitWith {
|
|
_result set ["message", "Invalid group status."];
|
|
_result
|
|
};
|
|
|
|
if (_hasRole && !(_finalRole in (_self getOrDefault ["validRoles", []]))) exitWith {
|
|
_result set ["message", "Invalid group role."];
|
|
_result
|
|
};
|
|
|
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
|
private _isAuthorized = (_self call ["isGroupLeader", [_requesterUid, _groupID]]) || { _permissionService call ["canDispatch", [_requesterUid]] };
|
|
if !_isAuthorized exitWith {
|
|
_result set ["message", "You are not authorized to update that group."];
|
|
_result
|
|
};
|
|
|
|
private _groupRegistry = _self call ["syncGroups", []];
|
|
private _groupRecord = +(_groupRegistry getOrDefault [_groupID, createHashMap]);
|
|
if (_groupRecord isEqualTo createHashMap) exitWith {
|
|
_result set ["message", "Group could not be resolved."];
|
|
_result
|
|
};
|
|
|
|
private _didChangeStatus = _hasStatus && { (_groupRecord getOrDefault ["status", ""]) isNotEqualTo _finalStatus };
|
|
private _didChangeRole = _hasRole && { (_groupRecord getOrDefault ["role", ""]) isNotEqualTo _finalRole };
|
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
|
_result set ["message", "CAD extension state is unavailable."];
|
|
_result
|
|
};
|
|
|
|
private _updateContext = createHashMapFromArray [
|
|
["groupId", _groupID],
|
|
["groupCallsign", _groupRecord getOrDefault ["callsign", _groupID]],
|
|
["requesterUid", _requesterUid],
|
|
["currentRole", _groupRecord getOrDefault ["role", "infantry"]],
|
|
["currentStatus", _groupRecord getOrDefault ["status", "available"]],
|
|
["role", [_finalRole, ""] select !_hasRole],
|
|
["status", [_finalStatus, ""] select !_hasStatus],
|
|
["mode", _mode]
|
|
];
|
|
|
|
private _profileResult = _persistenceService call ["updateGroupProfileFromContext", [_updateContext]];
|
|
if !(_profileResult getOrDefault ["success", false]) exitWith {
|
|
_result set ["message", "CAD extension rejected the group profile update."];
|
|
_result
|
|
};
|
|
|
|
private _profileData = +(_profileResult getOrDefault ["data", createHashMap]);
|
|
private _profile = +(_profileData getOrDefault ["profile", createHashMap]);
|
|
if (_profile isEqualTo createHashMap) exitWith {
|
|
_result set ["message", "CAD extension returned an invalid group profile."];
|
|
_result
|
|
};
|
|
|
|
_groupRecord set ["role", _profile getOrDefault ["role", _groupRecord getOrDefault ["role", "infantry"]]];
|
|
_groupRecord set ["status", _profile getOrDefault ["status", _groupRecord getOrDefault ["status", "available"]]];
|
|
_groupRecord set ["lastUpdate", serverTime];
|
|
|
|
private _activityEntry = +(_profileData getOrDefault ["activity", createHashMap]);
|
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
|
};
|
|
|
|
_result set ["success", true];
|
|
_result set ["message", _profileData getOrDefault ["message", "Group profile updated."]];
|
|
_result set ["changed", _profileData getOrDefault ["changed", (_didChangeStatus || _didChangeRole)]];
|
|
_result set ["group", _groupRecord];
|
|
_result
|
|
}],
|
|
["updateGroupStatus", compileFinal {
|
|
_self call ["applyGroupProfileUpdate", [_this # 0, _this # 1, _this # 2, "", "status"]]
|
|
}],
|
|
["updateGroupRole", compileFinal {
|
|
_self call ["applyGroupProfileUpdate", [_this # 0, _this # 1, "", _this # 2, "role"]]
|
|
}],
|
|
["updateGroupProfile", compileFinal {
|
|
_self call ["applyGroupProfileUpdate", [_this # 0, _this # 1, _this # 2, _this # 3, "profile"]]
|
|
}]
|
|
];
|
|
|
|
createHashMapObject [GVAR(GroupRepositoryBaseClass)]
|