From 1b000af2e615e0426c86fb7112c2b4bc7a88c07a Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Thu, 30 Apr 2026 22:48:52 -0500 Subject: [PATCH] Add prototypes for entity and hostage task management - Introduced `EntityControllerBaseClass` for managing object-based entity controllers. - Added `HostageEntityController` to handle hostage-specific behaviors and interactions. - Created `HostageTaskBaseClass` to define the structure and logic for hostage-related tasks. - Updated `README.md` to include new prototypes and their purposes. - Refactored `taskObjectPrototypes.sqf` to load new classes and maintain organization. --- arma/server/addons/phone.7z | Bin 6025 -> 0 bytes .../task/functions/fnc_handleTaskRewards.sqf | 29 +- .../addons/task/functions/fnc_startTask.sqf | 6 +- .../task/prototypes/AttackTaskBaseClass.sqf | 171 +++++++++ .../task/prototypes/DefuseTaskBaseClass.sqf | 119 ++++++ .../prototypes/EntityControllerBaseClass.sqf | 87 +++++ .../prototypes/HostageEntityController.sqf | 127 +++++++ .../task/prototypes/HostageTaskBaseClass.sqf | 339 ++++++++++++++++++ arma/server/addons/task/prototypes/README.md | 16 + .../task/prototypes/TaskInstanceBaseClass.sqf | 120 +++++++ .../task/prototypes/taskObjectPrototypes.sqf | 263 +------------- 11 files changed, 1002 insertions(+), 275 deletions(-) delete mode 100644 arma/server/addons/phone.7z create mode 100644 arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf create mode 100644 arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf create mode 100644 arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf create mode 100644 arma/server/addons/task/prototypes/HostageEntityController.sqf create mode 100644 arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf create mode 100644 arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf diff --git a/arma/server/addons/phone.7z b/arma/server/addons/phone.7z deleted file mode 100644 index a8f16b59ac2699bbbfd628ddba1aafd139f416f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6025 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 DC?2Pn diff --git a/arma/server/addons/task/functions/fnc_handleTaskRewards.sqf b/arma/server/addons/task/functions/fnc_handleTaskRewards.sqf index 1ba8f83..8eaec05 100644 --- a/arma/server/addons/task/functions/fnc_handleTaskRewards.sqf +++ b/arma/server/addons/task/functions/fnc_handleTaskRewards.sqf @@ -201,29 +201,16 @@ private _grantOrgFleet = { }; private _equipment = _rewards getOrDefault ["equipment", []]; -if (count _equipment > 0) then { - ["equipment", _equipment] call _grantOrgAssets; -}; - -private _supplies = _rewards getOrDefault ["supplies", []]; -if (count _supplies > 0) then { - ["supplies", _supplies] call _grantOrgAssets; -}; - -private _weapons = _rewards getOrDefault ["weapons", []]; -if (count _weapons > 0) then { - ["weapons", _weapons] call _grantOrgAssets; -}; - private _special = _rewards getOrDefault ["special", []]; -if (count _special > 0) then { - ["special", _special] call _grantOrgAssets; -}; - +private _supplies = _rewards getOrDefault ["supplies", []]; private _vehicles = _rewards getOrDefault ["vehicles", []]; -if (count _vehicles > 0) then { - [_vehicles] call _grantOrgFleet; -}; +private _weapons = _rewards getOrDefault ["weapons", []]; + +if (_equipment isNotEqualTo []) then { ["equipment", _equipment] call _grantOrgAssets; }; +if (_supplies isNotEqualTo []) then {["supplies", _supplies] call _grantOrgAssets; }; +if (_weapons isNotEqualTo []) then { ["weapons", _weapons] call _grantOrgAssets; }; +if (_special isNotEqualTo []) then { ["special", _special] call _grantOrgAssets; }; +if (_vehicles isNotEqualTo []) then { [_vehicles] call _grantOrgFleet; }; if (_success) then { private _orgName = ""; diff --git a/arma/server/addons/task/functions/fnc_startTask.sqf b/arma/server/addons/task/functions/fnc_startTask.sqf index b108747..89c08af 100644 --- a/arma/server/addons/task/functions/fnc_startTask.sqf +++ b/arma/server/addons/task/functions/fnc_startTask.sqf @@ -96,9 +96,13 @@ if (_taskType isEqualTo "" || { _taskID isEqualTo "" }) exitWith { private _iedTimer = _taskParams getOrDefault ["iedTimer", 0]; { - private _role = _x; + private _role = _x; private _objects = _entities getOrDefault [_role, []]; { + if !(_x isEqualType objNull) then { + ["WARNING", format ["startTask: skipping non-object entity for role '%1' in task '%2': %3", _role, _taskID, _x]] call EFUNC(common,log); + continue; + }; if (isNull _x) then { continue; }; switch (_role) do { case "targets": { [_x, _taskID] call FUNC(makeTarget); }; diff --git a/arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf b/arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf new file mode 100644 index 0000000..23e7649 --- /dev/null +++ b/arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf @@ -0,0 +1,171 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype attack task class. + * + * Example: + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\TaskInstanceBaseClass.sqf"; + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\AttackTaskBaseClass.sqf"; + * + * private _task = createHashMapObject [ + * GVAR(AttackTaskBaseClass), + * [ + * "task_attack_review", + * createHashMapFromArray [ + * ["targets", [unit1, unit2, unit3]] + * ], + * createHashMapFromArray [ + * ["limitSuccess", 3], + * ["timeLimit", 900], + * ["funds", 50000], + * ["ratingSuccess", 25] + * ] + * ] + * ]; + * + * [_task] spawn { + * params ["_task"]; + * _task call ["runLoop", []]; + * }; + * _task = nil; // Safe after the spawned closure has captured the reference. + * + * Note: + * `runLoop` uses `sleep`, so it must be entered from scheduled code. + */ + +#pragma hemtt ignore_variables ["_self"] + +GVAR(AttackTaskBaseClass) = createHashMapFromArray [ + ["#base", GVAR(TaskInstanceBaseClass)], + ["#type", "AttackTaskBaseClass"], + ["#create", compileFinal { + params [ + ["_taskID", "", [""]], + ["_entities", createHashMap, [createHashMap]], + ["_taskParams", createHashMap, [createHashMap]] + ]; + + _self call ["initializeBaseState", [_taskID, "attack", _entities, _taskParams]]; + + private _targets = +(_entities getOrDefault ["targets", []]); + private _requiredKills = _taskParams getOrDefault ["limitSuccess", -1]; + if (_requiredKills < 0) then { _requiredKills = count _targets; }; + + private _maxTargetLosses = _taskParams getOrDefault ["limitFail", -1]; + if (_maxTargetLosses < 0) then { _maxTargetLosses = count _targets; }; + + _self set ["targets", _targets]; + _self set ["requiredKills", _requiredKills]; + _self set ["maxTargetLosses", _maxTargetLosses]; + _self set ["timeLimit", _taskParams getOrDefault ["timeLimit", 0]]; + + // Review-only registry entry to demonstrate where #delete is useful. + missionNamespace setVariable [_taskID, _self]; + }], + ["#delete", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + if (_taskID isNotEqualTo "") then { + missionNamespace setVariable [_taskID, nil]; + }; + }], + ["countKilledTargets", compileFinal { + private _targets = _self getOrDefault ["targets", []]; + { !alive _x } count _targets + }], + ["tick", compileFinal { + private _startedAt = _self getOrDefault ["startedAt", -1]; + private _timeLimit = _self getOrDefault ["timeLimit", 0]; + private _targetsKilled = _self call ["countKilledTargets", []]; + private _requiredKills = _self getOrDefault ["requiredKills", 0]; + private _maxTargetLosses = _self getOrDefault ["maxTargetLosses", 0]; + private _timeExpired = false; + + if (_timeLimit > 0 && { _startedAt >= 0 }) then { + _timeExpired = (serverTime - _startedAt) >= _timeLimit; + }; + + createHashMapFromArray [ + ["targetsKilled", _targetsKilled], + ["requiredKills", _requiredKills], + ["maxTargetLosses", _maxTargetLosses], + ["timeExpired", _timeExpired], + ["shouldFail", _timeExpired && { _targetsKilled < _requiredKills }], + ["shouldSucceed", _targetsKilled >= _requiredKills] + ] + }], + ["runLoop", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + private _targets = _self getOrDefault ["targets", []]; + private _timeLimit = _self getOrDefault ["timeLimit", 0]; + private _rewardData = _self getOrDefault ["rewardData", createHashMap]; + private _ratingFail = _rewardData getOrDefault ["ratingFail", 0]; + private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; + private _funds = _rewardData getOrDefault ["funds", 0]; + private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; + private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; + + waitUntil { + sleep 1; + GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; + count _targets > 0 + }; + + if (_timeLimit isNotEqualTo 0) then { + waitUntil { + sleep 1; + GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] + }; + }; + + _self call ["markActive", []]; + + while { (_self call ["getStatus", []]) isEqualTo "active" } do { + GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; + + private _snapshot = _self call ["tick", []]; + + if (_snapshot getOrDefault ["shouldFail", false]) exitWith { + _self call ["markFailed", ["Attack fail conditions met.", _snapshot]]; + }; + + if (_snapshot getOrDefault ["shouldSucceed", false]) exitWith { + _self call ["markSucceeded", [_snapshot]]; + }; + + sleep 1; + }; + + if ((_self call ["getStatus", []]) isEqualTo "failed") then { + { deleteVehicle _x } forEach _targets; + + [_taskID, "FAILED"] call BFUNC(taskSetState); + GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; + + sleep 1; + + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + + if (_endFail) then { ["MissionFail", false] remoteExec ["BIS_fnc_endMission", playerSide]; }; + } else { + { deleteVehicle _x } forEach _targets; + + [_taskID, _rewardData] call FUNC(handleTaskRewards); + [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); + GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; + + sleep 1; + + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_funds] call EFUNC(common,formatNumber)]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + + if (_endSuccess) then { ["MissionSuccess", true] remoteExec ["BIS_fnc_endMission", playerSide]; }; + }; + + true + }] +]; diff --git a/arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf b/arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf new file mode 100644 index 0000000..ea89bf3 --- /dev/null +++ b/arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf @@ -0,0 +1,119 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype defuse task class. + * + * Example: + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\TaskInstanceBaseClass.sqf"; + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\DefuseTaskBaseClass.sqf"; + * + * private _task = createHashMapObject [ + * GVAR(DefuseTaskBaseClass), + * [ + * "task_defuse_review", + * createHashMapFromArray [ + * ["ieds", [ied1, ied2]], + * ["protected", [truck1]] + * ], + * createHashMapFromArray [ + * ["limitSuccess", 2], + * ["limitFail", 1], + * ["iedTimer", 300], + * ["funds", 75000], + * ["ratingSuccess", 30] + * ] + * ] + * ]; + * + * [_task] spawn { + * params ["_task"]; + * _task call ["runLoop", []]; + * }; + * + * Note: + * `runLoop` uses `sleep`, so it must be entered from scheduled code. + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(DefuseTaskBaseClass) = createHashMapFromArray [ + ["#base", GVAR(TaskInstanceBaseClass)], + ["#type", "DefuseTaskBaseClass"], + ["#create", compileFinal { + params [ + ["_taskID", "", [""]], + ["_entities", createHashMap, [createHashMap]], + ["_taskParams", createHashMap, [createHashMap]] + ]; + + _self call ["initializeBaseState", [_taskID, "defuse", _entities, _taskParams]]; + + 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]]; + + // Review-only registry entry to demonstrate where #delete is useful. + missionNamespace setVariable [_taskID, _self]; + }], + ["#delete", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + if (_taskID isNotEqualTo "") then { + missionNamespace setVariable [_taskID, nil]; + }; + }], + ["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 + }] +]; diff --git a/arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf b/arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf new file mode 100644 index 0000000..ddb3efd --- /dev/null +++ b/arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf @@ -0,0 +1,87 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype base class for object-based entity controllers. + * + * Example: + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\EntityControllerBaseClass.sqf"; + * + * private _controller = createHashMapObject [ + * GVAR(EntityControllerBaseClass), + * [ + * "task_review_001", + * hostage1, + * "custom", + * createHashMapFromArray [ + * ["radius", 2] + * ] + * ] + * ]; + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(EntityControllerBaseClass) = createHashMapFromArray [ + ["#type", "EntityControllerBaseClass"], + ["initializeControllerState", compileFinal { + params [ + ["_taskID", "", [""]], + ["_entity", objNull, [objNull]], + ["_controllerType", "custom", [""]], + ["_controllerParams", createHashMap, [createHashMap]] + ]; + + _self set ["taskID", _taskID]; + _self set ["entity", _entity]; + _self set ["controllerType", _controllerType]; + _self set ["controllerParams", _controllerParams]; + _self set ["status", "created"]; + _self set ["startedAt", -1]; + _self set ["finishedAt", -1]; + true + }], + ["#create", compileFinal { + private _taskID = ""; + private _entity = objNull; + private _controllerType = "custom"; + private _controllerParams = createHashMap; + + if (_this isEqualType [] && { count _this > 0 }) then { + _taskID = _this param [0, "", [""]]; + _entity = _this param [1, objNull, [objNull]]; + + if ((count _this > 2) && { (_this select 2) isEqualType "" }) then { + _controllerType = _this param [2, "custom", [""]]; + _controllerParams = _this param [3, createHashMap, [createHashMap]]; + } else { + _controllerParams = _this param [2, createHashMap, [createHashMap]]; + }; + }; + + _self call ["initializeControllerState", [_taskID, _entity, _controllerType, _controllerParams]]; + }], + ["getEntity", compileFinal { + _self getOrDefault ["entity", objNull] + }], + ["markActive", compileFinal { + _self set ["status", "active"]; + _self set ["startedAt", serverTime]; + true + }], + ["markFinished", compileFinal { + _self set ["status", "finished"]; + _self set ["finishedAt", serverTime]; + true + }], + ["markAborted", compileFinal { + _self set ["status", "aborted"]; + _self set ["finishedAt", serverTime]; + true + }], + ["cleanup", compileFinal { + false + }], + ["runLoop", compileFinal { + false + }] +]; diff --git a/arma/server/addons/task/prototypes/HostageEntityController.sqf b/arma/server/addons/task/prototypes/HostageEntityController.sqf new file mode 100644 index 0000000..a538e43 --- /dev/null +++ b/arma/server/addons/task/prototypes/HostageEntityController.sqf @@ -0,0 +1,127 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype hostage entity controller. + * + * Example: + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\EntityControllerBaseClass.sqf"; + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\HostageEntityController.sqf"; + * + * private _controller = createHashMapObject [ + * GVAR(HostageEntityController), + * [ + * "task_hostage_review", + * hostage1, + * createHashMapFromArray [ + * ["rescueRadius", 2], + * ["loopAnimation", "acts_executionvictim_loop"], + * ["rescueAnimation", "acts_executionvictim_unbow"] + * ] + * ] + * ]; + * + * [_controller] spawn { + * params ["_controller"]; + * _controller call ["runLoop", []]; + * }; + * + * Note: + * `runLoop` uses `sleep`, so it must be entered from scheduled code. + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(HostageEntityController) = createHashMapFromArray [ + ["#base", GVAR(EntityControllerBaseClass)], + ["#type", "HostageEntityController"], + ["#create", compileFinal { + params [ + ["_taskID", "", [""]], + ["_entity", objNull, [objNull]], + ["_controllerParams", createHashMap, [createHashMap]] + ]; + + _self call ["initializeControllerState", [_taskID, _entity, "hostage", _controllerParams]]; + _self set ["rescueRadius", _controllerParams getOrDefault ["rescueRadius", 2]]; + _self set ["loopAnimation", _controllerParams getOrDefault ["loopAnimation", "acts_executionvictim_loop"]]; + _self set ["rescueAnimation", _controllerParams getOrDefault ["rescueAnimation", "acts_executionvictim_unbow"]]; + + private _netID = if (isNull _entity) then { "" } else { netId _entity }; + if (_netID isNotEqualTo "") then { + private _controllerKey = format ["hostage_controller_%1", _netID]; + missionNamespace setVariable [_controllerKey, _self]; + }; + }], + ["#delete", compileFinal { + private _entity = _self getOrDefault ["entity", objNull]; + if (!isNull _entity) then { + private _controllerKey = format ["hostage_controller_%1", netId _entity]; + missionNamespace setVariable [_controllerKey, nil]; + }; + }], + ["applyInitialState", compileFinal { + private _entity = _self getOrDefault ["entity", objNull]; + if (isNull _entity || { !alive _entity }) exitWith { false }; + + _entity setCaptive true; + _entity enableAIFeature ["MOVE", false]; + _entity playMove (_self getOrDefault ["loopAnimation", "acts_executionvictim_loop"]); + true + }], + ["findNearbyRescuer", compileFinal { + private _entity = _self getOrDefault ["entity", objNull]; + if (isNull _entity || { !alive _entity }) exitWith { objNull }; + + private _radius = _self getOrDefault ["rescueRadius", 2]; + private _nearPlayers = allPlayers inAreaArray [ASLToAGL getPosASL _entity, _radius, _radius, 0, false, 2]; + if (_nearPlayers isEqualTo []) exitWith { objNull }; + + _nearPlayers select 0 + }], + ["transitionToRescued", compileFinal { + params [["_rescuer", objNull, [objNull]]]; + + private _entity = _self getOrDefault ["entity", objNull]; + if (isNull _entity || { isNull _rescuer }) exitWith { false }; + + [_entity] joinSilent (group _rescuer); + _entity setCaptive true; + _entity enableAIFeature ["MOVE", true]; + _entity playMove (_self getOrDefault ["rescueAnimation", "acts_executionvictim_unbow"]); + true + }], + ["runLoop", compileFinal { + private _entity = _self getOrDefault ["entity", objNull]; + if (isNull _entity) exitWith { + _self call ["markAborted", []]; + false + }; + + _self call ["markActive", []]; + + if !(_self call ["applyInitialState", []]) exitWith { + _self call ["markAborted", []]; + false + }; + + private _rescuer = objNull; + waitUntil { + sleep 1; + + if (isNull _entity || { !alive _entity }) exitWith { true }; + + _rescuer = _self call ["findNearbyRescuer", []]; + !isNull _rescuer + }; + + if (isNull _entity || { !alive _entity }) exitWith { + _self call ["markAborted", []]; + false + }; + + _self call ["transitionToRescued", [_rescuer]]; + _self call ["markFinished", []]; + true + }] +]; diff --git a/arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf b/arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf new file mode 100644 index 0000000..2d9aa4a --- /dev/null +++ b/arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf @@ -0,0 +1,339 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype hostage task class. + * + * Example: + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\TaskInstanceBaseClass.sqf"; + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\HostageTaskBaseClass.sqf"; + * + * private _task = createHashMapObject [ + * GVAR(HostageTaskBaseClass), + * [ + * "task_hostage_review", + * createHashMapFromArray [ + * ["hostages", [hostage1, hostage2]], + * ["shooters", [shooter1, shooter2]] + * ], + * createHashMapFromArray [ + * ["extractionZone", "hostage_extract"], + * ["limitSuccess", 2], + * ["limitFail", 1], + * ["execution", true], + * ["timeLimit", 900], + * ["funds", 100000], + * ["ratingSuccess", 50] + * ] + * ] + * ]; + * + * [_task] spawn { + * params ["_task"]; + * _task call ["runLoop", []]; + * }; + * + * Note: + * `runLoop` and the wait helpers use `sleep`, so they must be entered from + * scheduled code. + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(HostageTaskBaseClass) = createHashMapFromArray [ + ["#base", GVAR(TaskInstanceBaseClass)], + ["#type", "HostageTaskBaseClass"], + ["#create", compileFinal { + params [ + ["_taskID", "", [""]], + ["_entities", createHashMap, [createHashMap]], + ["_taskParams", createHashMap, [createHashMap]] + ]; + + _self call ["initializeBaseState", [_taskID, "hostage", _entities, _taskParams]]; + + 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]; + _self set ["hostageControllers", []]; + + _self call ["createHostageControllers", []]; + + // Review-only registry entry to demonstrate where #delete is useful. + missionNamespace setVariable [_taskID, _self]; + }], + ["#delete", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + if (_taskID isNotEqualTo "") then { + missionNamespace setVariable [_taskID, nil]; + }; + }], + ["createHostageControllers", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + private _hostages = _self getOrDefault ["hostages", []]; + private _controllers = []; + + { + _controllers pushBack (createHashMapObject [ + GVAR(HostageEntityController), + [ + _taskID, + _x, + createHashMapFromArray [ + ["rescueRadius", 2], + ["loopAnimation", "acts_executionvictim_loop"], + ["rescueAnimation", "acts_executionvictim_unbow"] + ] + ] + ]); + } forEach _hostages; + + _self set ["hostageControllers", _controllers]; + _controllers + }], + ["startHostageControllers", compileFinal { + private _controllers = _self getOrDefault ["hostageControllers", []]; + + { + [_x] spawn { + params ["_controller"]; + _controller call ["runLoop", []]; + }; + } forEach _controllers; + + true + }], + ["refreshEntitiesFromStore", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + if (_taskID isEqualTo "") exitWith { false }; + + private _hostages = GVAR(TaskStore) call ["getTaskEntities", ["hostages", _taskID]]; + private _shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]]; + + _self set ["hostages", _hostages]; + _self set ["shooters", _shooters]; + true + }], + ["trackParticipants", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + if (_taskID isEqualTo "") exitWith { false }; + + private _hostages = _self getOrDefault ["hostages", []]; + private _shooters = _self getOrDefault ["shooters", []]; + private _extZone = _self getOrDefault ["extractionZone", ""]; + + GVAR(TaskStore) call ["trackParticipants", [_taskID, _hostages + _shooters, _extZone, 250]]; + true + }], + ["waitForRequiredEntities", compileFinal { + waitUntil { + sleep 1; + _self call ["refreshEntitiesFromStore", []]; + count (_self getOrDefault ["hostages", []]) > 0 + }; + + waitUntil { + sleep 1; + _self call ["refreshEntitiesFromStore", []]; + _self call ["trackParticipants", []]; + count (_self getOrDefault ["shooters", []]) > 0 + }; + + private _hostages = _self getOrDefault ["hostages", []]; + private _taskParams = _self getOrDefault ["taskParams", createHashMap]; + 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 ["requiredRescues", _requiredRescues]; + _self set ["maxHostageLosses", _maxHostageLosses]; + true + }], + ["waitForAssignmentIfTimed", compileFinal { + private _timeLimit = _self getOrDefault ["timeLimit", 0]; + private _taskID = _self getOrDefault ["taskID", ""]; + + if (_timeLimit <= 0 || { _taskID isEqualTo "" }) exitWith { true }; + + waitUntil { + sleep 1; + GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] + }; + + true + }], + ["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 }) || { (_shootersAlive <= 0) && { _inZone >= _requiredRescues } && { _killed < _maxHostageLosses } }] + ] + }], + ["handleFailureOutcome", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + private _hostages = _self getOrDefault ["hostages", []]; + private _shooters = _self getOrDefault ["shooters", []]; + private _rewardData = _self getOrDefault ["rewardData", createHashMap]; + private _ratingFail = _rewardData getOrDefault ["ratingFail", 0]; + private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; + private _cbrn = _self getOrDefault ["cbrn", false]; + private _hostage = _self getOrDefault ["execution", false]; + private _cbrnZone = _self getOrDefault ["cbrnZone", ""]; + + if (_cbrn && { _cbrnZone isNotEqualTo "" }) then { + "SmokeShellYellow" createVehicle getMarkerPos _cbrnZone; + + sleep 5; + + { + if (captive _x) then { + _x setDamage 0.9; + _x playMove "acts_executionvictim_kill_end"; + + sleep 2.75; + + _x setDamage 1; + }; + } forEach _hostages; + }; + + if (_hostage) then { + { + _x enableAIFeature ["MOVE", true]; + _x playMove ""; + } forEach _shooters; + + sleep 1; + + { _x setCaptive false; } forEach _hostages; + + sleep 5; + }; + + { deleteVehicle _x } forEach _hostages; + { deleteVehicle _x } forEach _shooters; + + [_taskID, "FAILED"] call BFUNC(taskSetState); + + sleep 1; + + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + + if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; + true + }], + ["handleSuccessOutcome", compileFinal { + private _taskID = _self getOrDefault ["taskID", ""]; + private _hostages = _self getOrDefault ["hostages", []]; + private _shooters = _self getOrDefault ["shooters", []]; + private _rewardData = _self getOrDefault ["rewardData", createHashMap]; + private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; + private _funds = _rewardData getOrDefault ["funds", 0]; + private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; + + { deleteVehicle _x } forEach _hostages; + { deleteVehicle _x } forEach _shooters; + + [_taskID, _rewardData] call FUNC(handleTaskRewards); + [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); + + sleep 1; + + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_funds] call EFUNC(common,formatNumber)]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + + if (_endSuccess) then { ["MissionSuccess", true] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; + true + }], + ["runLoop", compileFinal { + _self call ["waitForRequiredEntities", []]; + _self call ["startHostageControllers", []]; + _self call ["waitForAssignmentIfTimed", []]; + _self call ["markActive", []]; + + while { (_self call ["getStatus", []]) isEqualTo "active" } do { + _self call ["trackParticipants", []]; + 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; + }; + + if ((_self call ["getStatus", []]) isEqualTo "failed") then { + _self call ["handleFailureOutcome", []]; + } else { + _self call ["handleSuccessOutcome", []]; + }; + + true + }] +]; diff --git a/arma/server/addons/task/prototypes/README.md b/arma/server/addons/task/prototypes/README.md index 1bb039a..98a2d0a 100644 --- a/arma/server/addons/task/prototypes/README.md +++ b/arma/server/addons/task/prototypes/README.md @@ -5,21 +5,37 @@ instances. They are not part of runtime initialization. Current prototypes: - `TaskInstanceBaseClass` +- `EntityControllerBaseClass` +- `AttackTaskBaseClass` - `HostageTaskBaseClass` +- `HostageEntityController` - `DefuseTaskBaseClass` Source: - [taskObjectPrototypes.sqf](./taskObjectPrototypes.sqf) +- [TaskInstanceBaseClass.sqf](./TaskInstanceBaseClass.sqf) +- [EntityControllerBaseClass.sqf](./EntityControllerBaseClass.sqf) +- [AttackTaskBaseClass.sqf](./AttackTaskBaseClass.sqf) +- [HostageTaskBaseClass.sqf](./HostageTaskBaseClass.sqf) +- [HostageEntityController.sqf](./HostageEntityController.sqf) +- [DefuseTaskBaseClass.sqf](./DefuseTaskBaseClass.sqf) Purpose: - show what per-task instance objects could look like +- show what per-entity heartbeat/controller 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 +- keep shared lifecycle and reward initialization in `TaskInstanceBaseClass` + so concrete task prototypes only define task-specific state +- keep heartbeat-style AI/object behavior in separate entity controllers instead + of mixing it into task outcome loops Important design choice: - these prototypes use explicit `markSucceeded`, `markFailed`, and `cleanup` methods instead of relying on `#delete` +- task loops that use `sleep` or `waitUntil` with `sleep` must be started from + scheduled code, typically via `spawn` That is intentional. `createHashMapObject` destructor timing is reference-based, so `#delete` is not a good primitive for mission-critical task completion or diff --git a/arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf b/arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf new file mode 100644 index 0000000..a0b084d --- /dev/null +++ b/arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf @@ -0,0 +1,120 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype base class for object-based task instances. + * + * Example: + * call compile preprocessFileLineNumbers + * "\forge\forge_server\addons\task\prototypes\TaskInstanceBaseClass.sqf"; + * + * private _task = createHashMapObject [ + * GVAR(TaskInstanceBaseClass), + * [ + * "task_review_001", + * "custom", + * createHashMap, + * createHashMapFromArray [ + * ["funds", 50000], + * ["ratingSuccess", 25] + * ] + * ] + * ]; + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(TaskInstanceBaseClass) = createHashMapFromArray [ + ["#type", "TaskInstanceBaseClass"], + ["initializeBaseState", compileFinal { + params [ + ["_taskID", "", [""]], + ["_taskType", "custom", [""]], + ["_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 ["resultSnapshot", createHashMap]; + _self set ["rewardData", createHashMapFromArray [ + ["funds", _taskParams getOrDefault ["funds", 0]], + ["ratingFail", _taskParams getOrDefault ["ratingFail", 0]], + ["ratingSuccess", _taskParams getOrDefault ["ratingSuccess", 0]], + ["equipment", _taskParams getOrDefault ["equipment", []]], + ["supplies", _taskParams getOrDefault ["supplies", []]], + ["weapons", _taskParams getOrDefault ["weapons", []]], + ["vehicles", _taskParams getOrDefault ["vehicles", []]], + ["special", _taskParams getOrDefault ["special", []]] + ]]; + true + }], + ["#create", compileFinal { + private _taskID = ""; + private _taskType = "custom"; + private _entities = createHashMap; + private _taskParams = createHashMap; + + if (_this isEqualType [] && { count _this > 0 }) then { + _taskID = _this param [0, "", [""]]; + + if ((count _this > 1) && { (_this select 1) isEqualType "" }) then { + _taskType = _this param [1, "custom", [""]]; + _entities = _this param [2, createHashMap, [createHashMap]]; + _taskParams = _this param [3, createHashMap, [createHashMap]]; + } else { + _entities = _this param [1, createHashMap, [createHashMap]]; + _taskParams = _this param [2, createHashMap, [createHashMap]]; + }; + }; + + _self call ["initializeBaseState", [_taskID, _taskType, _entities, _taskParams]]; + }], + ["getTaskID", compileFinal { + _self getOrDefault ["taskID", ""] + }], + ["getTaskType", compileFinal { + _self getOrDefault ["taskType", ""] + }], + ["getStatus", compileFinal { + _self getOrDefault ["status", "created"] + }], + ["getRewardData", compileFinal { + _self getOrDefault ["rewardData", createHashMap] + }], + ["markActive", compileFinal { + _self set ["status", "active"]; + _self set ["startedAt", serverTime]; + true + }], + ["markSucceeded", compileFinal { + params [["_resultSnapshot", createHashMap, [createHashMap]]]; + + _self set ["status", "succeeded"]; + _self set ["finishedAt", serverTime]; + _self set ["resultSnapshot", _resultSnapshot]; + true + }], + ["markFailed", compileFinal { + params [["_reason", "", [""]], ["_resultSnapshot", createHashMap, [createHashMap]]]; + + _self set ["status", "failed"]; + _self set ["finishedAt", serverTime]; + _self set ["failureReason", _reason]; + _self set ["resultSnapshot", _resultSnapshot]; + true + }], + ["cleanup", compileFinal { + false + }], + ["tick", compileFinal { + createHashMap + }], + ["runLoop", compileFinal { + false + }] +]; diff --git a/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf b/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf index f87bc57..bb834ba 100644 --- a/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf +++ b/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf @@ -1,7 +1,7 @@ #include "..\script_component.hpp" /* - * Review-only prototype for object-based task instances. + * Review-only prototype loader 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 @@ -30,261 +30,18 @@ * ]; */ -#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 - }] -]; +call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\TaskInstanceBaseClass.sqf"; +call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\EntityControllerBaseClass.sqf"; +call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\AttackTaskBaseClass.sqf"; +call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\HostageTaskBaseClass.sqf"; +call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\HostageEntityController.sqf"; +call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\DefuseTaskBaseClass.sqf"; createHashMapFromArray [ ["TaskInstanceBaseClass", GVAR(TaskInstanceBaseClass)], + ["EntityControllerBaseClass", GVAR(EntityControllerBaseClass)], + ["AttackTaskBaseClass", GVAR(AttackTaskBaseClass)], ["HostageTaskBaseClass", GVAR(HostageTaskBaseClass)], + ["HostageEntityController", GVAR(HostageEntityController)], ["DefuseTaskBaseClass", GVAR(DefuseTaskBaseClass)] ]