forge/arma/server/addons/task/functions/fnc_missionManager.sqf
Jacob Schmidt 45a4f7460a Integrate task contracts and CAD UI pipeline
- add the imported server task addon to the current framework with task ownership, task catalog, mission-manager attack generation, org-owned reward routing, participant notifications, and reputation syncing
- restructure org persistence so core org data, assets, fleet, and members are handled through the current Redis/extension model with matching Rust repository and service updates
- wire the client CAD addon into the framework, actor device action, shared web UI bridge pattern, and task listing/acceptance flow
- add a source-driven CAD web UI layout with ui.config.mjs and extend the shared web UI builder to support custom HTML template pages for multi-surface UIs
2026-03-28 02:20:34 -05:00

370 lines
14 KiB
Plaintext

#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Manages attack-only dynamic mission generation.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* [] call forge_server_task_fnc_missionManager
*
* Public: No
*/
#pragma hemtt ignore_variables ["_self"]
GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [
["#type", "MissionManagerBaseClass"],
["#create", compileFinal {
private _missionConfig = missionConfigFile >> "CfgMissions";
_self set ["missionConfig", _missionConfig];
_self set ["locationsConfig", (_missionConfig >> "Locations")];
_self set ["aiGroupsConfig", (_missionConfig >> "AIGroups")];
_self set ["attackConfig", (_missionConfig >> "MissionTypes" >> "Attack")];
_self set ["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")];
_self set ["missionInterval", getNumber (_missionConfig >> "missionInterval")];
_self set ["recentLocationRegistry", createHashMap];
_self set ["activeMissionRegistry", createHashMap];
}],
["getMissionInterval", compileFinal {
private _interval = _self getOrDefault ["missionInterval", 300];
if (_interval <= 0) then { _interval = 300; };
_interval
}],
["getMaxConcurrentMissions", compileFinal {
private _maxConcurrent = _self getOrDefault ["maxConcurrentMissions", 1];
if (_maxConcurrent <= 0) then { _maxConcurrent = 1; };
_maxConcurrent
}],
["getLocationReuseCooldown", compileFinal {
private _missionConfig = _self getOrDefault ["missionConfig", configNull];
private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown");
if (_cooldown <= 0) then { _cooldown = 900; };
_cooldown
}],
["getActiveMissionIds", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
keys _activeMissionRegistry
}],
["getActiveLocationKeys", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
private _locationKeys = [];
{
private _locationKey = _y getOrDefault ["locationKey", ""];
if (_locationKey isNotEqualTo "") then {
_locationKeys pushBackUnique _locationKey;
};
} forEach _activeMissionRegistry;
_locationKeys
}],
["buildAttackSpawnPosition", compileFinal {
params [["_locationConfig", configNull, [configNull]]];
if (isNull _locationConfig) exitWith { [0, 0, 0] };
private _center = getArray (_locationConfig >> "position");
private _radius = getNumber (_locationConfig >> "radius");
if (_radius <= 0) exitWith { _center };
private _spawnPosition = +_center;
private _attempts = 0;
while { _attempts < 8 } do {
private _angle = random 360;
private _distance = (_radius * 0.2) + random (_radius * 0.65);
private _candidate = [
(_center # 0) + ((sin _angle) * _distance),
(_center # 1) + ((cos _angle) * _distance),
_center param [2, 0]
];
if !(surfaceIsWater _candidate) exitWith {
_spawnPosition = _candidate;
};
_attempts = _attempts + 1;
};
_spawnPosition
}],
["selectAttackLocation", compileFinal {
private _locationsConfig = _self getOrDefault ["locationsConfig", configNull];
private _locations = [];
private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap];
private _activeLocationKeys = _self call ["getActiveLocationKeys", []];
private _reuseCooldown = _self call ["getLocationReuseCooldown", []];
private _now = serverTime;
{
private _locationKey = configName _x;
private _lastUsed = _recentLocationRegistry getOrDefault [_locationKey, -1];
private _isCoolingDown = (_lastUsed >= 0) && { (_now - _lastUsed) < _reuseCooldown };
if (
"attack" in getArray (_x >> "suitable")
&& { !(_locationKey in _activeLocationKeys) }
&& { !_isCoolingDown }
) then {
_locations pushBack _x;
};
} forEach ("true" configClasses _locationsConfig);
if (_locations isEqualTo []) then {
{
if ("attack" in getArray (_x >> "suitable") && { !(configName _x in _activeLocationKeys) }) then {
_locations pushBack _x;
};
} forEach ("true" configClasses _locationsConfig);
};
if (_locations isEqualTo []) exitWith { createHashMap };
private _location = selectRandom _locations;
createHashMapFromArray [
["config", _location],
["key", configName _location],
["position", _self call ["buildAttackSpawnPosition", [_location]]]
]
}],
["spawnAttackGroup", compileFinal {
params [["_position", [0, 0, 0], [[]]]];
private _aiGroupsConfig = _self getOrDefault ["aiGroupsConfig", configNull];
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _groups = [];
{
if ("attack" in getArray (_x >> "suitable")) then {
_groups pushBack _x;
};
} forEach ("true" configClasses _aiGroupsConfig);
if (_groups isEqualTo []) exitWith { grpNull };
private _groupConfig = selectRandom _groups;
private _side = getText (_groupConfig >> "side");
private _group = createGroup (call compile _side);
private _minUnits = getNumber (_attackConfig >> "minUnits");
private _maxUnits = getNumber (_attackConfig >> "maxUnits");
if (_minUnits <= 0) then { _minUnits = 4; };
if (_maxUnits < _minUnits) then { _maxUnits = _minUnits; };
private _targetUnitCount = floor random [ _minUnits, ceil ((_minUnits + _maxUnits) / 2), _maxUnits + 1 ];
private _unitPool = [];
{
if ((getText (_x >> "side")) isNotEqualTo _side) then { continue; };
{
_unitPool pushBack createHashMapFromArray [
["vehicle", getText (_x >> "vehicle")],
["rank", getText (_x >> "rank")],
["position", getArray (_x >> "position")]
];
} forEach ("true" configClasses (_x >> "Units"));
} forEach _groups;
if (_unitPool isEqualTo []) exitWith {
deleteGroup _group;
grpNull
};
private _leaderPool = _unitPool select {
toUpperANSI (_x getOrDefault ["rank", "PRIVATE"]) in ["SERGEANT", "LIEUTENANT", "CAPTAIN", "MAJOR", "COLONEL"]
};
if (_leaderPool isEqualTo []) then { _leaderPool = +_unitPool; };
private _spawnDefs = [selectRandom _leaderPool];
for "_i" from 1 to (_targetUnitCount - 1) do {
_spawnDefs pushBack (selectRandom _unitPool);
};
{
private _unitClass = _x getOrDefault ["vehicle", ""];
if (_unitClass isEqualTo "") then { continue; };
private _unitOffset = +(_x getOrDefault ["position", [0, 0, 0]]);
if (count _unitOffset < 3) then { _unitOffset resize 3; };
_unitOffset set [0, (_unitOffset # 0) + (random 6 - 3)];
_unitOffset set [1, (_unitOffset # 1) + (random 6 - 3)];
private _unit = _group createUnit [_unitClass, _position vectorAdd _unitOffset, [], 0, "NONE"];
_unit setRank (_x getOrDefault ["rank", "PRIVATE"]);
} forEach _spawnDefs;
_group
}],
["rollRewards", compileFinal {
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _equipmentRewards = [];
private _supplyRewards = [];
private _weaponRewards = [];
private _vehicleRewards = [];
private _specialRewards = [];
{
private _category = _x;
{
_x params ["_item", "_chance"];
if (random 1 < _chance) then {
switch (_category) do {
case "equipment": { _equipmentRewards pushBack _item; };
case "supplies": { _supplyRewards pushBack _item; };
case "weapons": { _weaponRewards pushBack _item; };
case "vehicles": { _vehicleRewards pushBack _item; };
case "special": { _specialRewards pushBack _item; };
};
};
} forEach (getArray (_attackConfig >> "Rewards" >> _category));
} forEach ["equipment", "supplies", "weapons", "vehicles", "special"];
createHashMapFromArray [
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
}],
["createAttackTask", compileFinal {
params [
["_taskID", "", [""]],
["_position", [0, 0, 0], [[]]],
["_locationConfig", configNull, [configNull]]
];
if (_taskID isEqualTo "" || { isNull _locationConfig }) exitWith { false };
private _locationKey = configName _locationConfig;
private _locationType = getText (_locationConfig >> "type");
if (_locationType isEqualTo "") then { _locationType = "area"; };
[
west,
_taskID,
[
format ["Eliminate hostile forces operating near %1.", _locationKey],
format ["Attack: %1", _locationKey],
_locationType
],
_position,
"CREATED",
1,
true,
"attack"
] call BFUNC(taskCreate);
true
}],
["startAttackMission", compileFinal {
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _locationData = _self call ["selectAttackLocation"];
if (_locationData isEqualTo createHashMap) exitWith { "" };
private _location = _locationData getOrDefault ["config", configNull];
private _locationKey = _locationData getOrDefault ["key", ""];
private _position = _locationData getOrDefault ["position", [0, 0, 0]];
private _group = _self call ["spawnAttackGroup", [_position]];
if (isNull _group) exitWith { "" };
private _units = units _group;
if (_units isEqualTo []) exitWith {
deleteGroup _group;
""
};
private _taskID = format ["task_attack_%1", round (diag_tickTime * 1000)];
{
[_x, _taskID] call FUNC(makeTarget);
} forEach _units;
_self call ["createAttackTask", [_taskID, _position, _location]];
GVAR(TaskStore) call ["registerTaskCatalogEntry", [_taskID, createHashMapFromArray [
["type", "attack"],
["title", format ["Attack: %1", _locationKey]],
["description", format ["Eliminate hostile forces operating near %1.", _locationKey]],
["position", _position],
["locationKey", _locationKey],
["accepted", false],
["requesterUid", ""],
["orgID", "default"],
["source", "mission_manager"]
]]];
private _rewardRange = getArray (_attackConfig >> "Rewards" >> "money");
private _reputationRange = getArray (_attackConfig >> "Rewards" >> "reputation");
private _penaltyRange = getArray (_attackConfig >> "penalty");
private _timeRange = getArray (_attackConfig >> "timeLimit");
private _rewards = _self call ["rollRewards"];
private _params = [
_taskID,
0,
count _units,
_rewardRange call BFUNC(randomNum),
_penaltyRange call BFUNC(randomNum),
_reputationRange call BFUNC(randomNum),
false,
false,
_timeRange call BFUNC(randomNum),
_rewards get "equipment",
_rewards get "supplies",
_rewards get "weapons",
_rewards get "vehicles",
_rewards get "special"
];
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
_activeMissionRegistry set [_taskID, createHashMapFromArray [
["locationKey", _locationKey],
["startedAt", serverTime]
]];
_self set ["activeMissionRegistry", _activeMissionRegistry];
["attack", _params, 0, ""] spawn FUNC(handler);
_taskID
}],
["completeMission", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
private _missionRecord = _activeMissionRegistry getOrDefault [_taskID, createHashMap];
private _locationKey = _missionRecord getOrDefault ["locationKey", ""];
_activeMissionRegistry deleteAt _taskID;
_self set ["activeMissionRegistry", _activeMissionRegistry];
if (_locationKey isNotEqualTo "") then {
private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap];
_recentLocationRegistry set [_locationKey, serverTime];
_self set ["recentLocationRegistry", _recentLocationRegistry];
};
true
}]
];
GVAR(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)];
[{
{
private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]];
if (_status in ["succeeded", "failed"]) then {
GVAR(MissionManager) call ["completeMission", [_x]];
GVAR(TaskStore) call ["clearTaskStatus", [_x]];
};
} forEach (GVAR(MissionManager) call ["getActiveMissionIds", []]);
if (count (GVAR(MissionManager) call ["getActiveMissionIds", []]) >= (GVAR(MissionManager) call ["getMaxConcurrentMissions", []])) exitWith {};
private _taskID = GVAR(MissionManager) call ["startAttackMission", []];
if (_taskID isEqualTo "") exitWith {
["WARNING", "Mission manager failed to start an attack mission."] call EFUNC(common,log);
};
["INFO", format ["Mission manager started attack mission %1.", _taskID]] call EFUNC(common,log);
}, GVAR(MissionManager) call ["getMissionInterval", []], []] call CFUNC(addPerFrameHandler);