Refactor task store into modular services
- Add task chain prerequisite parsing and startup gating - Split lifecycle, catalog, entity, participant, and reward logic into service objects - Update task modules and docs for chained task flow
This commit is contained in:
parent
08200488de
commit
12dc262136
@ -34,6 +34,7 @@ class CfgVehicles {
|
||||
typeName = "STRING";
|
||||
// defaultValue = """";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_Attack)
|
||||
class LimitFail: Edit {
|
||||
property = "FORGE_Module_Attack_LimitFail";
|
||||
displayName = "Fail Limit";
|
||||
@ -292,6 +293,7 @@ class CfgVehicles {
|
||||
tooltip = "Unique identifier for this task";
|
||||
typeName = "STRING";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_Defend)
|
||||
class DefenseZone: Edit {
|
||||
property = "FORGE_Module_Defend_DefenseZone";
|
||||
displayName = "Defense Zone Marker";
|
||||
@ -417,6 +419,7 @@ class CfgVehicles {
|
||||
typeName = "STRING";
|
||||
// defaultValue = """";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_Defuse)
|
||||
class LimitFail: Edit {
|
||||
property = "FORGE_Module_Defuse_LimitFail";
|
||||
displayName = "Fail Limit";
|
||||
@ -529,6 +532,7 @@ class CfgVehicles {
|
||||
typeName = "STRING";
|
||||
// defaultValue = """";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_Destroy)
|
||||
class LimitFail: Edit {
|
||||
property = "FORGE_Module_Destroy_LimitFail";
|
||||
displayName = "Fail Limit";
|
||||
@ -641,6 +645,7 @@ class CfgVehicles {
|
||||
typeName = "STRING";
|
||||
// defaultValue = """";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_Hostage)
|
||||
class LimitFail: Edit {
|
||||
property = "FORGE_Module_Hostage_LimitFail";
|
||||
displayName = "Fail Limit";
|
||||
@ -789,6 +794,7 @@ class CfgVehicles {
|
||||
tooltip = "Unique identifier for this task";
|
||||
typeName = "STRING";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_Delivery)
|
||||
class DeliveryZone: Edit {
|
||||
property = "FORGE_Module_Delivery_DeliveryZone";
|
||||
displayName = "Delivery Zone Marker";
|
||||
@ -942,6 +948,7 @@ class CfgVehicles {
|
||||
typeName = "STRING";
|
||||
// defaultValue = """";
|
||||
};
|
||||
TASK_CHAIN_ATTRIBUTES(FORGE_Module_HVT)
|
||||
class LimitFail: Edit {
|
||||
property = "FORGE_Module_HVT_LimitFail";
|
||||
displayName = "Fail Limit";
|
||||
|
||||
@ -19,6 +19,7 @@ PREP(initTaskStore);
|
||||
PREP_SUBDIR(generators,attackMissionGenerator);
|
||||
|
||||
PREP_SUBDIR(helpers,handleTaskRewards);
|
||||
PREP_SUBDIR(helpers,parseTaskChainAttributes);
|
||||
PREP_SUBDIR(helpers,parseRewards);
|
||||
PREP_SUBDIR(helpers,spawnEnemyWave);
|
||||
PREP_SUBDIR(helpers,startTask);
|
||||
@ -37,6 +38,12 @@ PREP_SUBDIR(modules,protectedModule);
|
||||
PREP_SUBDIR(modules,shootersModule);
|
||||
|
||||
PREP_SUBDIR(objects,TaskInstanceBaseClass);
|
||||
PREP_SUBDIR(objects,TaskStateGateway);
|
||||
PREP_SUBDIR(objects,TaskLifecycleReporter);
|
||||
PREP_SUBDIR(objects,TaskCatalogStore);
|
||||
PREP_SUBDIR(objects,TaskEntityRegistry);
|
||||
PREP_SUBDIR(objects,TaskParticipantTracker);
|
||||
PREP_SUBDIR(objects,TaskRewardService);
|
||||
PREP_SUBDIR(objects,EntityControllerBaseClass);
|
||||
PREP_SUBDIR(objects,AttackTaskBaseClass);
|
||||
PREP_SUBDIR(objects,HostageTaskBaseClass);
|
||||
|
||||
@ -8,6 +8,12 @@ private _category = [QUOTE(MOD_NAME), LLSTRING(displayName)];
|
||||
|
||||
#include "initSettings.inc.sqf"
|
||||
|
||||
[] call FUNC(TaskStateGateway);
|
||||
[] call FUNC(TaskLifecycleReporter);
|
||||
[] call FUNC(TaskCatalogStore);
|
||||
[] call FUNC(TaskEntityRegistry);
|
||||
[] call FUNC(TaskParticipantTracker);
|
||||
[] call FUNC(TaskRewardService);
|
||||
[] call FUNC(TaskInstanceBaseClass);
|
||||
[] call FUNC(EntityControllerBaseClass);
|
||||
[] call FUNC(AttackTaskBaseClass);
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
params [["_taskType", "", [""]], ["_args", [], [[]]], ["_minRating", 0, [0]], ["_requesterUid", "", [""]]];
|
||||
|
||||
private _taskID = "";
|
||||
private _shouldStartTaskLogic = true;
|
||||
|
||||
if (_minRating > 0) then {
|
||||
if (_requesterUid isEqualTo "") then {
|
||||
@ -71,9 +72,24 @@ if (_taskID isNotEqualTo "") then {
|
||||
]] call EFUNC(common,log);
|
||||
};
|
||||
|
||||
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "available"]];
|
||||
private _initialStatus = GVAR(TaskStore) call ["resolveInitialTaskStatus", [_taskID, _catalogEntry]];
|
||||
GVAR(TaskStore) call ["setTaskStatus", [_taskID, _initialStatus]];
|
||||
if (_initialStatus isEqualTo "locked") then {
|
||||
["INFO", format ["Task %1 is waiting for chained prerequisites before task logic starts.", _taskID]] call EFUNC(common,log);
|
||||
waitUntil {
|
||||
sleep 2;
|
||||
private _status = GVAR(TaskStore) call ["getTaskStatus", [_taskID]];
|
||||
_status isNotEqualTo "locked"
|
||||
};
|
||||
if ((GVAR(TaskStore) call ["getTaskStatus", [_taskID]]) isEqualTo "") then {
|
||||
_shouldStartTaskLogic = false;
|
||||
["WARNING", format ["Task %1 was cleared before its chained prerequisites unlocked.", _taskID]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if !(_shouldStartTaskLogic) exitWith {};
|
||||
|
||||
switch (_taskType) do {
|
||||
case "attack": {
|
||||
private _thread = _args spawn FUNC(attack);
|
||||
|
||||
@ -24,600 +24,119 @@
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskStore) = createHashMapObject [[
|
||||
["#type", "TaskStore"],
|
||||
["#create", compileFinal {
|
||||
_self set ["participantRegistry", createHashMap];
|
||||
_self set ["taskLifecycleRegistry", createHashMap];
|
||||
_self set ["taskEntityRegistries", createHashMapFromArray [
|
||||
["cargo", createHashMap],
|
||||
["hostages", createHashMap],
|
||||
["hvts", createHashMap],
|
||||
["ieds", createHashMap],
|
||||
["entities", createHashMap],
|
||||
["shooters", createHashMap],
|
||||
["targets", createHashMap]
|
||||
]];
|
||||
|
||||
}],
|
||||
["#create", compileFinal {}],
|
||||
["resetMissionState", compileFinal {
|
||||
_self set ["participantRegistry", createHashMap];
|
||||
_self set ["taskLifecycleRegistry", createHashMap];
|
||||
_self set ["taskEntityRegistries", createHashMapFromArray [
|
||||
["cargo", createHashMap],
|
||||
["hostages", createHashMap],
|
||||
["hvts", createHashMap],
|
||||
["ieds", createHashMap],
|
||||
["entities", createHashMap],
|
||||
["shooters", createHashMap],
|
||||
["targets", createHashMap]
|
||||
]];
|
||||
GVAR(TaskLifecycleReporter) call ["resetRuntimeState", []];
|
||||
GVAR(TaskCatalogStore) call ["resetRuntimeState", []];
|
||||
GVAR(TaskEntityRegistry) call ["resetRuntimeState", []];
|
||||
GVAR(TaskParticipantTracker) call ["resetRuntimeState", []];
|
||||
|
||||
["task:reset", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if (
|
||||
!_isSuccess
|
||||
|| { !(_result isEqualType "") }
|
||||
|| { (_result find "Error:") == 0 }
|
||||
) exitWith {
|
||||
["WARNING", "Failed to reset task backend state during task store initialization."] call EFUNC(common,log);
|
||||
false
|
||||
};
|
||||
|
||||
["INFO", "Task backend state reset for mission lifecycle."] call EFUNC(common,log);
|
||||
true
|
||||
}],
|
||||
["callTaskStateEnvelope", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||
|
||||
private _envelope = createHashMapFromArray [
|
||||
["success", false],
|
||||
["error", ""]
|
||||
];
|
||||
|
||||
if (_function isEqualTo "") exitWith { _envelope };
|
||||
|
||||
[_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !_isSuccess exitWith {
|
||||
_envelope set ["error", format ["Task backend call '%1' failed.", _function]];
|
||||
_envelope
|
||||
};
|
||||
if !(_result isEqualType "") exitWith {
|
||||
_envelope set ["error", format ["Task backend call '%1' returned an invalid response.", _function]];
|
||||
_envelope
|
||||
};
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Task extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
|
||||
_envelope set ["error", _result select [7]];
|
||||
_envelope
|
||||
};
|
||||
|
||||
_envelope set ["success", true];
|
||||
if (_result isNotEqualTo "") then {
|
||||
_envelope set ["data", fromJSON _result];
|
||||
};
|
||||
|
||||
_envelope
|
||||
}],
|
||||
["callTaskState", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]], ["_fallback", nil]];
|
||||
|
||||
private _envelope = _self call ["callTaskStateEnvelope", [_function, _arguments]];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith { _fallback };
|
||||
|
||||
_envelope getOrDefault ["data", _fallback]
|
||||
GVAR(TaskStateGateway) call ["reset", []]
|
||||
}],
|
||||
["bindTaskOwnership", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["requesterUid", _requesterUid],
|
||||
["orgID", "default"],
|
||||
["message", ""]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith {
|
||||
_result set ["message", "Missing task ID."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = "default";
|
||||
|
||||
if (_requesterUid isNotEqualTo "") then {
|
||||
private _actor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
_orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||
};
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
private _envelope = _self call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:ownership:bind",
|
||||
[_taskID, toJSON _context]
|
||||
]
|
||||
];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Failed to bind task ownership."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _bindResult = _envelope getOrDefault ["data", createHashMap];
|
||||
_result set ["success", true];
|
||||
_result set ["message", _bindResult getOrDefault [
|
||||
"message",
|
||||
["No requester UID provided. Bound task to default organization.", "Task ownership updated."] select (_requesterUid isNotEqualTo "")
|
||||
]];
|
||||
_result set ["orgID", _bindResult getOrDefault ["orgId", _orgID]];
|
||||
_result
|
||||
GVAR(TaskCatalogStore) call ["bindTaskOwnership", [_taskID, _requesterUid]]
|
||||
}],
|
||||
["releaseTaskOwnership", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _envelope = _self call ["callTaskStateEnvelope", ["task:ownership:release", [_taskID]]];
|
||||
_envelope getOrDefault ["success", false]
|
||||
GVAR(TaskCatalogStore) call ["releaseTaskOwnership", [_taskID]]
|
||||
}],
|
||||
["buildTaskLifecycleEventPayload", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_status", "", [""]], ["_extra", createHashMap]];
|
||||
|
||||
if !(_extra isEqualType createHashMap) then {
|
||||
_extra = createHashMap;
|
||||
};
|
||||
|
||||
private _catalogEntry = _self call ["getTaskCatalogEntry", [_taskID]];
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
private _lifecycle = +(_lifecycleRegistry getOrDefault [_taskID, createHashMap]);
|
||||
private _startedAt = _lifecycle getOrDefault ["startedAt", -1];
|
||||
private _finishedAt = _lifecycle getOrDefault ["finishedAt", -1];
|
||||
|
||||
createHashMapFromArray [
|
||||
["taskID", _taskID],
|
||||
["taskType", _catalogEntry getOrDefault ["type", ""]],
|
||||
["title", _catalogEntry getOrDefault ["title", _taskID]],
|
||||
["description", _catalogEntry getOrDefault ["description", ""]],
|
||||
["position", +(_catalogEntry getOrDefault ["position", []])],
|
||||
["status", _status],
|
||||
["source", _catalogEntry getOrDefault ["source", "task"]],
|
||||
["requesterUid", _catalogEntry getOrDefault ["requesterUid", ""]],
|
||||
["orgID", _catalogEntry getOrDefault ["orgID", "default"]],
|
||||
["startedAt", _startedAt],
|
||||
["finishedAt", _finishedAt],
|
||||
["duration", if (_startedAt >= 0 && { _finishedAt >= 0 }) then { _finishedAt - _startedAt } else { -1 }],
|
||||
["failureReason", _extra getOrDefault ["failureReason", ""]],
|
||||
["participants", _self call ["getTaskParticipantUids", [_taskID]]],
|
||||
["rewardData", +(_extra getOrDefault ["rewardData", createHashMap])],
|
||||
["resultSnapshot", +(_extra getOrDefault ["resultSnapshot", createHashMap])],
|
||||
["catalogEntry", +_catalogEntry]
|
||||
]
|
||||
GVAR(TaskLifecycleReporter) call ["buildTaskLifecycleEventPayload", _this]
|
||||
}],
|
||||
["emitTaskLifecycleEvent", compileFinal {
|
||||
params [["_eventName", "", [""]], ["_taskID", "", [""]], ["_status", "", [""]], ["_extra", createHashMap]];
|
||||
|
||||
if (_eventName isEqualTo "" || { _taskID isEqualTo "" }) exitWith { createHashMap };
|
||||
if (isNil QEGVAR(common,EventBus)) exitWith { createHashMap };
|
||||
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
_eventName,
|
||||
_self call ["buildTaskLifecycleEventPayload", [_taskID, _status, _extra]],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]]
|
||||
GVAR(TaskLifecycleReporter) call ["emitTaskLifecycleEvent", _this]
|
||||
}],
|
||||
["normalizePrerequisiteTaskIds", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["normalizePrerequisiteTaskIds", _this]
|
||||
}],
|
||||
["getTaskPrerequisites", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["getTaskPrerequisites", _this]
|
||||
}],
|
||||
["isTaskCompleted", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["isTaskCompleted", _this]
|
||||
}],
|
||||
["areTaskPrerequisitesSatisfied", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["areTaskPrerequisitesSatisfied", _this]
|
||||
}],
|
||||
["resolveInitialTaskStatus", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["resolveInitialTaskStatus", _this]
|
||||
}],
|
||||
["markTaskCompleted", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["markTaskCompleted", _this]
|
||||
}],
|
||||
["unlockDependentTasks", compileFinal {
|
||||
GVAR(TaskCatalogStore) call ["unlockDependentTasks", _this]
|
||||
}],
|
||||
["registerTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _entry isEqualTo createHashMap }) exitWith { false };
|
||||
|
||||
private _envelope = _self call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:catalog:upsert",
|
||||
[_taskID, toJSON _entry]
|
||||
]
|
||||
];
|
||||
private _registered = _envelope getOrDefault ["success", false];
|
||||
|
||||
if (_registered) then {
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
private _lifecycle = +(_lifecycleRegistry getOrDefault [_taskID, createHashMap]);
|
||||
_lifecycle set ["createdAt", serverTime];
|
||||
_lifecycleRegistry set [_taskID, _lifecycle];
|
||||
_self set ["taskLifecycleRegistry", _lifecycleRegistry];
|
||||
|
||||
_self call ["emitTaskLifecycleEvent", ["task.created", _taskID, "created", createHashMap]];
|
||||
};
|
||||
|
||||
_registered
|
||||
GVAR(TaskCatalogStore) call ["registerTaskCatalogEntry", _this]
|
||||
}],
|
||||
["getActiveTaskCatalog", compileFinal {
|
||||
private _entries = _self call ["callTaskState", ["task:catalog:active", [], []]];
|
||||
if !(_entries isEqualType []) exitWith { [] };
|
||||
|
||||
_entries
|
||||
GVAR(TaskCatalogStore) call ["getActiveTaskCatalog", _this]
|
||||
}],
|
||||
["hasTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _entry = _self call ["callTaskState", ["task:catalog:get", [_taskID], objNull]];
|
||||
_entry isEqualType createHashMap
|
||||
GVAR(TaskCatalogStore) call ["hasTaskCatalogEntry", _this]
|
||||
}],
|
||||
["getTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
[(_self call ["callTaskState", ["task:catalog:get", [_taskID], createHashMap]])] params [["_entry", createHashMap, [createHashMap]]];
|
||||
if !(_entry isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_entry
|
||||
GVAR(TaskCatalogStore) call ["getTaskCatalogEntry", _this]
|
||||
}],
|
||||
["isTaskAccepted", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
[(_self call ["getTaskCatalogEntry", [_taskID]])] params [["_entry", createHashMap, [createHashMap]]];
|
||||
if (_entry isEqualTo createHashMap) exitWith { false };
|
||||
|
||||
[(_entry getOrDefault ["accepted", false])] params [["_accepted", false, [false]]];
|
||||
[(_entry getOrDefault ["requesterUid", ""])] params [["_requesterUid", "", [""]]];
|
||||
|
||||
_accepted || { _requesterUid isNotEqualTo "" }
|
||||
GVAR(TaskCatalogStore) call ["isTaskAccepted", _this]
|
||||
}],
|
||||
["acceptTask", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Unable to accept task."],
|
||||
["entry", createHashMap]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "" || { _requesterUid isEqualTo "" }) exitWith {
|
||||
_result set ["message", "Missing task ID or requester UID."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _actor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
private _envelope = _self call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:ownership:accept",
|
||||
[_taskID, toJSON _context]
|
||||
]
|
||||
];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Unable to accept task."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _acceptResult = _envelope getOrDefault ["data", createHashMap];
|
||||
private _entry = _acceptResult getOrDefault ["entry", createHashMap];
|
||||
if !(_entry isEqualType createHashMap) then {
|
||||
_entry = createHashMap;
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", _acceptResult getOrDefault ["message", "Task accepted."]];
|
||||
_result set ["entry", _entry];
|
||||
_result
|
||||
GVAR(TaskCatalogStore) call ["acceptTask", _this]
|
||||
}],
|
||||
["setTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_status", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
|
||||
|
||||
private _envelope = _self call ["callTaskStateEnvelope", ["task:status:set", [_taskID, _status]]];
|
||||
private _statusResult = _envelope getOrDefault ["success", false];
|
||||
|
||||
if (_statusResult) then {
|
||||
private _normalizedStatus = toLowerANSI _status;
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
private _lifecycle = +(_lifecycleRegistry getOrDefault [_taskID, createHashMap]);
|
||||
private _eventName = "";
|
||||
|
||||
switch (_normalizedStatus) do {
|
||||
case "active": {
|
||||
_lifecycle set ["startedAt", serverTime];
|
||||
_eventName = "task.started";
|
||||
};
|
||||
case "succeeded": {
|
||||
_lifecycle set ["finishedAt", serverTime];
|
||||
_eventName = "task.completed";
|
||||
};
|
||||
case "failed": {
|
||||
_lifecycle set ["finishedAt", serverTime];
|
||||
_eventName = "task.failed";
|
||||
};
|
||||
};
|
||||
|
||||
_lifecycleRegistry set [_taskID, _lifecycle];
|
||||
_self set ["taskLifecycleRegistry", _lifecycleRegistry];
|
||||
|
||||
if (_eventName isNotEqualTo "") then {
|
||||
_self call ["emitTaskLifecycleEvent", [_eventName, _taskID, _normalizedStatus, createHashMap]];
|
||||
};
|
||||
};
|
||||
|
||||
_statusResult
|
||||
GVAR(TaskCatalogStore) call ["setTaskStatus", _this]
|
||||
}],
|
||||
["getTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { "" };
|
||||
|
||||
private _status = _self call ["callTaskState", ["task:status:get", [_taskID], ""]];
|
||||
if !(_status isEqualType "") exitWith { "" };
|
||||
|
||||
_status
|
||||
GVAR(TaskCatalogStore) call ["getTaskStatus", _this]
|
||||
}],
|
||||
["clearTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
[(_self call ["callTaskState", ["task:status:clear", [_taskID], false]])] params [["_statusResult", false, [false]]];
|
||||
|
||||
_statusResult
|
||||
GVAR(TaskCatalogStore) call ["clearTaskStatus", _this]
|
||||
}],
|
||||
["registerTaskEntity", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_taskID", "", [""]], ["_entity", objNull, [objNull]]];
|
||||
|
||||
if (_registryKey isEqualTo "" || { _taskID isEqualTo "" } || { isNull _entity }) exitWith { false };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
private _registry = +(_taskEntityRegistries getOrDefault [_registryKey, createHashMap]);
|
||||
private _entities = +(_registry getOrDefault [_taskID, []]);
|
||||
_entities pushBackUnique _entity;
|
||||
_registry set [_taskID, _entities];
|
||||
_taskEntityRegistries set [_registryKey, _registry];
|
||||
_self set ["taskEntityRegistries", _taskEntityRegistries];
|
||||
|
||||
true
|
||||
GVAR(TaskEntityRegistry) call ["registerTaskEntity", _this]
|
||||
}],
|
||||
["getTaskEntities", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_taskID", "", [""]]];
|
||||
|
||||
if (_registryKey isEqualTo "" || { _taskID isEqualTo "" }) exitWith { [] };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
private _registry = _taskEntityRegistries getOrDefault [_registryKey, createHashMap];
|
||||
|
||||
+(_registry getOrDefault [_taskID, []])
|
||||
GVAR(TaskEntityRegistry) call ["getTaskEntities", _this]
|
||||
}],
|
||||
["findTaskEntityOwner", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_entity", objNull, [objNull]]];
|
||||
|
||||
if (_registryKey isEqualTo "" || { isNull _entity }) exitWith { "" };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
private _registry = _taskEntityRegistries getOrDefault [_registryKey, createHashMap];
|
||||
private _resolvedTaskID = "";
|
||||
|
||||
{
|
||||
private _taskID = _x;
|
||||
private _entities = _y;
|
||||
|
||||
if (_entity in _entities) exitWith {
|
||||
_resolvedTaskID = _taskID;
|
||||
};
|
||||
|
||||
private _matchingEntity = _entities select {
|
||||
!isNull _x
|
||||
&& { (typeOf _x) isEqualTo (typeOf _entity) }
|
||||
&& { _x distance _entity < 1 }
|
||||
};
|
||||
if (_matchingEntity isNotEqualTo []) exitWith {
|
||||
_resolvedTaskID = _taskID;
|
||||
};
|
||||
} forEach _registry;
|
||||
|
||||
_resolvedTaskID
|
||||
GVAR(TaskEntityRegistry) call ["findTaskEntityOwner", _this]
|
||||
}],
|
||||
["clearTaskEntities", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
|
||||
{
|
||||
private _registry = +_y;
|
||||
_registry deleteAt _taskID;
|
||||
_taskEntityRegistries set [_x, _registry];
|
||||
} forEach _taskEntityRegistries;
|
||||
|
||||
_self set ["taskEntityRegistries", _taskEntityRegistries];
|
||||
true
|
||||
GVAR(TaskEntityRegistry) call ["clearTaskEntities", _this]
|
||||
}],
|
||||
["trackParticipants", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entities", [], [[]]], ["_marker", "", [""]], ["_radius", 300, [0]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
|
||||
private _activePlayers = allPlayers select {
|
||||
alive _x
|
||||
&& { side group _x isEqualTo west }
|
||||
};
|
||||
|
||||
if (_marker isNotEqualTo "" && { markerShape _marker in ["RECTANGLE", "ELLIPSE"] }) then {
|
||||
{
|
||||
private _uid = getPlayerUID _x;
|
||||
if (_uid isNotEqualTo "" && { _x inArea _marker }) then {
|
||||
if !(_uid in _participantSnapshots) then {
|
||||
_participantSnapshots set [_uid, createHashMapFromArray [
|
||||
["startRating", rating _x]
|
||||
]];
|
||||
};
|
||||
};
|
||||
} forEach _activePlayers;
|
||||
};
|
||||
|
||||
if (_radius > 0 && { _entities isNotEqualTo [] }) then {
|
||||
{
|
||||
private _entity = _x;
|
||||
if (isNull _entity) then { continue; };
|
||||
|
||||
{
|
||||
private _uid = getPlayerUID _x;
|
||||
if (_uid isNotEqualTo "" && { (_x distance2D _entity) <= _radius }) then {
|
||||
if !(_uid in _participantSnapshots) then {
|
||||
_participantSnapshots set [_uid, createHashMapFromArray [
|
||||
["startRating", rating _x]
|
||||
]];
|
||||
};
|
||||
};
|
||||
} forEach _activePlayers;
|
||||
} forEach _entities;
|
||||
};
|
||||
|
||||
_participantRegistry set [_taskID, _participantSnapshots];
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
|
||||
_participantSnapshots
|
||||
GVAR(TaskParticipantTracker) call ["trackParticipants", _this]
|
||||
}],
|
||||
["getTaskParticipants", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
+(_participantRegistry getOrDefault [_taskID, createHashMap])
|
||||
GVAR(TaskParticipantTracker) call ["getTaskParticipants", _this]
|
||||
}],
|
||||
["getTaskParticipantUids", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { [] };
|
||||
|
||||
keys (_self call ["getTaskParticipants", [_taskID]])
|
||||
GVAR(TaskParticipantTracker) call ["getTaskParticipantUids", _this]
|
||||
}],
|
||||
["resolveRewardContext", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["requesterUid", ""],
|
||||
["orgID", ""],
|
||||
["memberUids", []]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { _result };
|
||||
|
||||
private _rewardState = _self call ["callTaskState", ["task:ownership:reward_context", [_taskID], createHashMap]];
|
||||
if (_rewardState isEqualTo createHashMap) exitWith { _result };
|
||||
|
||||
private _requesterUid = _rewardState getOrDefault ["requesterUid", ""];
|
||||
private _resolvedOrgID = _rewardState getOrDefault ["orgId", ""];
|
||||
if (_resolvedOrgID isEqualTo "") exitWith { _result };
|
||||
|
||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_resolvedOrgID]];
|
||||
private _memberUids = [];
|
||||
if (_org isNotEqualTo createHashMap) then {
|
||||
private _members = _org getOrDefault ["members", createHashMap];
|
||||
if (_members isEqualType createHashMap) then {
|
||||
_memberUids = keys _members;
|
||||
};
|
||||
if (_requesterUid isNotEqualTo "" && { !(_requesterUid in _memberUids) }) then {
|
||||
_memberUids pushBack _requesterUid;
|
||||
};
|
||||
};
|
||||
|
||||
_result set ["requesterUid", _requesterUid];
|
||||
_result set ["orgID", _resolvedOrgID];
|
||||
_result set ["memberUids", _memberUids];
|
||||
_result
|
||||
GVAR(TaskRewardService) call ["resolveRewardContext", _this]
|
||||
}],
|
||||
["incrementDefuseCount", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { 0 };
|
||||
|
||||
["task:defuse:increment", [_taskID]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
|
||||
if !_isSuccess exitWith { 0 };
|
||||
if !(_result isEqualType "") exitWith { 0 };
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Task extension call 'task:defuse:increment' failed: %1", _result]] call EFUNC(common,log);
|
||||
0
|
||||
};
|
||||
|
||||
parseNumber _result
|
||||
[GVAR(TaskStateGateway) call ["callTaskState", ["task:defuse:increment", [_taskID], 0]]] params [["_count", 0, [0]]];
|
||||
_count
|
||||
}],
|
||||
["getDefuseCount", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { 0 };
|
||||
|
||||
["task:defuse:get", [_taskID]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !_isSuccess exitWith { 0 };
|
||||
if !(_result isEqualType "") exitWith { 0 };
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Task extension call 'task:defuse:get' failed: %1", _result]] call EFUNC(common,log);
|
||||
0
|
||||
};
|
||||
|
||||
parseNumber _result
|
||||
[GVAR(TaskStateGateway) call ["callTaskState", ["task:defuse:get", [_taskID], 0]]] params [["_count", 0, [0]]];
|
||||
_count
|
||||
}],
|
||||
["notifyParticipants", compileFinal {
|
||||
params [
|
||||
["_taskID", "", [""]],
|
||||
["_type", "info", [""]],
|
||||
["_title", "Tasks", [""]],
|
||||
["_message", "", [""]]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "" || { _message isEqualTo "" }) exitWith { false };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
|
||||
if (_participantSnapshots isEqualTo createHashMap) exitWith { false };
|
||||
|
||||
private _participantUids = keys _participantSnapshots;
|
||||
if (_participantUids isEqualTo []) exitWith { false };
|
||||
|
||||
if (isNil QEGVAR(common,EventBus)) exitWith {
|
||||
{
|
||||
private _player = [_x] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
[CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent);
|
||||
} forEach _participantUids;
|
||||
true
|
||||
};
|
||||
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
"task.notification.requested",
|
||||
createHashMapFromArray [
|
||||
["taskID", _taskID],
|
||||
["notificationType", _type],
|
||||
["title", _title],
|
||||
["message", _message],
|
||||
["participantUids", _participantUids]
|
||||
],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]];
|
||||
|
||||
true
|
||||
GVAR(TaskParticipantTracker) call ["notifyParticipants", _this]
|
||||
}],
|
||||
["clearTask", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
@ -626,246 +145,14 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
|
||||
_self call ["emitTaskLifecycleEvent", ["task.cleared", _taskID, "cleared", createHashMap]];
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
_participantRegistry deleteAt _taskID;
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
_lifecycleRegistry deleteAt _taskID;
|
||||
_self set ["taskLifecycleRegistry", _lifecycleRegistry];
|
||||
|
||||
_self call ["callTaskState", ["task:clear", [_taskID], false]];
|
||||
GVAR(TaskLifecycleReporter) call ["clearTaskLifecycle", [_taskID]];
|
||||
GVAR(TaskParticipantTracker) call ["clearTaskParticipants", [_taskID]];
|
||||
GVAR(TaskStateGateway) call ["callTaskState", ["task:clear", [_taskID], false]];
|
||||
_self call ["clearTaskEntities", [_taskID]];
|
||||
true
|
||||
}],
|
||||
["applyRatingOutcome", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_delta", 0, [0]]];
|
||||
|
||||
private _emitRatingEvent = {
|
||||
params [["_eventName", "", [""]], ["_payload", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_eventName isEqualTo "" || { isNil QEGVAR(common,EventBus) }) exitWith { createHashMap };
|
||||
|
||||
private _eventPayload = +_payload;
|
||||
_eventPayload set ["taskID", _taskID];
|
||||
_eventPayload set ["ratingDelta", _delta];
|
||||
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
_eventName,
|
||||
_eventPayload,
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]]
|
||||
};
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["participantUids", []],
|
||||
["orgIds", []],
|
||||
["contributions", createHashMap],
|
||||
["success", true],
|
||||
["mutationFailures", []],
|
||||
["persistenceFailures", []],
|
||||
["message", ""]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "" || { _delta isEqualTo 0 }) exitWith { _result };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
|
||||
if (_participantSnapshots isEqualTo createHashMap) exitWith { _result };
|
||||
|
||||
private _rewardContext = _self call ["resolveRewardContext", [_taskID]];
|
||||
private _participantUids = keys _participantSnapshots;
|
||||
if (_participantUids isEqualTo [] && { _delta > 0 }) then {
|
||||
private _requesterUid = _rewardContext getOrDefault ["requesterUid", ""];
|
||||
if (_requesterUid isNotEqualTo "") then {
|
||||
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||
if (!isNull _requesterPlayer) then {
|
||||
_participantUids pushBack _requesterUid;
|
||||
_participantSnapshots set [_requesterUid, createHashMapFromArray [
|
||||
["startRating", rating _requesterPlayer]
|
||||
]];
|
||||
_participantRegistry set [_taskID, _participantSnapshots];
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
["WARNING", format ["Task %1 had no tracked participants at payout time; falling back to requester %2 for personal earnings.", _taskID, _requesterUid]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (_participantUids isEqualTo []) exitWith {
|
||||
_result set ["success", false];
|
||||
_result set ["message", "No task participants were available for rating outcome."];
|
||||
["task.rating.failed", createHashMapFromArray [
|
||||
["participantUids", []],
|
||||
["orgIds", []],
|
||||
["contributions", createHashMap],
|
||||
["mutationFailures", []],
|
||||
["persistenceFailures", []],
|
||||
["message", _result get "message"]
|
||||
]] call _emitRatingEvent;
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgIds = [];
|
||||
private _contributions = createHashMap;
|
||||
private _totalContribution = 0;
|
||||
private _mutationFailures = [];
|
||||
private _persistenceFailures = [];
|
||||
|
||||
if (_delta > 0) then {
|
||||
{
|
||||
private _uid = _x;
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
|
||||
_contributions set [_uid, 1];
|
||||
_totalContribution = _totalContribution + 1;
|
||||
} forEach _participantUids;
|
||||
};
|
||||
|
||||
if (_totalContribution <= 0) exitWith {
|
||||
_result set ["success", false];
|
||||
_result set ["message", "No eligible participant contribution was available for rating outcome."];
|
||||
["task.rating.failed", createHashMapFromArray [
|
||||
["participantUids", +_participantUids],
|
||||
["orgIds", +_orgIds],
|
||||
["contributions", +_contributions],
|
||||
["mutationFailures", []],
|
||||
["persistenceFailures", []],
|
||||
["message", _result get "message"]
|
||||
]] call _emitRatingEvent;
|
||||
_self call ["clearTask", [_taskID]];
|
||||
_result
|
||||
};
|
||||
|
||||
{
|
||||
private _uid = _x;
|
||||
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid, ""]];
|
||||
if (_orgID isNotEqualTo "") then {
|
||||
_orgIds pushBackUnique _orgID;
|
||||
};
|
||||
|
||||
if (_delta > 0) then {
|
||||
private _contribution = _contributions getOrDefault [_uid, 0];
|
||||
if (_contribution <= 0) then { continue; };
|
||||
|
||||
private _account = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||
if (_account isEqualTo createHashMap) then {
|
||||
_account = EGVAR(bank,BankStore) call ["init", [_uid]];
|
||||
};
|
||||
|
||||
if (_account isNotEqualTo createHashMap) then {
|
||||
private _earnings = _account getOrDefault ["earnings", 0];
|
||||
private _earningsDelta = round ((_delta * _contribution) / _totalContribution);
|
||||
if (_earningsDelta <= 0) then { continue; };
|
||||
|
||||
private _patch = EGVAR(bank,BankStore) call [
|
||||
"mset",
|
||||
[
|
||||
_uid,
|
||||
createHashMapFromArray [["earnings", (_earnings + _earningsDelta)]],
|
||||
false
|
||||
]
|
||||
];
|
||||
if !(_patch isEqualType createHashMap) then { continue; };
|
||||
if (_patch isEqualTo createHashMap) then { continue; };
|
||||
|
||||
if (isNil QEGVAR(common,EventBus)) then {
|
||||
EGVAR(bank,BankMessenger) call ["sendAccountSync", [_uid, _patch]];
|
||||
} else {
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
"bank.account.sync.requested",
|
||||
createHashMapFromArray [
|
||||
["uid", _uid],
|
||||
["account", +_patch]
|
||||
],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]];
|
||||
};
|
||||
|
||||
if ((EGVAR(bank,BankStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||
_persistenceFailures pushBackUnique format ["bank:%1", _uid];
|
||||
["ERROR", format ["Task %1 updated bank earnings for %2, but durable save failed.", _taskID, _uid]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
};
|
||||
} forEach _participantUids;
|
||||
|
||||
private _ownerOrgID = _rewardContext getOrDefault ["orgID", ""];
|
||||
if (_ownerOrgID isNotEqualTo "") then {
|
||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_ownerOrgID]];
|
||||
|
||||
if (_org isNotEqualTo createHashMap) then {
|
||||
private _reputation = _org getOrDefault ["reputation", 0];
|
||||
private _nextReputation = round (_reputation + _delta);
|
||||
_org set ["reputation", _nextReputation];
|
||||
private _updatedOrg = EGVAR(org,OrgStore) call [
|
||||
"callHotOrg",
|
||||
[
|
||||
"org:hot:override",
|
||||
[_ownerOrgID, toJSON _org]
|
||||
]
|
||||
];
|
||||
|
||||
if (_updatedOrg isNotEqualTo createHashMap) then {
|
||||
private _patch = createHashMapFromArray [["reputation", _nextReputation]];
|
||||
private _memberUids = _rewardContext getOrDefault ["memberUids", []];
|
||||
if (isNil QEGVAR(common,EventBus)) then {
|
||||
{
|
||||
private _player = [_x] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
[CRPC(org,responseSyncOrg), [_patch], _player] call CFUNC(targetEvent);
|
||||
} forEach _memberUids;
|
||||
} else {
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
"org.sync.requested",
|
||||
createHashMapFromArray [
|
||||
["orgID", _ownerOrgID],
|
||||
["memberUids", +_memberUids],
|
||||
["patch", +_patch]
|
||||
],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]];
|
||||
};
|
||||
|
||||
_orgIds = [_ownerOrgID];
|
||||
if ((EGVAR(org,OrgStore) call ["saveById", [_ownerOrgID]]) isEqualTo createHashMap) then {
|
||||
_persistenceFailures pushBackUnique format ["organization:%1", _ownerOrgID];
|
||||
["ERROR", format ["Task %1 updated reputation for organization %2, but durable save failed.", _taskID, _ownerOrgID]] call EFUNC(common,log);
|
||||
};
|
||||
} else {
|
||||
["ERROR", format ["Failed to update organization %1 reputation for task %2.", _ownerOrgID, _taskID]] call EFUNC(common,log);
|
||||
_mutationFailures pushBackUnique format ["organization:%1", _ownerOrgID];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
_result set ["participantUids", _participantUids];
|
||||
_result set ["orgIds", _orgIds];
|
||||
_result set ["contributions", _contributions];
|
||||
_result set ["success", (_mutationFailures isEqualTo []) && { _persistenceFailures isEqualTo [] }];
|
||||
_result set ["mutationFailures", _mutationFailures];
|
||||
_result set ["persistenceFailures", _persistenceFailures];
|
||||
if (_mutationFailures isNotEqualTo [] || { _persistenceFailures isNotEqualTo [] }) then {
|
||||
private _messageParts = [];
|
||||
if (_mutationFailures isNotEqualTo []) then {
|
||||
_messageParts pushBack format ["mutation failures: %1", _mutationFailures joinString ", "];
|
||||
};
|
||||
if (_persistenceFailures isNotEqualTo []) then {
|
||||
_messageParts pushBack format ["persistence failures: %1", _persistenceFailures joinString ", "];
|
||||
};
|
||||
_result set ["message", _messageParts joinString "; "];
|
||||
};
|
||||
|
||||
private _eventName = ["task.rating.failed", "task.rating.applied"] select (_result getOrDefault ["success", false]);
|
||||
[_eventName, createHashMapFromArray [
|
||||
["participantUids", +(_result getOrDefault ["participantUids", []])],
|
||||
["orgIds", +(_result getOrDefault ["orgIds", []])],
|
||||
["contributions", +(_result getOrDefault ["contributions", createHashMap])],
|
||||
["mutationFailures", +(_result getOrDefault ["mutationFailures", []])],
|
||||
["persistenceFailures", +(_result getOrDefault ["persistenceFailures", []])],
|
||||
["message", _result getOrDefault ["message", ""]]
|
||||
]] call _emitRatingEvent;
|
||||
|
||||
_result
|
||||
GVAR(TaskRewardService) call ["applyRatingOutcome", _this]
|
||||
}]
|
||||
]];
|
||||
|
||||
|
||||
@ -2,7 +2,12 @@
|
||||
|
||||
/*
|
||||
* Author: OpenAI
|
||||
* Parses an Eden module reward-array string into a SQF array.
|
||||
* Parses an Eden module reward string into a SQF array.
|
||||
*
|
||||
* Supports both the preferred comma-separated format:
|
||||
* ItemGPS, FirstAidKit
|
||||
* and the legacy SQF array string format:
|
||||
* ["ItemGPS","FirstAidKit"]
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Raw value <STRING>
|
||||
@ -13,28 +18,35 @@
|
||||
* Parsed reward array <ARRAY>
|
||||
*
|
||||
* Example:
|
||||
* [_logic getVariable ["EquipmentRewards", "[]"], "attack_01", "equipment"] call forge_server_task_fnc_parseRewards;
|
||||
* [_logic getVariable ["EquipmentRewards", ""], "attack_01", "equipment"] call forge_server_task_fnc_parseRewards;
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
params [
|
||||
["_rawValue", "[]", [""]],
|
||||
["_taskLabel", "", [""]],
|
||||
["_rewardKey", "", [""]]
|
||||
];
|
||||
params [["_rawValue", "", [""]], ["_taskLabel", "", [""]], ["_rewardKey", "", [""]]];
|
||||
|
||||
private _trimmed = trim _rawValue;
|
||||
if (_trimmed isEqualTo "") exitWith { [] };
|
||||
if ((_trimmed select [0, 1]) isEqualTo "[") then {
|
||||
private _parsed = parseSimpleArray _trimmed;
|
||||
if (_parsed isEqualType []) exitWith { _parsed };
|
||||
|
||||
private _parsed = parseSimpleArray _trimmed;
|
||||
if (_parsed isEqualType []) exitWith { _parsed };
|
||||
["WARNING", format [
|
||||
"Task module '%1' reward input '%2' is invalid: %3. Expected comma-separated class names like ItemGPS, FirstAidKit or SQF array syntax like [""ItemGPS"",""FirstAidKit""].",
|
||||
_taskLabel,
|
||||
_rewardKey,
|
||||
_rawValue
|
||||
]] call EFUNC(common,log);
|
||||
|
||||
["WARNING", format [
|
||||
"Task module '%1' reward input '%2' is invalid: %3. Expected SQF array syntax like [""ItemGPS"",""FirstAidKit""].",
|
||||
_taskLabel,
|
||||
_rewardKey,
|
||||
_rawValue
|
||||
]] call EFUNC(common,log);
|
||||
[]
|
||||
};
|
||||
|
||||
[]
|
||||
private _parsedRewards = [];
|
||||
{
|
||||
private _reward = trim _x;
|
||||
if (_reward isEqualTo "") then { continue; };
|
||||
|
||||
_parsedRewards pushBackUnique _reward;
|
||||
} forEach (_trimmed splitString ",");
|
||||
|
||||
_parsedRewards
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Reads shared Eden task chain attributes and returns startTask parameter pairs.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Logic <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* Task parameter pairs <ARRAY>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
params [["_logic", objNull, [objNull]]];
|
||||
|
||||
private _prerequisiteRaw = _logic getVariable ["PrerequisiteTaskIds", ""];
|
||||
private _prerequisiteTaskIds = [];
|
||||
|
||||
if (_prerequisiteRaw isEqualType []) then {
|
||||
{
|
||||
if !(_x isEqualType "") then { continue; };
|
||||
if (_x isEqualTo "") then { continue; };
|
||||
_prerequisiteTaskIds pushBackUnique _x;
|
||||
} forEach _prerequisiteRaw;
|
||||
} else {
|
||||
if (_prerequisiteRaw isEqualType "") then {
|
||||
{
|
||||
if (_x isEqualTo "") then { continue; };
|
||||
_prerequisiteTaskIds pushBackUnique _x;
|
||||
} forEach (_prerequisiteRaw splitString ", ");
|
||||
};
|
||||
};
|
||||
|
||||
[
|
||||
["prerequisiteTaskIds", _prerequisiteTaskIds]
|
||||
]
|
||||
@ -24,6 +24,7 @@
|
||||
* Common keys:
|
||||
* "limitFail" <NUMBER> (default: -1)
|
||||
* "limitSuccess" <NUMBER> (default: -1)
|
||||
* "prerequisiteTaskIds" <ARRAY|STRING> (default: []) -- task IDs that must succeed before this task is available
|
||||
* "funds" <NUMBER> (default: 0)
|
||||
* "ratingFail" <NUMBER> (default: 0)
|
||||
* "ratingSuccess" <NUMBER> (default: 0)
|
||||
@ -123,7 +124,17 @@ private _iedTimer = _taskParams getOrDefault ["iedTimer", 0];
|
||||
|
||||
// --- 3. Register catalog entry ---
|
||||
|
||||
private _prerequisiteTaskIds = _taskParams getOrDefault [
|
||||
"prerequisiteTaskIds",
|
||||
_taskParams getOrDefault [
|
||||
"prerequisiteTaskIDs",
|
||||
_taskParams getOrDefault ["requiresTaskIds", []]
|
||||
]
|
||||
];
|
||||
|
||||
GVAR(TaskStore) call ["registerTaskCatalogEntry", [_taskID, createHashMapFromArray [
|
||||
["taskID", _taskID],
|
||||
["taskId", _taskID],
|
||||
["type", _taskType],
|
||||
["title", _title],
|
||||
["description", _description],
|
||||
@ -131,7 +142,8 @@ GVAR(TaskStore) call ["registerTaskCatalogEntry", [_taskID, createHashMapFromArr
|
||||
["accepted", false],
|
||||
["requesterUid", _requesterUid],
|
||||
["orgID", "default"],
|
||||
["source", _source]
|
||||
["source", _source],
|
||||
["prerequisiteTaskIds", _prerequisiteTaskIds]
|
||||
]]];
|
||||
|
||||
// --- 4. Assemble type-specific handler args ---
|
||||
|
||||
@ -41,6 +41,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"attack",
|
||||
@ -51,7 +52,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
createHashMapFromArray [
|
||||
["targets", _syncedEntities]
|
||||
],
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["limitFail", _logic getVariable ["LimitFail", -1]],
|
||||
["limitSuccess", _logic getVariable ["LimitSuccess", -1]],
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
@ -65,7 +66,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -84,6 +84,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"defend",
|
||||
@ -92,7 +93,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
format ["Defend: %1", _taskID],
|
||||
"Hold the defense zone against incoming enemy forces.",
|
||||
createHashMap,
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
["ratingFail", _logic getVariable ["RatingFail", 0]],
|
||||
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
|
||||
@ -109,7 +110,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -58,6 +58,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"defuse",
|
||||
@ -69,7 +70,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["ieds", _iedEntities],
|
||||
["protected", _protectedEntities]
|
||||
],
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["limitFail", _logic getVariable ["LimitFail", -1]],
|
||||
["limitSuccess", _logic getVariable ["LimitSuccess", -1]],
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
@ -83,7 +84,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -51,6 +51,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"delivery",
|
||||
@ -61,7 +62,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
createHashMapFromArray [
|
||||
["cargo", _cargoEntities]
|
||||
],
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["limitFail", _logic getVariable ["LimitFail", -1]],
|
||||
["limitSuccess", _logic getVariable ["LimitSuccess", -1]],
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
@ -76,7 +77,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -41,6 +41,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"destroy",
|
||||
@ -51,7 +52,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
createHashMapFromArray [
|
||||
["targets", _syncedEntities]
|
||||
],
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["limitFail", _logic getVariable ["LimitFail", -1]],
|
||||
["limitSuccess", _logic getVariable ["LimitSuccess", -1]],
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
@ -65,7 +66,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -66,6 +66,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"hostage",
|
||||
@ -77,7 +78,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["hostages", _hostageEntities],
|
||||
["shooters", _shooterEntities]
|
||||
],
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["limitFail", _logic getVariable ["LimitFail", -1]],
|
||||
["limitSuccess", _logic getVariable ["LimitSuccess", -1]],
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
@ -95,7 +96,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -47,6 +47,7 @@ private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "
|
||||
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
|
||||
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
|
||||
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
|
||||
private _taskChainParams = [_logic] call FUNC(parseTaskChainAttributes);
|
||||
|
||||
[
|
||||
"hvt",
|
||||
@ -57,7 +58,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
createHashMapFromArray [
|
||||
["hvts", _syncedEntities]
|
||||
],
|
||||
createHashMapFromArray [
|
||||
createHashMapFromArray ([
|
||||
["limitFail", _logic getVariable ["LimitFail", -1]],
|
||||
["limitSuccess", _logic getVariable ["LimitSuccess", -1]],
|
||||
["funds", _logic getVariable ["CompanyFunds", 0]],
|
||||
@ -73,7 +74,7 @@ private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID,
|
||||
["weapons", _weaponRewards],
|
||||
["vehicles", _vehicleRewards],
|
||||
["special", _specialRewards]
|
||||
]
|
||||
] + _taskChainParams)
|
||||
] call FUNC(startTask);
|
||||
|
||||
deleteVehicle _logic;
|
||||
|
||||
@ -0,0 +1,356 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Catalog/status/chaining store for task metadata.
|
||||
*
|
||||
* TaskStore keeps the public facade used by the rest of the task system. This
|
||||
* object owns catalog persistence calls, active status, acceptance, and chained
|
||||
* task availability.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Task catalog store object <HASHMAP OBJECT>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskCatalogStore) = createHashMapObject [[
|
||||
["#type", "TaskCatalogStore"],
|
||||
["#create", compileFinal {
|
||||
_self call ["resetRuntimeState", []];
|
||||
}],
|
||||
["resetRuntimeState", compileFinal {
|
||||
_self set ["completedTaskRegistry", createHashMap];
|
||||
_self set ["taskDependencyRegistry", createHashMap];
|
||||
true
|
||||
}],
|
||||
["bindTaskOwnership", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["requesterUid", _requesterUid],
|
||||
["orgID", "default"],
|
||||
["message", ""]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith {
|
||||
_result set ["message", "Missing task ID."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = "default";
|
||||
|
||||
if (_requesterUid isNotEqualTo "") then {
|
||||
private _actor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
_orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||
};
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
private _envelope = GVAR(TaskStateGateway) call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:ownership:bind",
|
||||
[_taskID, toJSON _context]
|
||||
]
|
||||
];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Failed to bind task ownership."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _bindResult = _envelope getOrDefault ["data", createHashMap];
|
||||
_result set ["success", true];
|
||||
_result set ["message", _bindResult getOrDefault [
|
||||
"message",
|
||||
["No requester UID provided. Bound task to default organization.", "Task ownership updated."] select (_requesterUid isNotEqualTo "")
|
||||
]];
|
||||
_result set ["orgID", _bindResult getOrDefault ["orgId", _orgID]];
|
||||
_result
|
||||
}],
|
||||
["releaseTaskOwnership", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _envelope = GVAR(TaskStateGateway) call ["callTaskStateEnvelope", ["task:ownership:release", [_taskID]]];
|
||||
_envelope getOrDefault ["success", false]
|
||||
}],
|
||||
["normalizePrerequisiteTaskIds", compileFinal {
|
||||
params [["_value", [], [[], ""]]];
|
||||
|
||||
if (_value isEqualType "") then { _value = [_value]; };
|
||||
if !(_value isEqualType []) exitWith { [] };
|
||||
|
||||
private _taskIDs = [];
|
||||
{
|
||||
if !(_x isEqualType "") then { continue; };
|
||||
if (_x isEqualTo "") then { continue; };
|
||||
_taskIDs pushBackUnique _x;
|
||||
} forEach _value;
|
||||
|
||||
_taskIDs
|
||||
}],
|
||||
["getTaskPrerequisites", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { [] };
|
||||
|
||||
private _dependencyRegistry = _self getOrDefault ["taskDependencyRegistry", createHashMap];
|
||||
+(_dependencyRegistry getOrDefault [_taskID, []])
|
||||
}],
|
||||
["isTaskCompleted", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _completedRegistry = _self getOrDefault ["completedTaskRegistry", createHashMap];
|
||||
if (_completedRegistry getOrDefault [_taskID, false]) exitWith { true };
|
||||
|
||||
(_self call ["getTaskStatus", [_taskID]]) isEqualTo "succeeded"
|
||||
}],
|
||||
["areTaskPrerequisitesSatisfied", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
|
||||
|
||||
private _prerequisites = _self call ["getTaskPrerequisites", [_taskID]];
|
||||
if (_prerequisites isEqualTo [] && { _entry isNotEqualTo createHashMap }) then {
|
||||
_prerequisites = _self call ["normalizePrerequisiteTaskIds", [_entry getOrDefault ["prerequisiteTaskIds", []]]];
|
||||
};
|
||||
if (_prerequisites isEqualTo []) exitWith { true };
|
||||
|
||||
private _satisfied = true;
|
||||
{
|
||||
if !(_self call ["isTaskCompleted", [_x]]) exitWith { _satisfied = false; };
|
||||
} forEach _prerequisites;
|
||||
|
||||
_satisfied
|
||||
}],
|
||||
["resolveInitialTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_self call ["areTaskPrerequisitesSatisfied", [_taskID, _entry]]) exitWith { "available" };
|
||||
|
||||
"locked"
|
||||
}],
|
||||
["markTaskCompleted", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _completedRegistry = _self getOrDefault ["completedTaskRegistry", createHashMap];
|
||||
_completedRegistry set [_taskID, true];
|
||||
_self set ["completedTaskRegistry", _completedRegistry];
|
||||
true
|
||||
}],
|
||||
["unlockDependentTasks", compileFinal {
|
||||
params [["_completedTaskID", "", [""]]];
|
||||
|
||||
private _dependencyRegistry = _self getOrDefault ["taskDependencyRegistry", createHashMap];
|
||||
{
|
||||
private _dependentTaskID = _x;
|
||||
private _prerequisites = _y;
|
||||
|
||||
if !(_completedTaskID in _prerequisites) then { continue; };
|
||||
if ((_self call ["getTaskStatus", [_dependentTaskID]]) isNotEqualTo "locked") then { continue; };
|
||||
if !(_self call ["areTaskPrerequisitesSatisfied", [_dependentTaskID]]) then { continue; };
|
||||
|
||||
_self call ["setTaskStatus", [_dependentTaskID, "available"]];
|
||||
["INFO", format ["Unlocked chained task '%1' after prerequisite '%2' completed.", _dependentTaskID, _completedTaskID]] call EFUNC(common,log);
|
||||
} forEach _dependencyRegistry;
|
||||
|
||||
true
|
||||
}],
|
||||
["registerTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _entry isEqualTo createHashMap }) exitWith { false };
|
||||
|
||||
_entry set ["taskID", _taskID];
|
||||
_entry set ["taskId", _taskID];
|
||||
|
||||
private _prerequisiteTaskIds = _self call ["normalizePrerequisiteTaskIds", [_entry getOrDefault ["prerequisiteTaskIds", []]]];
|
||||
_entry set ["prerequisiteTaskIds", _prerequisiteTaskIds];
|
||||
|
||||
private _dependencyRegistry = _self getOrDefault ["taskDependencyRegistry", createHashMap];
|
||||
if (_prerequisiteTaskIds isEqualTo []) then {
|
||||
_dependencyRegistry deleteAt _taskID;
|
||||
} else {
|
||||
_dependencyRegistry set [_taskID, _prerequisiteTaskIds];
|
||||
};
|
||||
_self set ["taskDependencyRegistry", _dependencyRegistry];
|
||||
|
||||
private _initialStatus = ["available", "locked"] select !(_self call ["areTaskPrerequisitesSatisfied", [_taskID, _entry]]);
|
||||
_entry set ["locked", _initialStatus isEqualTo "locked"];
|
||||
|
||||
private _envelope = GVAR(TaskStateGateway) call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:catalog:upsert",
|
||||
[_taskID, toJSON _entry]
|
||||
]
|
||||
];
|
||||
private _registered = _envelope getOrDefault ["success", false];
|
||||
|
||||
if (_registered) then {
|
||||
GVAR(TaskLifecycleReporter) call ["recordTaskCreated", [_taskID]];
|
||||
GVAR(TaskLifecycleReporter) call ["emitTaskLifecycleEvent", ["task.created", _taskID, "created", createHashMap]];
|
||||
_self call ["setTaskStatus", [_taskID, _initialStatus]];
|
||||
};
|
||||
|
||||
_registered
|
||||
}],
|
||||
["getActiveTaskCatalog", compileFinal {
|
||||
private _entries = GVAR(TaskStateGateway) call ["callTaskState", ["task:catalog:active", [], []]];
|
||||
if !(_entries isEqualType []) exitWith { [] };
|
||||
|
||||
private _visibleEntries = [];
|
||||
{
|
||||
if !(_x isEqualType createHashMap) then { continue; };
|
||||
|
||||
private _taskID = _x getOrDefault ["taskID", _x getOrDefault ["taskId", ""]];
|
||||
if (_taskID isEqualTo "") then { continue; };
|
||||
|
||||
private _status = _self call ["getTaskStatus", [_taskID]];
|
||||
if !(_status in ["available", "assigned", "active"]) then { continue; };
|
||||
if !(_self call ["areTaskPrerequisitesSatisfied", [_taskID, _x]]) then { continue; };
|
||||
|
||||
_visibleEntries pushBack _x;
|
||||
} forEach _entries;
|
||||
|
||||
_visibleEntries
|
||||
}],
|
||||
["hasTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _entry = GVAR(TaskStateGateway) call ["callTaskState", ["task:catalog:get", [_taskID], objNull]];
|
||||
_entry isEqualType createHashMap
|
||||
}],
|
||||
["getTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
[(GVAR(TaskStateGateway) call ["callTaskState", ["task:catalog:get", [_taskID], createHashMap]])] params [["_entry", createHashMap, [createHashMap]]];
|
||||
if !(_entry isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_entry
|
||||
}],
|
||||
["isTaskAccepted", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
[(_self call ["getTaskCatalogEntry", [_taskID]])] params [["_entry", createHashMap, [createHashMap]]];
|
||||
if (_entry isEqualTo createHashMap) exitWith { false };
|
||||
|
||||
[(_entry getOrDefault ["accepted", false])] params [["_accepted", false, [false]]];
|
||||
[(_entry getOrDefault ["requesterUid", ""])] params [["_requesterUid", "", [""]]];
|
||||
|
||||
_accepted || { _requesterUid isNotEqualTo "" }
|
||||
}],
|
||||
["acceptTask", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Unable to accept task."],
|
||||
["entry", createHashMap]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "" || { _requesterUid isEqualTo "" }) exitWith {
|
||||
_result set ["message", "Missing task ID or requester UID."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _actor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
private _envelope = GVAR(TaskStateGateway) call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:ownership:accept",
|
||||
[_taskID, toJSON _context]
|
||||
]
|
||||
];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Unable to accept task."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _acceptResult = _envelope getOrDefault ["data", createHashMap];
|
||||
private _entry = _acceptResult getOrDefault ["entry", createHashMap];
|
||||
if !(_entry isEqualType createHashMap) then { _entry = createHashMap; };
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", _acceptResult getOrDefault ["message", "Task accepted."]];
|
||||
_result set ["entry", _entry];
|
||||
_result
|
||||
}],
|
||||
["setTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_status", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
|
||||
|
||||
private _envelope = GVAR(TaskStateGateway) call ["callTaskStateEnvelope", ["task:status:set", [_taskID, _status]]];
|
||||
private _statusResult = _envelope getOrDefault ["success", false];
|
||||
|
||||
if (_statusResult) then {
|
||||
private _normalizedStatus = toLowerANSI _status;
|
||||
private _eventName = GVAR(TaskLifecycleReporter) call ["recordTaskStatus", [_taskID, _normalizedStatus]];
|
||||
|
||||
if (_eventName isNotEqualTo "") then {
|
||||
GVAR(TaskLifecycleReporter) call ["emitTaskLifecycleEvent", [_eventName, _taskID, _normalizedStatus, createHashMap]];
|
||||
};
|
||||
|
||||
if (_normalizedStatus isEqualTo "succeeded") then {
|
||||
_self call ["markTaskCompleted", [_taskID]];
|
||||
_self call ["unlockDependentTasks", [_taskID]];
|
||||
};
|
||||
};
|
||||
|
||||
_statusResult
|
||||
}],
|
||||
["getTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { "" };
|
||||
|
||||
[(GVAR(TaskStateGateway) call ["callTaskState", ["task:status:get", [_taskID], ""]])] params [["_status", "", [""]]];
|
||||
_status
|
||||
}],
|
||||
["clearTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
[(GVAR(TaskStateGateway) call ["callTaskState", ["task:status:clear", [_taskID], false]])] params [["_statusResult", false, [false]]];
|
||||
|
||||
_statusResult
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(TaskCatalogStore)
|
||||
@ -0,0 +1,106 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Runtime entity registry for task-owned Arma objects.
|
||||
*
|
||||
* Stores object references by registry key and task ID. TaskStore remains the
|
||||
* public facade, while this object owns entity storage and lookup behavior.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Task entity registry object <HASHMAP OBJECT>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskEntityRegistry) = createHashMapObject [[
|
||||
["#type", "TaskEntityRegistry"],
|
||||
["#create", compileFinal {
|
||||
_self call ["resetRuntimeState", []];
|
||||
}],
|
||||
["resetRuntimeState", compileFinal {
|
||||
_self set ["taskEntityRegistries", createHashMapFromArray [
|
||||
["cargo", createHashMap],
|
||||
["hostages", createHashMap],
|
||||
["hvts", createHashMap],
|
||||
["ieds", createHashMap],
|
||||
["entities", createHashMap],
|
||||
["shooters", createHashMap],
|
||||
["targets", createHashMap]
|
||||
]];
|
||||
true
|
||||
}],
|
||||
["registerTaskEntity", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_taskID", "", [""]], ["_entity", objNull, [objNull]]];
|
||||
|
||||
if (_registryKey isEqualTo "" || { _taskID isEqualTo "" } || { isNull _entity }) exitWith { false };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
private _registry = +(_taskEntityRegistries getOrDefault [_registryKey, createHashMap]);
|
||||
private _entities = +(_registry getOrDefault [_taskID, []]);
|
||||
_entities pushBackUnique _entity;
|
||||
_registry set [_taskID, _entities];
|
||||
_taskEntityRegistries set [_registryKey, _registry];
|
||||
_self set ["taskEntityRegistries", _taskEntityRegistries];
|
||||
|
||||
true
|
||||
}],
|
||||
["getTaskEntities", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_taskID", "", [""]]];
|
||||
|
||||
if (_registryKey isEqualTo "" || { _taskID isEqualTo "" }) exitWith { [] };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
private _registry = _taskEntityRegistries getOrDefault [_registryKey, createHashMap];
|
||||
|
||||
+(_registry getOrDefault [_taskID, []])
|
||||
}],
|
||||
["findTaskEntityOwner", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_entity", objNull, [objNull]]];
|
||||
|
||||
if (_registryKey isEqualTo "" || { isNull _entity }) exitWith { "" };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
private _registry = _taskEntityRegistries getOrDefault [_registryKey, createHashMap];
|
||||
private _resolvedTaskID = "";
|
||||
|
||||
{
|
||||
private _taskID = _x;
|
||||
private _entities = _y;
|
||||
|
||||
if (_entity in _entities) exitWith { _resolvedTaskID = _taskID; };
|
||||
|
||||
private _matchingEntity = _entities select {
|
||||
!isNull _x
|
||||
&& { (typeOf _x) isEqualTo (typeOf _entity) }
|
||||
&& { _x distance _entity < 1 }
|
||||
};
|
||||
|
||||
if (_matchingEntity isNotEqualTo []) exitWith { _resolvedTaskID = _taskID; };
|
||||
} forEach _registry;
|
||||
|
||||
_resolvedTaskID
|
||||
}],
|
||||
["clearTaskEntities", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
|
||||
|
||||
{
|
||||
private _registry = +_y;
|
||||
_registry deleteAt _taskID;
|
||||
_taskEntityRegistries set [_x, _registry];
|
||||
} forEach _taskEntityRegistries;
|
||||
|
||||
_self set ["taskEntityRegistries", _taskEntityRegistries];
|
||||
true
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(TaskEntityRegistry)
|
||||
@ -0,0 +1,128 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Task lifecycle timestamp tracking and event reporting.
|
||||
*
|
||||
* Owns task lifecycle timestamps and emits task lifecycle events through the
|
||||
* common event bus. TaskStore remains the public facade.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Task lifecycle reporter object <HASHMAP OBJECT>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskLifecycleReporter) = createHashMapObject [[
|
||||
["#type", "TaskLifecycleReporter"],
|
||||
["#create", compileFinal {
|
||||
_self call ["resetRuntimeState", []];
|
||||
}],
|
||||
["resetRuntimeState", compileFinal {
|
||||
_self set ["taskLifecycleRegistry", createHashMap];
|
||||
true
|
||||
}],
|
||||
["recordTaskCreated", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
private _lifecycle = +(_lifecycleRegistry getOrDefault [_taskID, createHashMap]);
|
||||
_lifecycle set ["createdAt", serverTime];
|
||||
_lifecycleRegistry set [_taskID, _lifecycle];
|
||||
_self set ["taskLifecycleRegistry", _lifecycleRegistry];
|
||||
true
|
||||
}],
|
||||
["recordTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_status", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { "" };
|
||||
|
||||
private _normalizedStatus = toLowerANSI _status;
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
private _lifecycle = +(_lifecycleRegistry getOrDefault [_taskID, createHashMap]);
|
||||
private _eventName = "";
|
||||
|
||||
switch (_normalizedStatus) do {
|
||||
case "active": {
|
||||
_lifecycle set ["startedAt", serverTime];
|
||||
_eventName = "task.started";
|
||||
};
|
||||
case "succeeded": {
|
||||
_lifecycle set ["finishedAt", serverTime];
|
||||
_eventName = "task.completed";
|
||||
};
|
||||
case "failed": {
|
||||
_lifecycle set ["finishedAt", serverTime];
|
||||
_eventName = "task.failed";
|
||||
};
|
||||
};
|
||||
|
||||
_lifecycleRegistry set [_taskID, _lifecycle];
|
||||
_self set ["taskLifecycleRegistry", _lifecycleRegistry];
|
||||
|
||||
_eventName
|
||||
}],
|
||||
["clearTaskLifecycle", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
_lifecycleRegistry deleteAt _taskID;
|
||||
_self set ["taskLifecycleRegistry", _lifecycleRegistry];
|
||||
true
|
||||
}],
|
||||
["buildTaskLifecycleEventPayload", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_status", "", [""]], ["_extra", createHashMap]];
|
||||
|
||||
if !(_extra isEqualType createHashMap) then {
|
||||
_extra = createHashMap;
|
||||
};
|
||||
|
||||
private _catalogEntry = GVAR(TaskCatalogStore) call ["getTaskCatalogEntry", [_taskID]];
|
||||
private _lifecycleRegistry = _self getOrDefault ["taskLifecycleRegistry", createHashMap];
|
||||
private _lifecycle = +(_lifecycleRegistry getOrDefault [_taskID, createHashMap]);
|
||||
private _startedAt = _lifecycle getOrDefault ["startedAt", -1];
|
||||
private _finishedAt = _lifecycle getOrDefault ["finishedAt", -1];
|
||||
|
||||
createHashMapFromArray [
|
||||
["taskID", _taskID],
|
||||
["taskType", _catalogEntry getOrDefault ["type", ""]],
|
||||
["title", _catalogEntry getOrDefault ["title", _taskID]],
|
||||
["description", _catalogEntry getOrDefault ["description", ""]],
|
||||
["position", +(_catalogEntry getOrDefault ["position", []])],
|
||||
["status", _status],
|
||||
["source", _catalogEntry getOrDefault ["source", "task"]],
|
||||
["requesterUid", _catalogEntry getOrDefault ["requesterUid", ""]],
|
||||
["orgID", _catalogEntry getOrDefault ["orgID", "default"]],
|
||||
["startedAt", _startedAt],
|
||||
["finishedAt", _finishedAt],
|
||||
["duration", if (_startedAt >= 0 && { _finishedAt >= 0 }) then { _finishedAt - _startedAt } else { -1 }],
|
||||
["failureReason", _extra getOrDefault ["failureReason", ""]],
|
||||
["participants", GVAR(TaskParticipantTracker) call ["getTaskParticipantUids", [_taskID]]],
|
||||
["rewardData", +(_extra getOrDefault ["rewardData", createHashMap])],
|
||||
["resultSnapshot", +(_extra getOrDefault ["resultSnapshot", createHashMap])],
|
||||
["catalogEntry", +_catalogEntry]
|
||||
]
|
||||
}],
|
||||
["emitTaskLifecycleEvent", compileFinal {
|
||||
params [["_eventName", "", [""]], ["_taskID", "", [""]], ["_status", "", [""]], ["_extra", createHashMap]];
|
||||
|
||||
if (_eventName isEqualTo "" || { _taskID isEqualTo "" }) exitWith { createHashMap };
|
||||
if (isNil QEGVAR(common,EventBus)) exitWith { createHashMap };
|
||||
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
_eventName,
|
||||
_self call ["buildTaskLifecycleEventPayload", [_taskID, _status, _extra]],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]]
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(TaskLifecycleReporter)
|
||||
@ -0,0 +1,155 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Runtime participant tracking and task notification fanout.
|
||||
*
|
||||
* TaskStore remains the public facade, while this object owns participant
|
||||
* snapshots keyed by task ID.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Task participant tracker object <HASHMAP OBJECT>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskParticipantTracker) = createHashMapObject [[
|
||||
["#type", "TaskParticipantTracker"],
|
||||
["#create", compileFinal {
|
||||
_self call ["resetRuntimeState", []];
|
||||
}],
|
||||
["resetRuntimeState", compileFinal {
|
||||
_self set ["participantRegistry", createHashMap];
|
||||
true
|
||||
}],
|
||||
["trackParticipants", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entities", [], [[]]], ["_marker", "", [""]], ["_radius", 300, [0]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
|
||||
private _activePlayers = allPlayers select {
|
||||
alive _x
|
||||
&& { side group _x isEqualTo west }
|
||||
};
|
||||
|
||||
if (_marker isNotEqualTo "" && { markerShape _marker in ["RECTANGLE", "ELLIPSE"] }) then {
|
||||
{
|
||||
private _uid = getPlayerUID _x;
|
||||
if (_uid isNotEqualTo "" && { _x inArea _marker }) then {
|
||||
if !(_uid in _participantSnapshots) then {
|
||||
_participantSnapshots set [_uid, createHashMapFromArray [
|
||||
["startRating", rating _x]
|
||||
]];
|
||||
};
|
||||
};
|
||||
} forEach _activePlayers;
|
||||
};
|
||||
|
||||
if (_radius > 0 && { _entities isNotEqualTo [] }) then {
|
||||
{
|
||||
private _entity = _x;
|
||||
if (isNull _entity) then { continue; };
|
||||
|
||||
{
|
||||
private _uid = getPlayerUID _x;
|
||||
if (_uid isNotEqualTo "" && { (_x distance2D _entity) <= _radius }) then {
|
||||
if !(_uid in _participantSnapshots) then {
|
||||
_participantSnapshots set [_uid, createHashMapFromArray [
|
||||
["startRating", rating _x]
|
||||
]];
|
||||
};
|
||||
};
|
||||
} forEach _activePlayers;
|
||||
} forEach _entities;
|
||||
};
|
||||
|
||||
_participantRegistry set [_taskID, _participantSnapshots];
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
|
||||
_participantSnapshots
|
||||
}],
|
||||
["recordParticipant", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_uid", "", [""]], ["_snapshot", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _uid isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
|
||||
_participantSnapshots set [_uid, +_snapshot];
|
||||
_participantRegistry set [_taskID, _participantSnapshots];
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
|
||||
_participantSnapshots
|
||||
}],
|
||||
["getTaskParticipants", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
+(_participantRegistry getOrDefault [_taskID, createHashMap])
|
||||
}],
|
||||
["getTaskParticipantUids", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { [] };
|
||||
|
||||
keys (_self call ["getTaskParticipants", [_taskID]])
|
||||
}],
|
||||
["clearTaskParticipants", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
_participantRegistry deleteAt _taskID;
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
true
|
||||
}],
|
||||
["notifyParticipants", compileFinal {
|
||||
params [
|
||||
["_taskID", "", [""]],
|
||||
["_type", "info", [""]],
|
||||
["_title", "Tasks", [""]],
|
||||
["_message", "", [""]]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "" || { _message isEqualTo "" }) exitWith { false };
|
||||
|
||||
private _participantSnapshots = _self call ["getTaskParticipants", [_taskID]];
|
||||
if (_participantSnapshots isEqualTo createHashMap) exitWith { false };
|
||||
|
||||
private _participantUids = keys _participantSnapshots;
|
||||
if (_participantUids isEqualTo []) exitWith { false };
|
||||
if (isNil QEGVAR(common,EventBus)) exitWith {
|
||||
{
|
||||
private _player = [_x] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
[CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent);
|
||||
} forEach _participantUids;
|
||||
true
|
||||
};
|
||||
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
"task.notification.requested",
|
||||
createHashMapFromArray [
|
||||
["taskID", _taskID],
|
||||
["notificationType", _type],
|
||||
["title", _title],
|
||||
["message", _message],
|
||||
["participantUids", _participantUids]
|
||||
],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]];
|
||||
|
||||
true
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(TaskParticipantTracker)
|
||||
@ -0,0 +1,276 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Task reward and rating outcome service.
|
||||
*
|
||||
* Resolves task ownership reward context and applies player earnings plus
|
||||
* organization reputation outcomes. TaskStore remains the public facade.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Task reward service object <HASHMAP OBJECT>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskRewardService) = createHashMapObject [[
|
||||
["#type", "TaskRewardService"],
|
||||
["resolveRewardContext", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["requesterUid", ""],
|
||||
["orgID", ""],
|
||||
["memberUids", []]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { _result };
|
||||
|
||||
private _rewardState = GVAR(TaskStateGateway) call ["callTaskState", ["task:ownership:reward_context", [_taskID], createHashMap]];
|
||||
if (_rewardState isEqualTo createHashMap) exitWith { _result };
|
||||
|
||||
private _requesterUid = _rewardState getOrDefault ["requesterUid", ""];
|
||||
private _resolvedOrgID = _rewardState getOrDefault ["orgId", ""];
|
||||
if (_resolvedOrgID isEqualTo "") exitWith { _result };
|
||||
|
||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_resolvedOrgID]];
|
||||
private _memberUids = [];
|
||||
if (_org isNotEqualTo createHashMap) then {
|
||||
private _members = _org getOrDefault ["members", createHashMap];
|
||||
|
||||
if (_members isEqualType createHashMap) then { _memberUids = keys _members; };
|
||||
if (_requesterUid isNotEqualTo "" && { !(_requesterUid in _memberUids) }) then { _memberUids pushBack _requesterUid; };
|
||||
};
|
||||
|
||||
_result set ["requesterUid", _requesterUid];
|
||||
_result set ["orgID", _resolvedOrgID];
|
||||
_result set ["memberUids", _memberUids];
|
||||
_result
|
||||
}],
|
||||
["applyRatingOutcome", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_delta", 0, [0]]];
|
||||
|
||||
private _emitRatingEvent = {
|
||||
params [["_eventName", "", [""]], ["_payload", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_eventName isEqualTo "" || { isNil QEGVAR(common,EventBus) }) exitWith { createHashMap };
|
||||
|
||||
private _eventPayload = +_payload;
|
||||
_eventPayload set ["taskID", _taskID];
|
||||
_eventPayload set ["ratingDelta", _delta];
|
||||
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
_eventName,
|
||||
_eventPayload,
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]]
|
||||
};
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["participantUids", []],
|
||||
["orgIds", []],
|
||||
["contributions", createHashMap],
|
||||
["success", true],
|
||||
["mutationFailures", []],
|
||||
["persistenceFailures", []],
|
||||
["message", ""]
|
||||
];
|
||||
|
||||
if (_taskID isEqualTo "" || { _delta isEqualTo 0 }) exitWith { _result };
|
||||
|
||||
private _participantSnapshots = GVAR(TaskParticipantTracker) call ["getTaskParticipants", [_taskID]];
|
||||
if (_participantSnapshots isEqualTo createHashMap) exitWith { _result };
|
||||
|
||||
private _rewardContext = _self call ["resolveRewardContext", [_taskID]];
|
||||
private _participantUids = keys _participantSnapshots;
|
||||
if (_participantUids isEqualTo [] && { _delta > 0 }) then {
|
||||
private _requesterUid = _rewardContext getOrDefault ["requesterUid", ""];
|
||||
if (_requesterUid isNotEqualTo "") then {
|
||||
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||
if (!isNull _requesterPlayer) then {
|
||||
_participantUids pushBack _requesterUid;
|
||||
_participantSnapshots = GVAR(TaskParticipantTracker) call ["recordParticipant", [_taskID, _requesterUid, createHashMapFromArray [
|
||||
["startRating", rating _requesterPlayer]
|
||||
]]];
|
||||
["WARNING", format ["Task %1 had no tracked participants at payout time; falling back to requester %2 for personal earnings.", _taskID, _requesterUid]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (_participantUids isEqualTo []) exitWith {
|
||||
_result set ["success", false];
|
||||
_result set ["message", "No task participants were available for rating outcome."];
|
||||
["task.rating.failed", createHashMapFromArray [
|
||||
["participantUids", []],
|
||||
["orgIds", []],
|
||||
["contributions", createHashMap],
|
||||
["mutationFailures", []],
|
||||
["persistenceFailures", []],
|
||||
["message", _result get "message"]
|
||||
]] call _emitRatingEvent;
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgIds = [];
|
||||
private _contributions = createHashMap;
|
||||
private _totalContribution = 0;
|
||||
private _mutationFailures = [];
|
||||
private _persistenceFailures = [];
|
||||
|
||||
if (_delta > 0) then {
|
||||
{
|
||||
private _uid = _x;
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
|
||||
_contributions set [_uid, 1];
|
||||
_totalContribution = _totalContribution + 1;
|
||||
} forEach _participantUids;
|
||||
};
|
||||
|
||||
if (_totalContribution <= 0) exitWith {
|
||||
_result set ["success", false];
|
||||
_result set ["message", "No eligible participant contribution was available for rating outcome."];
|
||||
["task.rating.failed", createHashMapFromArray [
|
||||
["participantUids", +_participantUids],
|
||||
["orgIds", +_orgIds],
|
||||
["contributions", +_contributions],
|
||||
["mutationFailures", []],
|
||||
["persistenceFailures", []],
|
||||
["message", _result get "message"]
|
||||
]] call _emitRatingEvent;
|
||||
GVAR(TaskStore) call ["clearTask", [_taskID]];
|
||||
_result
|
||||
};
|
||||
|
||||
{
|
||||
private _uid = _x;
|
||||
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid, ""]];
|
||||
if (_orgID isNotEqualTo "") then { _orgIds pushBackUnique _orgID; };
|
||||
if (_delta > 0) then {
|
||||
private _contribution = _contributions getOrDefault [_uid, 0];
|
||||
if (_contribution <= 0) then { continue; };
|
||||
|
||||
private _account = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||
if (_account isEqualTo createHashMap) then { _account = EGVAR(bank,BankStore) call ["init", [_uid]]; };
|
||||
if (_account isNotEqualTo createHashMap) then {
|
||||
private _earnings = _account getOrDefault ["earnings", 0];
|
||||
private _earningsDelta = round ((_delta * _contribution) / _totalContribution);
|
||||
if (_earningsDelta <= 0) then { continue; };
|
||||
|
||||
private _patch = EGVAR(bank,BankStore) call [
|
||||
"mset",
|
||||
[
|
||||
_uid,
|
||||
createHashMapFromArray [["earnings", (_earnings + _earningsDelta)]],
|
||||
false
|
||||
]
|
||||
];
|
||||
|
||||
if !(_patch isEqualType createHashMap) then { continue; };
|
||||
if (_patch isEqualTo createHashMap) then { continue; };
|
||||
if (isNil QEGVAR(common,EventBus)) then {
|
||||
EGVAR(bank,BankMessenger) call ["sendAccountSync", [_uid, _patch]];
|
||||
} else {
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
"bank.account.sync.requested",
|
||||
createHashMapFromArray [
|
||||
["uid", _uid],
|
||||
["account", +_patch]
|
||||
],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]];
|
||||
};
|
||||
|
||||
if ((EGVAR(bank,BankStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||
_persistenceFailures pushBackUnique format ["bank:%1", _uid];
|
||||
["ERROR", format ["Task %1 updated bank earnings for %2, but durable save failed.", _taskID, _uid]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
};
|
||||
} forEach _participantUids;
|
||||
|
||||
private _ownerOrgID = _rewardContext getOrDefault ["orgID", ""];
|
||||
if (_ownerOrgID isNotEqualTo "") then {
|
||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_ownerOrgID]];
|
||||
|
||||
if (_org isNotEqualTo createHashMap) then {
|
||||
private _reputation = _org getOrDefault ["reputation", 0];
|
||||
private _nextReputation = round (_reputation + _delta);
|
||||
_org set ["reputation", _nextReputation];
|
||||
private _updatedOrg = EGVAR(org,OrgStore) call [
|
||||
"callHotOrg",
|
||||
[
|
||||
"org:hot:override",
|
||||
[_ownerOrgID, toJSON _org]
|
||||
]
|
||||
];
|
||||
|
||||
if (_updatedOrg isNotEqualTo createHashMap) then {
|
||||
private _patch = createHashMapFromArray [["reputation", _nextReputation]];
|
||||
private _memberUids = _rewardContext getOrDefault ["memberUids", []];
|
||||
if (isNil QEGVAR(common,EventBus)) then {
|
||||
{
|
||||
private _player = [_x] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
[CRPC(org,responseSyncOrg), [_patch], _player] call CFUNC(targetEvent);
|
||||
} forEach _memberUids;
|
||||
} else {
|
||||
EGVAR(common,EventBus) call ["emit", [
|
||||
"org.sync.requested",
|
||||
createHashMapFromArray [
|
||||
["orgID", _ownerOrgID],
|
||||
["memberUids", +_memberUids],
|
||||
["patch", +_patch]
|
||||
],
|
||||
createHashMapFromArray [["source", "task"]]
|
||||
]];
|
||||
};
|
||||
|
||||
_orgIds = [_ownerOrgID];
|
||||
if ((EGVAR(org,OrgStore) call ["saveById", [_ownerOrgID]]) isEqualTo createHashMap) then {
|
||||
_persistenceFailures pushBackUnique format ["organization:%1", _ownerOrgID];
|
||||
["ERROR", format ["Task %1 updated reputation for organization %2, but durable save failed.", _taskID, _ownerOrgID]] call EFUNC(common,log);
|
||||
};
|
||||
} else {
|
||||
["ERROR", format ["Failed to update organization %1 reputation for task %2.", _ownerOrgID, _taskID]] call EFUNC(common,log);
|
||||
_mutationFailures pushBackUnique format ["organization:%1", _ownerOrgID];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
_result set ["participantUids", _participantUids];
|
||||
_result set ["orgIds", _orgIds];
|
||||
_result set ["contributions", _contributions];
|
||||
_result set ["success", (_mutationFailures isEqualTo []) && { _persistenceFailures isEqualTo [] }];
|
||||
_result set ["mutationFailures", _mutationFailures];
|
||||
_result set ["persistenceFailures", _persistenceFailures];
|
||||
if (_mutationFailures isNotEqualTo [] || { _persistenceFailures isNotEqualTo [] }) then {
|
||||
private _messageParts = [];
|
||||
if (_mutationFailures isNotEqualTo []) then {
|
||||
_messageParts pushBack format ["mutation failures: %1", _mutationFailures joinString ", "];
|
||||
};
|
||||
if (_persistenceFailures isNotEqualTo []) then {
|
||||
_messageParts pushBack format ["persistence failures: %1", _persistenceFailures joinString ", "];
|
||||
};
|
||||
_result set ["message", _messageParts joinString "; "];
|
||||
};
|
||||
|
||||
private _eventName = ["task.rating.failed", "task.rating.applied"] select (_result getOrDefault ["success", false]);
|
||||
[_eventName, createHashMapFromArray [
|
||||
["participantUids", +(_result getOrDefault ["participantUids", []])],
|
||||
["orgIds", +(_result getOrDefault ["orgIds", []])],
|
||||
["contributions", +(_result getOrDefault ["contributions", createHashMap])],
|
||||
["mutationFailures", +(_result getOrDefault ["mutationFailures", []])],
|
||||
["persistenceFailures", +(_result getOrDefault ["persistenceFailures", []])],
|
||||
["message", _result getOrDefault ["message", ""]]
|
||||
]] call _emitRatingEvent;
|
||||
|
||||
_result
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(TaskRewardService)
|
||||
@ -0,0 +1,77 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* Author: IDSolutions
|
||||
* Gateway for task hot-state extension calls.
|
||||
*
|
||||
* TaskStore owns gameplay/runtime behavior. This gateway owns the transport
|
||||
* boundary to the extension-backed task state service.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Task state gateway object <HASHMAP OBJECT>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(TaskStateGateway) = createHashMapObject [[
|
||||
["#type", "TaskStateGateway"],
|
||||
["reset", compileFinal {
|
||||
["task:reset", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if (
|
||||
!_isSuccess
|
||||
|| { !(_result isEqualType "") }
|
||||
|| { (_result find "Error:") == 0 }
|
||||
) exitWith {
|
||||
["WARNING", "Failed to reset task backend state during task store initialization."] call EFUNC(common,log);
|
||||
false
|
||||
};
|
||||
|
||||
["INFO", "Task backend state reset for mission lifecycle."] call EFUNC(common,log);
|
||||
true
|
||||
}],
|
||||
["callTaskStateEnvelope", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||
|
||||
private _envelope = createHashMapFromArray [
|
||||
["success", false],
|
||||
["error", ""]
|
||||
];
|
||||
|
||||
if (_function isEqualTo "") exitWith { _envelope };
|
||||
|
||||
[_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !_isSuccess exitWith {
|
||||
_envelope set ["error", format ["Task backend call '%1' failed.", _function]];
|
||||
_envelope
|
||||
};
|
||||
if !(_result isEqualType "") exitWith {
|
||||
_envelope set ["error", format ["Task backend call '%1' returned an invalid response.", _function]];
|
||||
_envelope
|
||||
};
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Task extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
|
||||
_envelope set ["error", _result select [7]];
|
||||
_envelope
|
||||
};
|
||||
|
||||
_envelope set ["success", true];
|
||||
if (_result isNotEqualTo "") then { _envelope set ["data", fromJSON _result]; };
|
||||
|
||||
_envelope
|
||||
}],
|
||||
["callTaskState", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]], ["_fallback", nil]];
|
||||
|
||||
private _envelope = _self call ["callTaskStateEnvelope", [_function, _arguments]];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith { _fallback };
|
||||
if (isNil { _envelope get "data" }) exitWith { _fallback };
|
||||
|
||||
_envelope get "data"
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(TaskStateGateway)
|
||||
@ -12,30 +12,38 @@
|
||||
class EquipmentRewards: Edit { \
|
||||
property = QUOTE(DOUBLES(PREFIX,EquipmentRewards)); \
|
||||
displayName = "Equipment Rewards"; \
|
||||
tooltip = "SQF array string for equipment rewards, e.g. [""ItemGPS"",""ItemCompass""]"; \
|
||||
tooltip = "Comma-separated equipment class names, e.g. ItemGPS, ItemCompass. Legacy SQF arrays still work."; \
|
||||
typeName = "STRING"; \
|
||||
}; \
|
||||
class SupplyRewards: Edit { \
|
||||
property = QUOTE(DOUBLES(PREFIX,SupplyRewards)); \
|
||||
displayName = "Supply Rewards"; \
|
||||
tooltip = "SQF array string for supply rewards, e.g. [""FirstAidKit"",""Medikit""]"; \
|
||||
tooltip = "Comma-separated supply class names, e.g. FirstAidKit, Medikit. Legacy SQF arrays still work."; \
|
||||
typeName = "STRING"; \
|
||||
}; \
|
||||
class WeaponRewards: Edit { \
|
||||
property = QUOTE(DOUBLES(PREFIX,WeaponRewards)); \
|
||||
displayName = "Weapon Rewards"; \
|
||||
tooltip = "SQF array string for weapon rewards, e.g. [""arifle_MX_F""]"; \
|
||||
tooltip = "Comma-separated weapon class names, e.g. arifle_MX_F, arifle_Katiba_F. Legacy SQF arrays still work."; \
|
||||
typeName = "STRING"; \
|
||||
}; \
|
||||
class VehicleRewards: Edit { \
|
||||
property = QUOTE(DOUBLES(PREFIX,VehicleRewards)); \
|
||||
displayName = "Vehicle Rewards"; \
|
||||
tooltip = "SQF array string for vehicle rewards, e.g. [""B_MRAP_01_F""]"; \
|
||||
tooltip = "Comma-separated vehicle class names, e.g. B_MRAP_01_F, B_Quadbike_01_F. Legacy SQF arrays still work."; \
|
||||
typeName = "STRING"; \
|
||||
}; \
|
||||
class SpecialRewards: Edit { \
|
||||
property = QUOTE(DOUBLES(PREFIX,SpecialRewards)); \
|
||||
displayName = "Special Rewards"; \
|
||||
tooltip = "SQF array string for special rewards, e.g. [""B_UAV_01_F""]"; \
|
||||
tooltip = "Comma-separated special reward class names, e.g. B_UAV_01_F, B_Heli_Light_01_F. Legacy SQF arrays still work."; \
|
||||
typeName = "STRING"; \
|
||||
};
|
||||
|
||||
#define TASK_CHAIN_ATTRIBUTES(PREFIX) \
|
||||
class PrerequisiteTaskIds: Edit { \
|
||||
property = QUOTE(DOUBLES(PREFIX,PrerequisiteTaskIds)); \
|
||||
displayName = "Prerequisite Task IDs"; \
|
||||
tooltip = "Comma-separated task IDs that must succeed before this task appears in CAD or can be assigned"; \
|
||||
typeName = "STRING"; \
|
||||
};
|
||||
|
||||
@ -295,7 +295,19 @@ General task rules:
|
||||
4. Use Forge grouping modules where required.
|
||||
5. Sync task modules to real world objects, units, vehicles, or grouping
|
||||
modules.
|
||||
6. Test that the task appears in CAD before relying on dispatch assignment.
|
||||
6. To chain tasks, set `Prerequisite Task IDs` on the dependent task module to
|
||||
a comma-separated list of task IDs that must succeed first.
|
||||
7. Reward class fields use comma-separated class names without brackets, such
|
||||
as `ItemGPS, FirstAidKit`. Existing SQF array strings such as
|
||||
`["ItemGPS","FirstAidKit"]` still work for older missions.
|
||||
8. Test that unchained tasks appear in CAD immediately and chained tasks appear
|
||||
only after their prerequisite tasks succeed.
|
||||
|
||||
Task chaining uses only task IDs. The dependent task is still registered during
|
||||
mission setup, but it stays hidden from CAD, cannot be assigned, and does not
|
||||
start its task logic until every prerequisite task has completed successfully.
|
||||
If any prerequisite task fails or never completes, the dependent task remains
|
||||
locked.
|
||||
|
||||
Zone fields that must reference area markers:
|
||||
|
||||
@ -333,7 +345,9 @@ Setup:
|
||||
5. Set `LimitFail` if the mission should fail after too many losses.
|
||||
6. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
7. Sync the attack module directly to the target units or vehicles.
|
||||
7. Set `Prerequisite Task IDs` only if this attack task should unlock after
|
||||
other tasks succeed.
|
||||
8. Sync the attack module directly to the target units or vehicles.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -362,7 +376,9 @@ Setup:
|
||||
or failed conditions.
|
||||
6. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
7. Sync the destroy module directly to the targets.
|
||||
7. Set `Prerequisite Task IDs` only if this destroy task should unlock after
|
||||
other tasks succeed.
|
||||
8. Sync the destroy module directly to the targets.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -408,8 +424,10 @@ Setup:
|
||||
failure.
|
||||
11. Set `TimeLimit` to the IED countdown in seconds.
|
||||
12. Set reward funds, rating gain/loss, and end-state behavior.
|
||||
13. Sync `FORGE_Module_Defuse` to `FORGE_Module_Explosives`.
|
||||
14. Sync `FORGE_Module_Defuse` to `FORGE_Module_Protected` if used.
|
||||
13. Set `Prerequisite Task IDs` only if this defuse task should unlock after
|
||||
other tasks succeed.
|
||||
14. Sync `FORGE_Module_Defuse` to `FORGE_Module_Explosives`.
|
||||
15. Sync `FORGE_Module_Defuse` to `FORGE_Module_Protected` if used.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -455,7 +473,9 @@ Setup:
|
||||
fail threshold.
|
||||
10. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
11. Sync `FORGE_Module_Delivery` to `FORGE_Module_Cargo`.
|
||||
11. Set `Prerequisite Task IDs` only if this delivery task should unlock after
|
||||
other tasks succeed.
|
||||
12. Sync `FORGE_Module_Delivery` to `FORGE_Module_Cargo`.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -510,8 +530,10 @@ Setup:
|
||||
15. If `CBRN Attack` is enabled, set `CBRNZone`.
|
||||
16. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
17. Sync `FORGE_Module_Hostage` to `FORGE_Module_Hostages`.
|
||||
18. Sync `FORGE_Module_Hostage` to `FORGE_Module_Shooters`.
|
||||
17. Set `Prerequisite Task IDs` only if this hostage task should unlock after
|
||||
other tasks succeed.
|
||||
18. Sync `FORGE_Module_Hostage` to `FORGE_Module_Hostages`.
|
||||
19. Sync `FORGE_Module_Hostage` to `FORGE_Module_Shooters`.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -562,7 +584,9 @@ Setup:
|
||||
capture mode.
|
||||
9. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
10. Sync the HVT module directly to the HVT unit or units.
|
||||
10. Set `Prerequisite Task IDs` only if this HVT task should unlock after other
|
||||
tasks succeed.
|
||||
11. Sync the HVT module directly to the HVT unit or units.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -600,6 +624,8 @@ Setup:
|
||||
9. Place one or more enemy groups or units to use as wave templates.
|
||||
10. Sync any unit from each enemy group to the defend module.
|
||||
11. Set reward funds, rating gain/loss, and end-state behavior.
|
||||
12. Set `Prerequisite Task IDs` only if this defend task should unlock after
|
||||
other tasks succeed.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -652,7 +678,8 @@ Before publishing a mission, verify:
|
||||
- Grouping modules are synced in the correct direction.
|
||||
- Success and fail limits match the number of required entities.
|
||||
- Reward funds and rating changes are intentional.
|
||||
- The task appears in CAD when created.
|
||||
- Unchained tasks appear in CAD when created.
|
||||
- Chained tasks remain hidden until all prerequisite task IDs succeed.
|
||||
- Assigned CAD tasks can be acknowledged, declined, and completed.
|
||||
|
||||
## Mission Validation Checklist
|
||||
|
||||
@ -28,6 +28,7 @@ when a catalog entry is inserted or ownership changes:
|
||||
- `accepted`
|
||||
- `requesterUid`
|
||||
- `orgID`
|
||||
- `prerequisiteTaskIds`
|
||||
|
||||
Ownership context:
|
||||
|
||||
@ -209,6 +210,10 @@ Available task modules:
|
||||
|
||||
These modules delegate to `forge_server_task_fnc_startTask`.
|
||||
|
||||
Each task module also includes an optional chain field:
|
||||
|
||||
- `Prerequisite Task IDs`: comma-separated task IDs that must succeed first.
|
||||
|
||||
## Mission Designer Guide
|
||||
|
||||
This section is the practical Eden setup guide for mission designers.
|
||||
@ -230,6 +235,13 @@ Use these rules for every Forge task:
|
||||
6. Grouping modules such as `Explosive Entities`, `Protected Entities`,
|
||||
`Cargo`, `Hostages`, and `Shooters` should be synced to real world objects,
|
||||
not other logic modules.
|
||||
7. To chain tasks, set `Prerequisite Task IDs` on the dependent task module.
|
||||
Use comma-separated IDs such as `attack_01, delivery_02`. The dependent
|
||||
task stays hidden from CAD and cannot be assigned until every listed task
|
||||
succeeds.
|
||||
8. Reward class fields accept comma-separated class names without brackets,
|
||||
such as `ItemGPS, FirstAidKit`. Legacy SQF array strings such as
|
||||
`["ItemGPS","FirstAidKit"]` are still supported.
|
||||
|
||||
### Attack Task
|
||||
|
||||
@ -473,6 +485,7 @@ through `forge_server_task_fnc_handler`.
|
||||
createHashMapFromArray [
|
||||
["limitFail", 0],
|
||||
["limitSuccess", 3],
|
||||
["prerequisiteTaskIds", ["recon_01"]],
|
||||
["funds", 50000],
|
||||
["ratingFail", -10],
|
||||
["ratingSuccess", 20],
|
||||
@ -484,6 +497,37 @@ through `forge_server_task_fnc_handler`.
|
||||
] call forge_server_task_fnc_startTask;
|
||||
```
|
||||
|
||||
## Chained Tasks
|
||||
|
||||
Use `prerequisiteTaskIds` when a task should stay hidden until one or more
|
||||
other tasks succeed. The task is still registered during mission setup, but it
|
||||
is stored with `locked` status, filtered out of CAD, blocked from assignment,
|
||||
and its task logic does not start until every prerequisite task has completed
|
||||
with `succeeded`.
|
||||
|
||||
```sqf
|
||||
[
|
||||
"delivery",
|
||||
"supply_delivery_02",
|
||||
getMarkerPos "delivery_zone_02",
|
||||
"Deliver Medical Supplies",
|
||||
"Move the cargo into the marked delivery area.",
|
||||
createHashMapFromArray [["cargo", [cargoBox1, cargoBox2]]],
|
||||
createHashMapFromArray [
|
||||
["deliveryZone", "delivery_zone_02"],
|
||||
["limitSuccess", 2],
|
||||
["prerequisiteTaskIds", ["compound_attack_01"]],
|
||||
["funds", 30000]
|
||||
]
|
||||
] call forge_server_task_fnc_startTask;
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `prerequisiteTaskIds` accepts either a string or an array of task ID strings.
|
||||
- All prerequisite tasks must succeed before the chained task unlocks.
|
||||
- If a prerequisite fails or never completes, the chained task remains locked.
|
||||
|
||||
## Handler Calls
|
||||
|
||||
Use `forge_server_task_fnc_handler` directly when the task entities are already
|
||||
|
||||
@ -295,7 +295,19 @@ General task rules:
|
||||
4. Use Forge grouping modules where required.
|
||||
5. Sync task modules to real world objects, units, vehicles, or grouping
|
||||
modules.
|
||||
6. Test that the task appears in CAD before relying on dispatch assignment.
|
||||
6. To chain tasks, set `Prerequisite Task IDs` on the dependent task module to
|
||||
a comma-separated list of task IDs that must succeed first.
|
||||
7. Reward class fields use comma-separated class names without brackets, such
|
||||
as `ItemGPS, FirstAidKit`. Existing SQF array strings such as
|
||||
`["ItemGPS","FirstAidKit"]` still work for older missions.
|
||||
8. Test that unchained tasks appear in CAD immediately and chained tasks appear
|
||||
only after their prerequisite tasks succeed.
|
||||
|
||||
Task chaining uses only task IDs. The dependent task is still registered during
|
||||
mission setup, but it stays hidden from CAD, cannot be assigned, and does not
|
||||
start its task logic until every prerequisite task has completed successfully.
|
||||
If any prerequisite task fails or never completes, the dependent task remains
|
||||
locked.
|
||||
|
||||
Zone fields that must reference area markers:
|
||||
|
||||
@ -333,7 +345,9 @@ Setup:
|
||||
5. Set `LimitFail` if the mission should fail after too many losses.
|
||||
6. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
7. Sync the attack module directly to the target units or vehicles.
|
||||
7. Set `Prerequisite Task IDs` only if this attack task should unlock after
|
||||
other tasks succeed.
|
||||
8. Sync the attack module directly to the target units or vehicles.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -362,7 +376,9 @@ Setup:
|
||||
or failed conditions.
|
||||
6. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
7. Sync the destroy module directly to the targets.
|
||||
7. Set `Prerequisite Task IDs` only if this destroy task should unlock after
|
||||
other tasks succeed.
|
||||
8. Sync the destroy module directly to the targets.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -408,8 +424,10 @@ Setup:
|
||||
failure.
|
||||
11. Set `TimeLimit` to the IED countdown in seconds.
|
||||
12. Set reward funds, rating gain/loss, and end-state behavior.
|
||||
13. Sync `FORGE_Module_Defuse` to `FORGE_Module_Explosives`.
|
||||
14. Sync `FORGE_Module_Defuse` to `FORGE_Module_Protected` if used.
|
||||
13. Set `Prerequisite Task IDs` only if this defuse task should unlock after
|
||||
other tasks succeed.
|
||||
14. Sync `FORGE_Module_Defuse` to `FORGE_Module_Explosives`.
|
||||
15. Sync `FORGE_Module_Defuse` to `FORGE_Module_Protected` if used.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -455,7 +473,9 @@ Setup:
|
||||
fail threshold.
|
||||
10. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
11. Sync `FORGE_Module_Delivery` to `FORGE_Module_Cargo`.
|
||||
11. Set `Prerequisite Task IDs` only if this delivery task should unlock after
|
||||
other tasks succeed.
|
||||
12. Sync `FORGE_Module_Delivery` to `FORGE_Module_Cargo`.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -510,8 +530,10 @@ Setup:
|
||||
15. If `CBRN Attack` is enabled, set `CBRNZone`.
|
||||
16. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
17. Sync `FORGE_Module_Hostage` to `FORGE_Module_Hostages`.
|
||||
18. Sync `FORGE_Module_Hostage` to `FORGE_Module_Shooters`.
|
||||
17. Set `Prerequisite Task IDs` only if this hostage task should unlock after
|
||||
other tasks succeed.
|
||||
18. Sync `FORGE_Module_Hostage` to `FORGE_Module_Hostages`.
|
||||
19. Sync `FORGE_Module_Hostage` to `FORGE_Module_Shooters`.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -562,7 +584,9 @@ Setup:
|
||||
capture mode.
|
||||
9. Set reward funds, rating gain/loss, end-state behavior, and optional
|
||||
`TimeLimit`.
|
||||
10. Sync the HVT module directly to the HVT unit or units.
|
||||
10. Set `Prerequisite Task IDs` only if this HVT task should unlock after other
|
||||
tasks succeed.
|
||||
11. Sync the HVT module directly to the HVT unit or units.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -600,6 +624,8 @@ Setup:
|
||||
9. Place one or more enemy groups or units to use as wave templates.
|
||||
10. Sync any unit from each enemy group to the defend module.
|
||||
11. Set reward funds, rating gain/loss, and end-state behavior.
|
||||
12. Set `Prerequisite Task IDs` only if this defend task should unlock after
|
||||
other tasks succeed.
|
||||
|
||||
Validation:
|
||||
|
||||
@ -652,7 +678,8 @@ Before publishing a mission, verify:
|
||||
- Grouping modules are synced in the correct direction.
|
||||
- Success and fail limits match the number of required entities.
|
||||
- Reward funds and rating changes are intentional.
|
||||
- The task appears in CAD when created.
|
||||
- Unchained tasks appear in CAD when created.
|
||||
- Chained tasks remain hidden until all prerequisite task IDs succeed.
|
||||
- Assigned CAD tasks can be acknowledged, declined, and completed.
|
||||
|
||||
## Mission Validation Checklist
|
||||
|
||||
@ -3,9 +3,6 @@ title: "Player Guide"
|
||||
description: "Use this guide as the player-facing overview for Forge systems. It explains what players interact with during normal missions, how task assignment works, and what persistent storage limits apply."
|
||||
---
|
||||
|
||||
Player-guide screenshots are stored as JPG files under
|
||||
`docus/public/images/player`.
|
||||
|
||||
## Opening Forge Interactions
|
||||
|
||||
Most Forge actions are opened from the actor interaction menu while standing
|
||||
|
||||
@ -27,6 +27,7 @@ when a catalog entry is inserted or ownership changes:
|
||||
- `accepted`
|
||||
- `requesterUid`
|
||||
- `orgID`
|
||||
- `prerequisiteTaskIds`
|
||||
|
||||
Ownership context:
|
||||
|
||||
@ -208,6 +209,10 @@ Available task modules:
|
||||
|
||||
These modules delegate to `forge_server_task_fnc_startTask`.
|
||||
|
||||
Each task module also includes an optional chain field:
|
||||
|
||||
- `Prerequisite Task IDs`: comma-separated task IDs that must succeed first.
|
||||
|
||||
## Mission Designer Guide
|
||||
|
||||
This section is the practical Eden setup guide for mission designers.
|
||||
@ -229,6 +234,13 @@ Use these rules for every Forge task:
|
||||
6. Grouping modules such as `Explosive Entities`, `Protected Entities`,
|
||||
`Cargo`, `Hostages`, and `Shooters` should be synced to real world objects,
|
||||
not other logic modules.
|
||||
7. To chain tasks, set `Prerequisite Task IDs` on the dependent task module.
|
||||
Use comma-separated IDs such as `attack_01, delivery_02`. The dependent
|
||||
task stays hidden from CAD and cannot be assigned until every listed task
|
||||
succeeds.
|
||||
8. Reward class fields accept comma-separated class names without brackets,
|
||||
such as `ItemGPS, FirstAidKit`. Legacy SQF array strings such as
|
||||
`["ItemGPS","FirstAidKit"]` are still supported.
|
||||
|
||||
### Attack Task
|
||||
|
||||
@ -472,6 +484,7 @@ through `forge_server_task_fnc_handler`.
|
||||
createHashMapFromArray [
|
||||
["limitFail", 0],
|
||||
["limitSuccess", 3],
|
||||
["prerequisiteTaskIds", ["recon_01"]],
|
||||
["funds", 50000],
|
||||
["ratingFail", -10],
|
||||
["ratingSuccess", 20],
|
||||
@ -483,6 +496,37 @@ through `forge_server_task_fnc_handler`.
|
||||
] call forge_server_task_fnc_startTask;
|
||||
```
|
||||
|
||||
## Chained Tasks
|
||||
|
||||
Use `prerequisiteTaskIds` when a task should stay hidden until one or more
|
||||
other tasks succeed. The task is still registered during mission setup, but it
|
||||
is stored with `locked` status, filtered out of CAD, blocked from assignment,
|
||||
and its task logic does not start until every prerequisite task has completed
|
||||
with `succeeded`.
|
||||
|
||||
```sqf
|
||||
[
|
||||
"delivery",
|
||||
"supply_delivery_02",
|
||||
getMarkerPos "delivery_zone_02",
|
||||
"Deliver Medical Supplies",
|
||||
"Move the cargo into the marked delivery area.",
|
||||
createHashMapFromArray [["cargo", [cargoBox1, cargoBox2]]],
|
||||
createHashMapFromArray [
|
||||
["deliveryZone", "delivery_zone_02"],
|
||||
["limitSuccess", 2],
|
||||
["prerequisiteTaskIds", ["compound_attack_01"]],
|
||||
["funds", 30000]
|
||||
]
|
||||
] call forge_server_task_fnc_startTask;
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `prerequisiteTaskIds` accepts either a string or an array of task ID strings.
|
||||
- All prerequisite tasks must succeed before the chained task unlocks.
|
||||
- If a prerequisite fails or never completes, the chained task remains locked.
|
||||
|
||||
## Handler Calls
|
||||
|
||||
Use `forge_server_task_fnc_handler` directly when the task entities are already
|
||||
|
||||
@ -45,18 +45,14 @@ impl<R: TaskRepository> TaskStateService<R> {
|
||||
|
||||
pub fn list_active_catalog(&self) -> Result<Vec<Value>, String> {
|
||||
let catalog = self.repository.list_catalog()?;
|
||||
let active_statuses = self.repository.list_active_statuses()?;
|
||||
let mut active_entries = Vec::new();
|
||||
|
||||
for (task_id, status) in active_statuses {
|
||||
for (task_id, entry) in catalog {
|
||||
let status = self.derive_catalog_status(&task_id, &entry)?;
|
||||
if !matches!(status.as_str(), "available" | "assigned" | "active") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(entry) = catalog.get(&task_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut entry = entry.fields.clone();
|
||||
entry.insert("taskId".to_string(), Value::String(task_id.clone()));
|
||||
entry.insert("taskID".to_string(), Value::String(task_id));
|
||||
@ -173,10 +169,15 @@ impl<R: TaskRepository> TaskStateService<R> {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.repository
|
||||
.get_completed_status(&entry_id)?
|
||||
.unwrap_or_default())
|
||||
if let Some(status) = self.repository.get_completed_status(&entry_id)? {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
let Some(entry) = self.repository.get_catalog_entry(&entry_id)? else {
|
||||
return Ok(String::new());
|
||||
};
|
||||
|
||||
Ok(Self::default_catalog_status(&entry))
|
||||
}
|
||||
|
||||
pub fn clear_status(&self, entry_id: String) -> Result<bool, String> {
|
||||
@ -245,6 +246,31 @@ impl<R: TaskRepository> TaskStateService<R> {
|
||||
Ok(entry.into_value())
|
||||
}
|
||||
|
||||
fn derive_catalog_status(&self, entry_id: &str, entry: &TaskRecord) -> Result<String, String> {
|
||||
if let Some(status) = self.repository.get_active_status(entry_id)? {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
if let Some(status) = self.repository.get_completed_status(entry_id)? {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
Ok(Self::default_catalog_status(entry))
|
||||
}
|
||||
|
||||
fn default_catalog_status(entry: &TaskRecord) -> String {
|
||||
if entry
|
||||
.fields
|
||||
.get("locked")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"locked".to_string()
|
||||
} else {
|
||||
"available".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_catalog_entry(entry: &mut TaskRecord, entry_id: &str) {
|
||||
let fields = &mut entry.fields;
|
||||
fields
|
||||
@ -383,6 +409,34 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_status_defaults_catalog_entries_to_available_or_locked() {
|
||||
let service = TaskStateService::new(InMemoryTaskRepository::new());
|
||||
|
||||
service
|
||||
.upsert_catalog_entry("task-open".to_string(), r#"{"title":"Open"}"#.to_string())
|
||||
.expect("open catalog upsert should succeed");
|
||||
service
|
||||
.upsert_catalog_entry(
|
||||
"task-locked".to_string(),
|
||||
r#"{"title":"Locked","locked":true}"#.to_string(),
|
||||
)
|
||||
.expect("locked catalog upsert should succeed");
|
||||
|
||||
assert_eq!(
|
||||
service
|
||||
.get_status("task-open".to_string())
|
||||
.expect("open status lookup should succeed"),
|
||||
"available"
|
||||
);
|
||||
assert_eq!(
|
||||
service
|
||||
.get_status("task-locked".to_string())
|
||||
.expect("locked status lookup should succeed"),
|
||||
"locked"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_active_catalog_returns_assignable_and_active_entries() {
|
||||
let service = TaskStateService::new(InMemoryTaskRepository::new());
|
||||
@ -435,4 +489,31 @@ mod tests {
|
||||
assert!(task_ids.contains(&"task-assigned"));
|
||||
assert!(task_ids.contains(&"task-active"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_active_catalog_includes_unstatused_unlocked_entries() {
|
||||
let service = TaskStateService::new(InMemoryTaskRepository::new());
|
||||
|
||||
service
|
||||
.upsert_catalog_entry("task-open".to_string(), r#"{"title":"Open"}"#.to_string())
|
||||
.expect("open catalog upsert should succeed");
|
||||
service
|
||||
.upsert_catalog_entry(
|
||||
"task-locked".to_string(),
|
||||
r#"{"title":"Locked","locked":true}"#.to_string(),
|
||||
)
|
||||
.expect("locked catalog upsert should succeed");
|
||||
|
||||
let active_catalog = service
|
||||
.list_active_catalog()
|
||||
.expect("active catalog should build");
|
||||
|
||||
let task_ids: Vec<_> = active_catalog
|
||||
.iter()
|
||||
.filter_map(|entry| entry.get("taskId").and_then(Value::as_str))
|
||||
.collect();
|
||||
|
||||
assert_eq!(active_catalog.len(), 1);
|
||||
assert!(task_ids.contains(&"task-open"));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user