#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; }; private _attackLocationCount = _self call ["getAttackLocationCount", []]; if (_attackLocationCount > 0) then { _maxConcurrent = _maxConcurrent min _attackLocationCount; }; _maxConcurrent }], ["getAttackLocationCount", compileFinal { private _locationsConfig = _self getOrDefault ["locationsConfig", configNull]; if (isNull _locationsConfig) exitWith { 0 }; private _count = 0; { if ("attack" in getArray (_x >> "suitable")) then { _count = _count + 1; }; } forEach ("true" configClasses _locationsConfig); _count }], ["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]]; private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_x]]; if (_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) 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);