- Updated fnc_defuseModule, fnc_deliveryModule, fnc_destroyModule, fnc_hostageModule, fnc_hvtModule to enhance parameter retrieval and logging. - Introduced error handling for missing task IDs across modules. - Consolidated task initialization logic into fnc_startTask for better maintainability. - Added fnc_cargoModule as a sync target for cargo entities in delivery tasks. - Improved logging to provide clearer insights into task parameters and synced entities.
351 lines
14 KiB
Plaintext
351 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; };
|
|
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]
|
|
]
|
|
}],
|
|
["startAttackMission", compileFinal {
|
|
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
|
|
private _locationData = _self call ["selectAttackLocation"];
|
|
if (_locationData isEqualTo createHashMap) exitWith { "" };
|
|
|
|
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)];
|
|
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 _success = [
|
|
"attack",
|
|
_taskID,
|
|
_position,
|
|
format ["Attack: %1", _locationKey],
|
|
format ["Eliminate hostile forces operating near %1.", _locationKey],
|
|
createHashMapFromArray [["targets", _units]],
|
|
createHashMapFromArray [
|
|
["limitFail", 0],
|
|
["limitSuccess", count _units],
|
|
["funds", _rewardRange call BFUNC(randomNum)],
|
|
["ratingFail", _penaltyRange call BFUNC(randomNum)],
|
|
["ratingSuccess", _reputationRange call BFUNC(randomNum)],
|
|
["endSuccess", false],
|
|
["endFail", false],
|
|
["timeLimit", _timeRange call BFUNC(randomNum)],
|
|
["equipment", _rewards get "equipment"],
|
|
["supplies", _rewards get "supplies"],
|
|
["weapons", _rewards get "weapons"],
|
|
["vehicles", _rewards get "vehicles"],
|
|
["special", _rewards get "special"]
|
|
],
|
|
0,
|
|
"",
|
|
"mission_manager"
|
|
] call FUNC(startTask);
|
|
|
|
if !(_success) exitWith { "" };
|
|
|
|
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
|
|
_activeMissionRegistry set [_taskID, createHashMapFromArray [
|
|
["locationKey", _locationKey],
|
|
["startedAt", serverTime]
|
|
]];
|
|
_self set ["activeMissionRegistry", _activeMissionRegistry];
|
|
|
|
_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);
|