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.
This commit is contained in:
Jacob Schmidt 2026-04-30 22:48:52 -05:00
parent 3535484079
commit 1b000af2e6
11 changed files with 1002 additions and 275 deletions

Binary file not shown.

View File

@ -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 = "";

View File

@ -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); };

View File

@ -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
}]
];

View File

@ -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
}]
];

View File

@ -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
}]
];

View File

@ -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
}]
];

View File

@ -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
}]
];

View File

@ -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

View File

@ -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
}]
];

View File

@ -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)]
]