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:
parent
d9404f2d60
commit
07d5422091
@ -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";
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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" };
|
||||
|
||||
@ -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))
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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); };
|
||||
|
||||
26
arma/server/addons/task/prototypes/README.md
Normal file
26
arma/server/addons/task/prototypes/README.md
Normal 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.
|
||||
290
arma/server/addons/task/prototypes/taskObjectPrototypes.sqf
Normal file
290
arma/server/addons/task/prototypes/taskObjectPrototypes.sqf
Normal 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)]
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user