Refactor task management system with object prototypes and enhanced logging

- Updated fnc_extCall.sqf to suppress logging for specific functions.
- Added object model prototypes for task instances in prototypes/taskObjectPrototypes.sqf.
- Enhanced README.md to document the new object model and its purpose.
- Modified XEH_postInit.sqf to improve event handling for defuse tasks.
- Updated various task functions (fnc_attack, fnc_defend, fnc_defuse, fnc_delivery, fnc_destroy, fnc_heartBeat, fnc_hostage, fnc_hvt) to include task acceptance checks.
- Improved fnc_makeHostage and fnc_makeIED to ensure proper task registration and state management.
- Introduced new methods in task object prototypes for better state management and task flow control.
This commit is contained in:
Jacob Schmidt 2026-04-28 23:04:22 -05:00
parent d9404f2d60
commit 07d5422091
18 changed files with 554 additions and 41 deletions

View File

@ -4,7 +4,7 @@
* File: fnc_extCall.sqf
* Author: IDSolutions
* Date: 2026-01-03
* Last Update: 2026-04-01
* Last Update: 2026-04-28
* Public: No
*
* Description:
@ -24,9 +24,12 @@
params [["_function", "", [""]], ["_arguments", [], [[]]]];
["INFO", format ["Calling function: %1", _function], nil, nil] call EFUNC(common,log);
private _quietFunctionLogs = ["task:defuse:get"];
private _functionLower = toLower _function;
if !(_functionLower in _quietFunctionLogs) then {
["INFO", format ["Calling function: %1", _function], nil, nil] call EFUNC(common,log);
};
private _chunkPrefix = "FORGE_TRANSPORT_CHUNK:";
private _chunkPrefixLength = count toArray _chunkPrefix;
private _unsupportedRoutePrefix = "Error: Unsupported transport route";

View File

@ -41,6 +41,17 @@ system intentionally starts clean after each server or mission restart.
- defuse progress
- per-task entity registries for cargo, hostages, HVTs, IEDs, protected entities, shooters, and targets
### Object Model Prototype
A review-only prototype for object-based task instances lives under
`prototypes/`. It is not wired into runtime.
- `prototypes/taskObjectPrototypes.sqf`
- `prototypes/README.md`
The prototype sketches `TaskInstanceBaseClass`, `HostageTaskBaseClass`, and
`DefuseTaskBaseClass` using `createHashMapObject` so the team can review a
stateful per-task design without replacing the current procedural task flows.
### Reward Handling
`fnc_handleTaskRewards.sqf` applies org-owned rewards:
- `funds` -> org funds

View File

@ -2,13 +2,27 @@
["ace_explosives_defuse", {
private _taskID = "";
private _explosive = objNull;
{
if (_x isEqualType objNull && { !isNull _x }) then {
if (isNull _explosive) then { _explosive = _x; };
_taskID = _x getVariable ["assignedTask", ""];
if (_taskID isNotEqualTo "") exitWith {};
};
} forEach _this;
if (_taskID isEqualTo "") exitWith {};
if (_taskID isEqualTo "" && { !isNull _explosive }) then {
_taskID = GVAR(TaskStore) call ["findTaskEntityOwner", ["ieds", _explosive]];
};
if (_taskID isEqualTo "") exitWith {
["WARNING", format [
"ACE Defuse Event Ignored: No assignedTask found. Explosive=%1, Type=%2, NetID=%3",
_explosive,
typeOf _explosive,
netId _explosive
]] call EFUNC(common,log);
};
GVAR(TaskStore) call ["incrementDefuseCount", [_taskID]];
}] call CFUNC(addEventHandler);

View File

@ -58,6 +58,14 @@ waitUntil {
};
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
if (_timeLimit isNotEqualTo 0) then {
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
};
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {

View File

@ -55,17 +55,41 @@ if (_defenseZone == "" || !(markerShape _defenseZone in ["RECTANGLE", "ELLIPSE"]
};
private _result = 0;
private _startTime = time;
private _nextWaveTime = _startTime;
private _startTime = -1;
private _nextWaveTime = -1;
private _currentWave = 0;
private _zoneEmptyCounter = 0;
private _warningIssued = false;
private _defenseStarted = false;
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, [], _defenseZone, 0]];
private _bluforInZone = count (allUnits select { _x isKindOf "CAManBase" && { side _x == west } && { alive _x }} inAreaArray _defenseZone);
private _readyToStart = _bluforInZone >= _minBlufor;
if (_readyToStart) then {
_defenseStarted = true;
_startTime = time;
_nextWaveTime = _startTime;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "info", "Tasks", "Defense has started. Hold the zone."]];
};
_readyToStart
};
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, [], _defenseZone, 0]];
private _bluforInZone = count (allUnits select { _x isKindOf "CAManBase" && { side _x == west } && { alive _x }} inAreaArray _defenseZone);
private _timeElapsed = time - _startTime;
private _timeElapsed = if (_defenseStarted) then { time - _startTime } else { 0 };
if (_bluforInZone < _minBlufor) then {
_zoneEmptyCounter = _zoneEmptyCounter + 1;
@ -79,7 +103,7 @@ waitUntil {
_warningIssued = false;
};
if (_currentWave < _waveCount && time >= _nextWaveTime) then {
if (_currentWave < _waveCount && _defenseStarted && { time >= _nextWaveTime }) then {
[_defenseZone, _taskID, _currentWave] call FUNC(spawnEnemyWave);
_currentWave = _currentWave + 1;

View File

@ -46,7 +46,6 @@ params [
private _result = 0;
private _ieds = [];
private _entities = [];
waitUntil {
sleep 1;
@ -54,25 +53,30 @@ waitUntil {
count _ieds > 0
};
waitUntil {
sleep 1;
_entities = GVAR(TaskStore) call ["getTaskEntities", ["entities", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _ieds + _entities, "", 250]];
count _entities > 0
};
_ieds = GVAR(TaskStore) call ["getTaskEntities", ["ieds", _taskID]];
_entities = GVAR(TaskStore) call ["getTaskEntities", ["entities", _taskID]];
private _entities = GVAR(TaskStore) call ["getTaskEntities", ["entities", _taskID]];
private _requiredDefusals = if (_limitSuccess < 0) then { count _ieds } else { _limitSuccess };
private _maxProtectedLosses = if (_limitFail < 0) then { count _entities } else { _limitFail };
private _entitiesDestroyed = 0;
private _defusedCount = 0;
private _shouldFail = false;
private _shouldSucceed = false;
private _done = false;
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _ieds + _entities, "", 250]];
private _entitiesDestroyed = ({ !alive _x } count _entities);
_entitiesDestroyed = ({ !alive _x } count _entities);
_defusedCount = GVAR(TaskStore) call ["getDefuseCount", [_taskID]];
_shouldFail = (_maxProtectedLosses > 0) && { _entitiesDestroyed >= _maxProtectedLosses };
_shouldSucceed = (_requiredDefusals > 0) && { _defusedCount >= _requiredDefusals } && { (_maxProtectedLosses <= 0) || { _entitiesDestroyed < _maxProtectedLosses } };
_done = false;
if (_entitiesDestroyed >= _limitFail) then { _result = 1; };
if (_shouldFail) then { _result = 1; };
if ((_result == 1) or _shouldSucceed) then { _done = true; };
(_result == 1) or ((GVAR(TaskStore) call ["getDefuseCount", [_taskID]]) >= _limitSuccess && (_entitiesDestroyed < _limitFail))
_done
};
if (_result == 1) then {

View File

@ -30,8 +30,16 @@ if (_taskID isEqualTo "") exitWith {
private _syncedModules = synchronizedObjects _logic;
private _iedModule = (_syncedModules select { typeOf _x isEqualTo "FORGE_Module_Explosives" }) param [0, objNull];
private _protectedModule = (_syncedModules select { typeOf _x isEqualTo "FORGE_Module_Protected" }) param [0, objNull];
private _iedEntities = if (!isNull _iedModule) then { synchronizedObjects _iedModule } else { [] };
private _protectedEntities = if (!isNull _protectedModule) then { synchronizedObjects _protectedModule } else { [] };
private _iedEntities = if (!isNull _iedModule) then {
synchronizedObjects _iedModule select { !(_x isKindOf "Logic") }
} else {
[]
};
private _protectedEntities = if (!isNull _protectedModule) then {
synchronizedObjects _protectedModule select { !(_x isKindOf "Logic") }
} else {
[]
};
["INFO", format [
"Defuse Module: TaskID: %1, IEDs: %2, Protected: %3, IED timer: %4s",

View File

@ -60,6 +60,14 @@ waitUntil {
};
_cargo = GVAR(TaskStore) call ["getTaskEntities", ["cargo", _taskID]];
if (_timeLimit isNotEqualTo 0) then {
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
};
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {

View File

@ -58,6 +58,14 @@ waitUntil {
};
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
if (_timeLimit isNotEqualTo 0) then {
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
};
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {

View File

@ -38,7 +38,8 @@ switch (_typeOf) do {
[_entity] joinSilent (group _nearPlayer);
_entity setCaptive false;
// Keep rescued hostages protected while they follow the player group.
_entity setCaptive true;
_entity enableAIFeature ["MOVE", true];
_entity playMove "acts_executionvictim_unbow";
};
@ -53,6 +54,14 @@ switch (_typeOf) do {
doStop _entity;
};
case "ied": {
private _taskID = _entity getVariable ["assignedTask", ""];
if (_taskID isNotEqualTo "") then {
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
};
while { alive _entity && _time > 0} do {
if (_time > 10) then { _entity say3D "FORGE_timerBeep" };
if (_time <= 10 && _time > 5) then { _entity say3D "FORGE_timerBeepShort" };

View File

@ -75,13 +75,26 @@ waitUntil {
_hostages = GVAR(TaskStore) call ["getTaskEntities", ["hostages", _taskID]];
_shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]];
private _requiredRescues = if (_limitSuccess < 0) then { count _hostages } else { _limitSuccess };
private _maxHostageLosses = if (_limitFail < 0) then { count _hostages } else { _limitFail };
if (_timeLimit isNotEqualTo 0) then {
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
};
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _hostages + _shooters, _extZone, 250]];
private _hostagesFreed = ({ !captive _x } count _hostages);
private _playerGroups = allPlayers apply { group _x };
private _hostagesFreed = ({
alive _x && { ((group _x) in _playerGroups) || { !captive _x } }
} count _hostages);
private _hostagesInZone = ({ _x inArea _extZone } count _hostages);
private _hostagesKilled = ({ !alive _x } count _hostages);
private _shootersAlive = ({ alive _x } count _shooters);
@ -89,18 +102,18 @@ waitUntil {
if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_hostagesFreed < _limitSuccess && _timeExpired) then { _result = 1; };
if (_hostagesKilled >= _limitFail) then { _result = 1; };
if (_hostagesFreed < _requiredRescues && _timeExpired) then { _result = 1; };
if (_hostagesKilled >= _maxHostageLosses) then { _result = 1; };
(_result == 1) or
((_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail)) or
((!isNil "_shooters") && (_shootersAlive <= 0) && (_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail))
((_hostagesInZone >= _requiredRescues) && (_hostagesKilled < _maxHostageLosses)) or
((!isNil "_shooters") && (_shootersAlive <= 0) && (_hostagesInZone >= _requiredRescues) && (_hostagesKilled < _maxHostageLosses))
} else {
if (_hostagesKilled >= _limitFail) then { _result = 1; };
if (_hostagesKilled >= _maxHostageLosses) then { _result = 1; };
(_result == 1) or
((_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail)) or
((!isNil "_shooters") && (_shootersAlive <= 0) && (_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail))
((_hostagesInZone >= _requiredRescues) && (_hostagesKilled < _maxHostageLosses)) or
((!isNil "_shooters") && (_shootersAlive <= 0) && (_hostagesInZone >= _requiredRescues) && (_hostagesKilled < _maxHostageLosses))
};
};

View File

@ -30,8 +30,20 @@ if (_taskID isEqualTo "") exitWith {
private _syncedModules = synchronizedObjects _logic;
private _hostageModule = (_syncedModules select { typeOf _x isEqualTo "FORGE_Module_Hostages" }) param [0, objNull];
private _shooterModule = (_syncedModules select { typeOf _x isEqualTo "FORGE_Module_Shooters" }) param [0, objNull];
private _hostageEntities = if (!isNull _hostageModule) then { synchronizedObjects _hostageModule } else { [] };
private _shooterEntities = if (!isNull _shooterModule) then { synchronizedObjects _shooterModule } else { [] };
private _hostageEntities = if (!isNull _hostageModule) then {
synchronizedObjects _hostageModule select {
(_x isKindOf "CAManBase") && { !(_x isKindOf "Logic") }
}
} else {
[]
};
private _shooterEntities = if (!isNull _shooterModule) then {
synchronizedObjects _shooterModule select {
(_x isKindOf "CAManBase") && { !(_x isKindOf "Logic") }
}
} else {
[]
};
["INFO", format [
"Hostage Module: TaskID: %1, ExtZone: %2, Hostages: %3, Shooters: %4, CBRN: %5, Execution: %6",

View File

@ -66,6 +66,14 @@ waitUntil {
};
_hvts = GVAR(TaskStore) call ["getTaskEntities", ["hvts", _taskID]];
if (_timeLimit isNotEqualTo 0) then {
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
};
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {

View File

@ -176,6 +176,26 @@ GVAR(TaskStore) = createHashMapObject [[
private _entry = _self call ["callTaskState", ["task:catalog:get", [_taskID], objNull]];
_entry isEqualType createHashMap
}],
["getTaskCatalogEntry", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { createHashMap };
private _entry = _self call ["callTaskState", ["task:catalog:get", [_taskID], createHashMap]];
if !(_entry isEqualType createHashMap) exitWith { createHashMap };
_entry
}],
["isTaskAccepted", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _entry = _self call ["getTaskCatalogEntry", [_taskID]];
if (_entry isEqualTo createHashMap) exitWith { false };
(_entry getOrDefault ["accepted", false]) || { (_entry getOrDefault ["requesterUid", ""]) isNotEqualTo "" }
}],
["acceptTask", compileFinal {
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
@ -278,6 +298,35 @@ GVAR(TaskStore) = createHashMapObject [[
+(_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", "", [""]]];
@ -382,20 +431,31 @@ GVAR(TaskStore) = createHashMapObject [[
if (_taskID isEqualTo "") exitWith { 0 };
private _nextCount = _self call ["callTaskState", ["task:defuse:increment", [_taskID], 0]];
if !(_nextCount isEqualType 0) exitWith { 0 };
["task:defuse:increment", [_taskID]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
_nextCount
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
}],
["getDefuseCount", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { 0 };
private _defuseCount = _self call ["callTaskState", ["task:defuse:get", [_taskID], 0]];
if !(_defuseCount isEqualType 0) 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
};
_defuseCount
parseNumber _result
}],
["notifyParticipants", compileFinal {
params [

View File

@ -27,4 +27,11 @@ if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call E
SETVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["hostages", _taskID, _entity]];
if (alive _entity) then { [_entity, "hostage"] spawn FUNC(heartBeat); };
if (alive _entity) then {
// Apply hostage protection immediately so nearby hostile AI cannot kill the
// unit before the scheduled heartbeat initializes the animation state.
_entity setCaptive true;
_entity enableAIFeature ["MOVE", false];
[_entity, "hostage"] spawn FUNC(heartBeat);
};

View File

@ -26,7 +26,7 @@ if (_time <= 0) exitWith { ["ERROR", "Invalid time provided for IED"] call EFUNC
["INFO", format ["Make IED: %1", _this]] call EFUNC(common,log);
SETVAR(_entity,assignedTask,_taskID);
SETPVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["ieds", _taskID, _entity]];
if (alive _entity) then { [_entity, "ied", _time] spawn FUNC(heartBeat); };

View File

@ -0,0 +1,26 @@
# Task Object Prototypes
This folder contains review-only `createHashMapObject` prototypes for task
instances. They are not part of runtime initialization.
Current prototypes:
- `TaskInstanceBaseClass`
- `HostageTaskBaseClass`
- `DefuseTaskBaseClass`
Source:
- [taskObjectPrototypes.sqf](./taskObjectPrototypes.sqf)
Purpose:
- show what per-task instance objects could look like
- separate state ownership from the current long procedural functions
- avoid committing the live addon to a large refactor before the model is
reviewed
Important design choice:
- these prototypes use explicit `markSucceeded`, `markFailed`, and `cleanup`
methods instead of relying on `#delete`
That is intentional. `createHashMapObject` destructor timing is reference-based,
so `#delete` is not a good primitive for mission-critical task completion or
reward flow.

View File

@ -0,0 +1,290 @@
#include "..\script_component.hpp"
/*
* Review-only prototype for object-based task instances.
*
* This file is intentionally not referenced by XEH_PREP or runtime init.
* It exists so the current procedural task flows can be compared against
* a createHashMapObject-based design before any live refactor is attempted.
*
* Usage in debug/testing:
* private _prototypes = call compile preprocessFileLineNumbers
* "\forge\forge_server\addons\task\prototypes\taskObjectPrototypes.sqf";
*
* private _task = createHashMapObject [
* _prototypes get "HostageTaskBaseClass",
* [
* "task_hostage_review",
* createHashMapFromArray [
* ["hostages", [hostage1, hostage2]],
* ["shooters", [shooter1, shooter2]]
* ],
* createHashMapFromArray [
* ["extractionZone", "hostage_extract"],
* ["limitSuccess", 2],
* ["limitFail", 1],
* ["execution", true],
* ["timeLimit", 900]
* ]
* ]
* ];
*/
#pragma hemtt ignore_variables ["_self"]
GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["#type", "TaskInstanceBaseClass"],
["#create", compileFinal {
params [
["_taskID", "", [""]],
["_taskType", "", [""]],
["_entities", createHashMap, [createHashMap]],
["_taskParams", createHashMap, [createHashMap]]
];
_self set ["taskID", _taskID];
_self set ["taskType", _taskType];
_self set ["entities", _entities];
_self set ["taskParams", _taskParams];
_self set ["status", "created"];
_self set ["startedAt", -1];
_self set ["finishedAt", -1];
_self set ["failureReason", ""];
_self set ["outcomeData", createHashMap];
}],
["getTaskID", compileFinal {
_self getOrDefault ["taskID", ""]
}],
["getTaskType", compileFinal {
_self getOrDefault ["taskType", ""]
}],
["getStatus", compileFinal {
_self getOrDefault ["status", "created"]
}],
["markActive", compileFinal {
_self set ["status", "active"];
_self set ["startedAt", serverTime];
true
}],
["markSucceeded", compileFinal {
params [["_outcomeData", createHashMap, [createHashMap]]];
_self set ["status", "succeeded"];
_self set ["finishedAt", serverTime];
_self set ["outcomeData", _outcomeData];
true
}],
["markFailed", compileFinal {
params [["_reason", "", [""]], ["_outcomeData", createHashMap, [createHashMap]]];
_self set ["status", "failed"];
_self set ["finishedAt", serverTime];
_self set ["failureReason", _reason];
_self set ["outcomeData", _outcomeData];
true
}],
["cleanup", compileFinal {
false
}],
["tick", compileFinal {
createHashMap
}],
["runLoop", compileFinal {
false
}]
];
GVAR(HostageTaskBaseClass) = createHashMapFromArray [
["#base", GVAR(TaskInstanceBaseClass)],
["#type", "HostageTaskBaseClass"],
["#create", compileFinal {
params [
["_taskID", "", [""]],
["_entities", createHashMap, [createHashMap]],
["_taskParams", createHashMap, [createHashMap]]
];
_self set ["taskID", _taskID];
_self set ["taskType", "hostage"];
_self set ["entities", _entities];
_self set ["taskParams", _taskParams];
_self set ["status", "created"];
_self set ["startedAt", -1];
_self set ["finishedAt", -1];
_self set ["failureReason", ""];
_self set ["outcomeData", createHashMap];
private _hostages = +(_entities getOrDefault ["hostages", []]);
private _shooters = +(_entities getOrDefault ["shooters", []]);
private _requiredRescues = _taskParams getOrDefault ["limitSuccess", -1];
if (_requiredRescues < 0) then { _requiredRescues = count _hostages; };
private _maxHostageLosses = _taskParams getOrDefault ["limitFail", -1];
if (_maxHostageLosses < 0) then { _maxHostageLosses = count _hostages; };
_self set ["hostages", _hostages];
_self set ["shooters", _shooters];
_self set ["extractionZone", _taskParams getOrDefault ["extractionZone", ""]];
_self set ["timeLimit", _taskParams getOrDefault ["timeLimit", 0]];
_self set ["execution", _taskParams getOrDefault ["execution", false]];
_self set ["cbrn", _taskParams getOrDefault ["cbrn", false]];
_self set ["cbrnZone", _taskParams getOrDefault ["cbrnZone", ""]];
_self set ["requiredRescues", _requiredRescues];
_self set ["maxHostageLosses", _maxHostageLosses];
}],
["countFreedHostages", compileFinal {
private _playerGroups = allPlayers apply { group _x };
private _hostages = _self getOrDefault ["hostages", []];
{
alive _x && { ((group _x) in _playerGroups) || { !captive _x } }
} count _hostages
}],
["countHostagesInZone", compileFinal {
private _extZone = _self getOrDefault ["extractionZone", ""];
private _hostages = _self getOrDefault ["hostages", []];
if (_extZone isEqualTo "") exitWith { 0 };
{ _x inArea _extZone } count _hostages
}],
["countKilledHostages", compileFinal {
private _hostages = _self getOrDefault ["hostages", []];
{ !alive _x } count _hostages
}],
["countAliveShooters", compileFinal {
private _shooters = _self getOrDefault ["shooters", []];
{ alive _x } count _shooters
}],
["tick", compileFinal {
private _startedAt = _self getOrDefault ["startedAt", -1];
private _timeLimit = _self getOrDefault ["timeLimit", 0];
private _killed = _self call ["countKilledHostages", []];
private _freed = _self call ["countFreedHostages", []];
private _inZone = _self call ["countHostagesInZone", []];
private _shootersAlive = _self call ["countAliveShooters", []];
private _requiredRescues = _self getOrDefault ["requiredRescues", 0];
private _maxHostageLosses = _self getOrDefault ["maxHostageLosses", 0];
private _timeExpired = false;
if (_timeLimit > 0 && { _startedAt >= 0 }) then {
_timeExpired = (serverTime - _startedAt) >= _timeLimit;
};
createHashMapFromArray [
["freed", _freed],
["inZone", _inZone],
["killed", _killed],
["shootersAlive", _shootersAlive],
["requiredRescues", _requiredRescues],
["maxHostageLosses", _maxHostageLosses],
["timeExpired", _timeExpired],
["shouldFail", (_killed >= _maxHostageLosses) || { _timeExpired && { _freed < _requiredRescues } }],
["shouldSucceed", (_inZone >= _requiredRescues) && { _killed < _maxHostageLosses }]
]
}],
["runLoop", compileFinal {
_self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do {
private _snapshot = _self call ["tick", []];
if (_snapshot getOrDefault ["shouldFail", false]) exitWith {
_self call ["markFailed", ["Hostage fail conditions met.", _snapshot]];
};
if (_snapshot getOrDefault ["shouldSucceed", false]) exitWith {
_self call ["markSucceeded", [_snapshot]];
};
sleep 1;
};
true
}]
];
GVAR(DefuseTaskBaseClass) = createHashMapFromArray [
["#base", GVAR(TaskInstanceBaseClass)],
["#type", "DefuseTaskBaseClass"],
["#create", compileFinal {
params [
["_taskID", "", [""]],
["_entities", createHashMap, [createHashMap]],
["_taskParams", createHashMap, [createHashMap]]
];
_self set ["taskID", _taskID];
_self set ["taskType", "defuse"];
_self set ["entities", _entities];
_self set ["taskParams", _taskParams];
_self set ["status", "created"];
_self set ["startedAt", -1];
_self set ["finishedAt", -1];
_self set ["failureReason", ""];
_self set ["outcomeData", createHashMap];
private _ieds = +(_entities getOrDefault ["ieds", []]);
private _protected = +(_entities getOrDefault ["protected", []]);
private _requiredDefusals = _taskParams getOrDefault ["limitSuccess", -1];
if (_requiredDefusals < 0) then { _requiredDefusals = count _ieds; };
private _maxProtectedLosses = _taskParams getOrDefault ["limitFail", -1];
if (_maxProtectedLosses < 0) then { _maxProtectedLosses = count _protected; };
_self set ["ieds", _ieds];
_self set ["protected", _protected];
_self set ["requiredDefusals", _requiredDefusals];
_self set ["maxProtectedLosses", _maxProtectedLosses];
_self set ["iedTimer", _taskParams getOrDefault ["iedTimer", 300]];
}],
["countProtectedDestroyed", compileFinal {
private _protected = _self getOrDefault ["protected", []];
{ !alive _x } count _protected
}],
["getDefuseCount", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""];
if (_taskID isEqualTo "") exitWith { 0 };
GVAR(TaskStore) call ["getDefuseCount", [_taskID]]
}],
["tick", compileFinal {
private _defusedCount = _self call ["getDefuseCount", []];
private _protectedDestroyed = _self call ["countProtectedDestroyed", []];
private _requiredDefusals = _self getOrDefault ["requiredDefusals", 0];
private _maxProtectedLosses = _self getOrDefault ["maxProtectedLosses", 0];
createHashMapFromArray [
["defusedCount", _defusedCount],
["protectedDestroyed", _protectedDestroyed],
["requiredDefusals", _requiredDefusals],
["maxProtectedLosses", _maxProtectedLosses],
["shouldFail", (_protectedDestroyed >= _maxProtectedLosses) && { _maxProtectedLosses > 0 }],
["shouldSucceed", (_defusedCount >= _requiredDefusals) && { _requiredDefusals > 0 } && { _protectedDestroyed < _maxProtectedLosses || { _maxProtectedLosses <= 0 } }]
]
}],
["runLoop", compileFinal {
_self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do {
private _snapshot = _self call ["tick", []];
if (_snapshot getOrDefault ["shouldFail", false]) exitWith {
_self call ["markFailed", ["Defuse fail conditions met.", _snapshot]];
};
if (_snapshot getOrDefault ["shouldSucceed", false]) exitWith {
_self call ["markSucceeded", [_snapshot]];
};
sleep 1;
};
true
}]
];
createHashMapFromArray [
["TaskInstanceBaseClass", GVAR(TaskInstanceBaseClass)],
["HostageTaskBaseClass", GVAR(HostageTaskBaseClass)],
["DefuseTaskBaseClass", GVAR(DefuseTaskBaseClass)]
]