forge/arma/server/addons/cad/functions/fnc_initAssignmentRepository.sqf
Jacob Schmidt 9b31184f0c Separate CAD task assignment from task acceptance
- Reserve tasks as `available`/`assigned` before leader acknowledgement
- Update CAD and task lifecycle handling and docs to reflect the new flow
- Remove startup heartbeat reset and reset task backend explicitly in preInit
2026-05-17 10:12:15 -05:00

608 lines
27 KiB
Plaintext

#include "..\script_component.hpp"
/*
* File: fnc_initAssignmentRepository.sqf
* Author: IDSolutions
* Date: 2026-03-30
* Public: No
*
* Description:
* Initializes the CAD assignment repository for contract assignment
* state and dispatcher/group-leader task actions.
*
* Arguments:
* None
*
* Return Value:
* CAD assignment repository object [HASHMAP OBJECT]
*
* Example:
* call forge_server_cad_fnc_initAssignmentRepository
*/
#pragma hemtt ignore_variables ["_self"]
GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
["#type", "CadAssignmentRepositoryBaseClass"],
["#create", compileFinal {
_self set ["ownershipHydrated", false];
}],
["loadState", compileFinal {
private _result = createHashMapFromArray [
["success", false],
["assignments", createHashMap],
["dispatchOrders", createHashMap]
];
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
if (_persistenceService isEqualTo createHashMap) exitWith { _result };
private _assignmentsResult = _persistenceService call ["loadAssignments", []];
if !(_assignmentsResult getOrDefault ["success", false]) exitWith { _result };
private _ordersResult = _persistenceService call ["loadDispatchOrders", []];
if !(_ordersResult getOrDefault ["success", false]) exitWith { _result };
private _assignmentRegistry = +(_assignmentsResult getOrDefault ["data", createHashMap]);
private _dispatchOrderRegistry = +(_ordersResult getOrDefault ["data", createHashMap]);
if !(_self getOrDefault ["ownershipHydrated", false]) then {
{
if ((_y getOrDefault ["state", ""]) isNotEqualTo "acknowledged") then { continue; };
if ((_y getOrDefault ["acknowledgedByUid", ""]) isEqualTo "") then { continue; };
if ((_dispatchOrderRegistry getOrDefault [_x, createHashMap]) isNotEqualTo createHashMap) then { continue; };
if !((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) in ["assigned", "active"]) then { continue; };
EGVAR(task,TaskStore) call ["acceptTask", [_x, _y getOrDefault ["acknowledgedByUid", ""]]];
EGVAR(task,TaskStore) call ["setTaskStatus", [_x, "active"]];
} forEach _assignmentRegistry;
_self set ["ownershipHydrated", true];
};
_result set ["success", true];
_result set ["assignments", _assignmentRegistry];
_result set ["dispatchOrders", _dispatchOrderRegistry];
_result
}],
["pruneAssignments", compileFinal {
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { 0 };
private _assignmentRegistry = _state getOrDefault ["assignments", createHashMap];
private _dispatchOrderRegistry = _state getOrDefault ["dispatchOrders", createHashMap];
private _keysToRemove = [];
{
if ((_dispatchOrderRegistry getOrDefault [_x, createHashMap]) isNotEqualTo createHashMap) then {
continue;
};
private _status = EGVAR(task,TaskStore) call ["getTaskStatus", [_x]];
if !(_status in ["available", "assigned", "active", ""]) then {
_keysToRemove pushBack _x;
};
} forEach _assignmentRegistry;
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
if (_persistenceService isNotEqualTo createHashMap) then {
{
_persistenceService call ["deleteAssignment", [_x]];
} forEach _keysToRemove;
};
count _keysToRemove
}],
["getAssignments", compileFinal {
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { [] };
values (_state getOrDefault ["assignments", createHashMap])
}],
["isDispatchOrder", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { false };
((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap
}],
["getAssignmentByTaskId", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { createHashMap };
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { createHashMap };
+((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap])
}],
["getDispatchOrderByTaskId", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { createHashMap };
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { createHashMap };
+((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap])
}],
["getCurrentTaskIdForGroup", compileFinal {
params [["_groupID", "", [""]]];
if (_groupID isEqualTo "") exitWith { "" };
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { "" };
private _assignmentRegistry = _state getOrDefault ["assignments", createHashMap];
private _dispatchOrderRegistry = _state getOrDefault ["dispatchOrders", createHashMap];
private _taskID = "";
{
if ((_y getOrDefault ["groupId", ""]) isNotEqualTo _groupID) then { continue; };
if !((_y getOrDefault ["state", ""]) in ["assigned", "acknowledged"]) then { continue; };
private _dispatchOrder = +(_dispatchOrderRegistry getOrDefault [_x, createHashMap]);
if (_dispatchOrder isEqualTo createHashMap) then {
if !((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) in ["assigned", "active"]) then { continue; };
_taskID = _x;
} else {
_taskID = _dispatchOrder getOrDefault ["title", _x];
};
} forEach _assignmentRegistry;
_taskID
}],
["buildDispatchOrderEntry", compileFinal {
params [
["_taskID", "", [""]],
["_order", createHashMap, [createHashMap]],
["_assignment", createHashMap, [createHashMap]],
["_groupRepository", createHashMap, [createHashMap]]
];
if (_taskID isEqualTo "" || { _order isEqualTo createHashMap }) exitWith { createHashMap };
private _entry = +_order;
private _targetGroupID = _order getOrDefault ["targetGroupId", ""];
if (_targetGroupID isNotEqualTo "") then {
private _targetGroup = _groupRepository call ["getGroupRecord", [_targetGroupID]];
if (_targetGroup isNotEqualTo createHashMap) then {
private _targetCallsign = _targetGroup getOrDefault ["callsign", _targetGroupID];
_entry set ["targetGroupCallsign", _targetCallsign];
_entry set ["position", +(_targetGroup getOrDefault ["position", _entry getOrDefault ["position", []]])];
_entry set ["title", format ["Backup %1", _targetCallsign]];
if ((_order getOrDefault ["note", ""]) isEqualTo "") then {
_entry set ["description", format ["Dispatch order to back up %1 at its current position.", _targetCallsign]];
};
};
};
_entry set ["taskId", _taskID];
_entry set ["taskID", _taskID];
_entry set ["type", _entry getOrDefault ["type", "dispatch_order"]];
_entry set ["isDispatchOrder", true];
_entry set ["assignedGroupId", _assignment getOrDefault ["groupId", ""]];
_entry set ["assignmentState", [_assignment getOrDefault ["state", ""], "unassigned"] select (_assignment isEqualTo createHashMap)];
_entry
}],
["buildDispatchOrderEntryForTask", compileFinal {
params [
["_taskID", "", [""]],
["_groupRepository", createHashMap, [createHashMap]]
];
if (_taskID isEqualTo "") exitWith { createHashMap };
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith { createHashMap };
private _order = +((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]);
if (_order isEqualTo createHashMap) exitWith { createHashMap };
private _assignment = +((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap]);
_self call ["buildDispatchOrderEntry", [_taskID, _order, _assignment, _groupRepository]]
}],
["assignTaskToGroup", compileFinal {
params [
["_requesterUid", "", [""]],
["_taskID", "", [""]],
["_groupID", "", [""]],
["_note", "", [""]]
];
private _result = createHashMapFromArray [
["success", false],
["message", "Unable to assign task."],
["assignment", createHashMap]
];
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
_result set ["message", "You are not authorized to assign contracts."];
_result
};
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _assignmentRegistry = _state getOrDefault ["assignments", createHashMap];
private _dispatchOrderRegistry = _state getOrDefault ["dispatchOrders", createHashMap];
private _isDispatchOrder = (_dispatchOrderRegistry getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap;
if (!_isDispatchOrder && { (EGVAR(task,TaskStore) call ["getTaskStatus", [_taskID]]) isNotEqualTo "available" }) exitWith {
_result set ["message", "Task is no longer available."];
_result
};
private _existingAssignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
if (
_existingAssignment isNotEqualTo createHashMap
&& { (_existingAssignment getOrDefault ["state", ""]) in ["assigned", "acknowledged"] }
) exitWith {
_result set ["message", ["Task is already assigned and must be declined or completed before reassignment.", "Dispatch order is already assigned and must be declined or closed before reassignment."] select _isDispatchOrder];
_result set ["assignment", _existingAssignment];
_result
};
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
private _groupRecord = _groupRepository call ["getGroupRecord", [_groupID]];
if (_groupRecord isEqualTo createHashMap) exitWith {
_result set ["message", "Selected group is unavailable."];
_result
};
private _leaderUid = _groupRecord getOrDefault ["leaderUid", ""];
if (_leaderUid isEqualTo "") exitWith {
_result set ["message", "Selected group has no online leader."];
_result
};
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
private _assignment = createHashMapFromArray [
["taskId", _taskID],
["groupId", _groupID],
["groupCallsign", _groupRecord getOrDefault ["callsign", _groupID]],
["assignedByUid", _requesterUid],
["assignedByName", ["Dispatcher", name _requesterPlayer] select (_requesterPlayer isNotEqualTo objNull)],
["assignedAt", serverTime],
["state", "assigned"],
["note", _note]
];
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
if (_persistenceService isEqualTo createHashMap) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _assignResult = _persistenceService call ["assignAssignment", [_taskID, _assignment]];
if !(_assignResult getOrDefault ["success", false]) exitWith {
_result set ["message", "CAD extension rejected the assignment."];
_result
};
private _assignData = +(_assignResult getOrDefault ["data", createHashMap]);
_assignment = +(_assignData getOrDefault ["assignment", createHashMap]);
if (_assignment isEqualTo createHashMap) exitWith {
_result set ["message", "CAD extension returned an invalid assignment."];
_result
};
private _activityEntry = +(_assignData getOrDefault ["activity", createHashMap]);
if (_activityEntry isNotEqualTo createHashMap) then {
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
_activityRepository call ["appendEntry", [_activityEntry]];
};
if (!_isDispatchOrder) then {
EGVAR(task,TaskStore) call ["setTaskStatus", [_taskID, "assigned"]];
};
_result set ["success", true];
_result set ["message", _assignData getOrDefault ["message", ["Task assigned.", "Dispatch order assigned."] select _isDispatchOrder]];
_result set ["assignment", _assignment];
_result set ["leaderUid", _leaderUid];
_result set ["isDispatchOrder", _isDispatchOrder];
if (_isDispatchOrder) then {
_result set ["order", +(_dispatchOrderRegistry getOrDefault [_taskID, createHashMap])];
};
_result
}],
["createDispatchOrder", compileFinal {
params [
["_requesterUid", "", [""]],
["_assigneeGroupID", "", [""]],
["_targetGroupID", "", [""]],
["_note", "", [""]],
["_priority", "priority", [""]],
["_request", createHashMap, [createHashMap]]
];
private _result = createHashMapFromArray [
["success", false],
["message", "Unable to create dispatch order."],
["assignment", createHashMap],
["order", createHashMap]
];
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
_result set ["message", "You are not authorized to create dispatch orders."];
_result
};
if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith {
_result set ["message", "Assignee and target groups are required."];
_result
};
if (_assigneeGroupID isEqualTo _targetGroupID) exitWith {
_result set ["message", "Assignee and target groups must be different."];
_result
};
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
private _assigneeGroup = _groupRepository call ["getGroupRecord", [_assigneeGroupID]];
if (_assigneeGroup isEqualTo createHashMap) exitWith {
_result set ["message", "Selected assignee group is unavailable."];
_result
};
private _assigneeLeaderUid = _assigneeGroup getOrDefault ["leaderUid", ""];
if (_assigneeLeaderUid isEqualTo "") exitWith {
_result set ["message", "Selected assignee group has no online leader."];
_result
};
private _targetGroup = _groupRepository call ["getGroupRecord", [_targetGroupID]];
if (_targetGroup isEqualTo createHashMap) exitWith {
_result set ["message", "Selected target group is unavailable."];
_result
};
private _validPriorities = ["routine", "priority", "emergency"];
private _finalPriority = toLowerANSI _priority;
if !(_finalPriority in _validPriorities) then {
_finalPriority = "priority";
};
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
private _dispatchContext = createHashMapFromArray [
["assigneeGroupId", _assigneeGroupID],
["assigneeGroupCallsign", _assigneeGroup getOrDefault ["callsign", _assigneeGroupID]],
["targetGroupId", _targetGroupID],
["targetGroupCallsign", _targetGroup getOrDefault ["callsign", _targetGroupID]],
["targetPosition", +(_targetGroup getOrDefault ["position", []])],
["createdByUid", _requesterUid],
["createdByName", ["Dispatcher", name _requesterPlayer] select (_requesterPlayer isNotEqualTo objNull)],
["requestId", _request getOrDefault ["requestId", ""]],
["requestType", _request getOrDefault ["type", ""]],
["requestTitle", _request getOrDefault ["title", ""]],
["requestSummary", _request getOrDefault ["summary", ""]],
["requestFields", +(_request getOrDefault ["fields", createHashMap])],
["note", _note],
["priority", _finalPriority],
["createdAt", serverTime]
];
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
if (_persistenceService isEqualTo createHashMap) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _createResult = _persistenceService call ["createDispatchOrderFromContext", [_dispatchContext]];
if !(_createResult getOrDefault ["success", false]) exitWith {
_result set ["message", "CAD extension rejected the dispatch order."];
_result
};
private _createData = +(_createResult getOrDefault ["data", createHashMap]);
private _taskID = _createData getOrDefault ["taskId", ""];
private _order = +(_createData getOrDefault ["order", createHashMap]);
private _assignment = +(_createData getOrDefault ["assignment", createHashMap]);
if (_taskID isEqualTo "" || { _order isEqualTo createHashMap } || { _assignment isEqualTo createHashMap }) exitWith {
_result set ["message", "CAD extension returned an invalid dispatch order."];
_result
};
private _activityEntry = +(_createData 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", _createData getOrDefault ["message", "Dispatch order created."]];
_result set ["assignment", _assignment];
_result set ["order", _order];
_result set ["leaderUid", _assigneeLeaderUid];
_result set ["isDispatchOrder", true];
_result
}],
["closeDispatchOrder", compileFinal {
params [["_requesterUid", "", [""]], ["_taskID", "", [""]]];
private _result = createHashMapFromArray [
["success", false],
["message", "Unable to close dispatch order."],
["assignment", createHashMap]
];
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
_result set ["message", "You are not authorized to close dispatch orders."];
_result
};
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _order = +((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]);
if (_order isEqualTo createHashMap) exitWith {
_result set ["message", "Dispatch order could not be resolved."];
_result
};
private _assignment = +((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap]);
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
if (_persistenceService isEqualTo createHashMap) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _closeResult = _persistenceService call ["closeDispatchOrder", [_taskID]];
if !(_closeResult getOrDefault ["success", false]) exitWith {
_result set ["message", "CAD extension rejected the dispatch order close."];
_result
};
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
_assignment = +(_closeData getOrDefault ["assignment", _assignment]);
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
if (_activityEntry isNotEqualTo createHashMap) then {
_activityEntry set ["actorUid", _requesterUid];
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
_activityRepository call ["appendEntry", [_activityEntry]];
};
_result set ["success", true];
_result set ["message", _closeData getOrDefault ["message", "Dispatch order closed."]];
_result set ["assignment", _assignment];
_result set ["isDispatchOrder", true];
_result
}],
["applyAssignmentTransition", compileFinal {
params [["_requesterUid", "", [""]], ["_taskID", "", [""]]];
private _result = createHashMapFromArray [
["success", false],
["message", "Unable to update task assignment."],
["assignment", createHashMap]
];
private _transition = _this param [2, "acknowledge", [""]];
private _state = _self call ["loadState", []];
if !(_state getOrDefault ["success", false]) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _assignment = +((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap]);
private _isDispatchOrder = ((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap;
if (_assignment isEqualTo createHashMap) exitWith {
_result set ["message", "Task is not assigned."];
_result
};
private _groupID = _assignment getOrDefault ["groupId", ""];
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
if !(_groupRepository call ["isGroupLeader", [_requesterUid, _groupID]]) exitWith {
_result set ["message", format ["Only the assigned group leader can %1 this task.", _transition]];
_result
};
switch (_transition) do {
case "acknowledge": {
if (!_isDispatchOrder) then {
private _acceptResult = EGVAR(task,TaskStore) call ["acceptTask", [_taskID, _requesterUid]];
if !(_acceptResult getOrDefault ["success", false]) exitWith {
_result set ["message", _acceptResult getOrDefault ["message", "Failed to accept task."]];
_result
};
EGVAR(task,TaskStore) call ["setTaskStatus", [_taskID, "active"]];
};
};
case "decline": {
if (!_isDispatchOrder) then {
EGVAR(task,TaskStore) call ["releaseTaskOwnership", [_taskID]];
EGVAR(task,TaskStore) call ["setTaskStatus", [_taskID, "available"]];
};
};
};
if (_result getOrDefault ["success", false]) exitWith { _result };
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
if (_persistenceService isEqualTo createHashMap) exitWith {
_result set ["message", "CAD extension state is unavailable."];
_result
};
private _patch = switch (_transition) do {
case "decline": {
createHashMapFromArray [
["state", "declined"],
["declinedAt", serverTime],
["declinedByUid", _requesterUid]
]
};
default {
createHashMapFromArray [
["state", "acknowledged"],
["acknowledgedAt", serverTime],
["acknowledgedByUid", _requesterUid]
]
};
};
private _transitionResult = switch (_transition) do {
case "decline": { _persistenceService call ["declineAssignment", [_taskID, _patch]] };
default { _persistenceService call ["acknowledgeAssignment", [_taskID, _patch]] };
};
if !(_transitionResult getOrDefault ["success", false]) exitWith {
_result set ["message", switch (_transition) do {
case "decline": { "CAD extension rejected the decline." };
default { "CAD extension rejected the acknowledgement." };
}];
_result
};
private _transitionData = +(_transitionResult getOrDefault ["data", createHashMap]);
_assignment = +(_transitionData getOrDefault ["assignment", createHashMap]);
if (_assignment isEqualTo createHashMap) exitWith {
_result set ["message", "CAD extension returned an invalid assignment."];
_result
};
private _activityEntry = +(_transitionData getOrDefault ["activity", createHashMap]);
if (_activityEntry isNotEqualTo createHashMap) then {
if (_isDispatchOrder) then {
_activityEntry set ["type", format ["dispatch_order_%1", _transition]];
_activityEntry set ["message", format ["%1 %2d %3.", _requesterUid, _transition, _taskID]];
};
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
_activityRepository call ["appendEntry", [_activityEntry]];
};
_result set ["success", true];
_result set ["message", switch (_transition) do {
case "decline": { [_transitionData getOrDefault ["message", "Task declined and returned to the contract board."], "Dispatch order declined and returned to the dispatch board."] select _isDispatchOrder };
default { [_transitionData getOrDefault ["message", "Task acknowledged."], "Dispatch order acknowledged."] select _isDispatchOrder };
}];
_result set ["assignment", _assignment];
_result set ["isDispatchOrder", _isDispatchOrder];
_result
}],
["acknowledgeTask", compileFinal {
_self call ["applyAssignmentTransition", [_this # 0, _this # 1, "acknowledge"]]
}],
["declineTask", compileFinal {
_self call ["applyAssignmentTransition", [_this # 0, _this # 1, "decline"]]
}]
];
createHashMapObject [GVAR(AssignmentRepositoryBaseClass)]