From 8ceec2bb44ecdd1d85013633d62d76d26065c5ee Mon Sep 17 00:00:00 2001 From: John Jordan Date: Sat, 8 Jun 2024 15:58:32 +0100 Subject: [PATCH] Add tank platoon support as alternative to CAS --- A3A/addons/core/CfgFunctions.hpp | 3 + A3A/addons/core/Stringtable.xml | 3 + .../Base/fn_getVehiclesGroundSupport.sqf | 10 +- .../CREATE/fn_createAttackForceLand.sqf | 7 +- .../core/functions/Supports/fn_SUP_tank.sqf | 51 +++++++ .../Supports/fn_SUP_tankAvailable.sqf | 24 ++++ .../functions/Supports/fn_SUP_tankRoutine.sqf | 128 ++++++++++++++++++ .../functions/Supports/fn_initSupports.sqf | 3 +- .../Supports/fn_showInterceptedSetupCall.sqf | 6 +- 9 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 A3A/addons/core/functions/Supports/fn_SUP_tank.sqf create mode 100644 A3A/addons/core/functions/Supports/fn_SUP_tankAvailable.sqf create mode 100644 A3A/addons/core/functions/Supports/fn_SUP_tankRoutine.sqf diff --git a/A3A/addons/core/CfgFunctions.hpp b/A3A/addons/core/CfgFunctions.hpp index c282ebaf2d..5de93ef3cf 100644 --- a/A3A/addons/core/CfgFunctions.hpp +++ b/A3A/addons/core/CfgFunctions.hpp @@ -665,6 +665,9 @@ class CfgFunctions class SUP_SAM {}; class SUP_SAMAvailable {}; class SUP_SAMRoutine {}; + class SUP_tank {}; + class SUP_tankAvailable {}; + class SUP_tankRoutine {}; class SUP_UAV {}; class SUP_UAVRoutine {}; }; diff --git a/A3A/addons/core/Stringtable.xml b/A3A/addons/core/Stringtable.xml index d8d9931bbd..c679fb4d68 100644 --- a/A3A/addons/core/Stringtable.xml +++ b/A3A/addons/core/Stringtable.xml @@ -10359,6 +10359,9 @@ 예상 설정 시간: %1분 Осталось времени: %1 мин. + + %1 just sent a tank platoon. + %1 is sending a spotting UAV. %1 세력이 감시형 UAV를 보냄 diff --git a/A3A/addons/core/functions/Base/fn_getVehiclesGroundSupport.sqf b/A3A/addons/core/functions/Base/fn_getVehiclesGroundSupport.sqf index e8a7773de9..aaf1972367 100644 --- a/A3A/addons/core/functions/Base/fn_getVehiclesGroundSupport.sqf +++ b/A3A/addons/core/functions/Base/fn_getVehiclesGroundSupport.sqf @@ -9,7 +9,7 @@ Arguments: Return value: [vehType, weight, vehType2, weight2, ...] */ -params ["_side", "_level"]; +params ["_side", "_level", ["_tanksOnly", false]]; _level = (_level max 1 min 10) - 1; private _faction = [A3A_faction_occ, A3A_faction_inv] select (_side == Invaders); @@ -36,14 +36,16 @@ if (_faction get "vehiclesHeavyTanks" isEqualTo []) then { _ltWeight = _ltWeight if (_faction get "vehiclesLightTanks" isEqualTo []) then { _tankWeight = _tankWeight + _ltWeight }; if (_faction get "vehiclesTanks" isEqualTo []) then { _ltWeight = _ltWeight + _tankWeight }; +[_faction get "vehiclesLightTanks", _ltWeight] call _fnc_addArrayToWeights; +[_faction get "vehiclesTanks", _tankWeight] call _fnc_addArrayToWeights; +[_faction get "vehiclesHeavyTanks", _hvytWeight] call _fnc_addArrayToWeights; +if (_tanksOnly) exitWith { _vehWeights }; + // only occupants use militia vehicles? if (_side == Occupants) then { [_faction get "vehiclesMilitiaLightArmed", _milCarWeight] call _fnc_addArrayToWeights; }; [_faction get "vehiclesLightArmed", _carWeight] call _fnc_addArrayToWeights; -[_faction get "vehiclesLightTanks", _ltWeight] call _fnc_addArrayToWeights; -[_faction get "vehiclesTanks", _tankWeight] call _fnc_addArrayToWeights; -[_faction get "vehiclesHeavyTanks", _hvytWeight] call _fnc_addArrayToWeights; [_vehAA, _aaWeight] call _fnc_addArrayToWeights; _vehWeights; diff --git a/A3A/addons/core/functions/CREATE/fn_createAttackForceLand.sqf b/A3A/addons/core/functions/CREATE/fn_createAttackForceLand.sqf index 9edb19938a..9d9aa481b9 100644 --- a/A3A/addons/core/functions/CREATE/fn_createAttackForceLand.sqf +++ b/A3A/addons/core/functions/CREATE/fn_createAttackForceLand.sqf @@ -13,7 +13,8 @@ Arguments: Total number of vehicles to create Number of attack/support vehicles to create Optional, tier modifier to apply to vehicle selection (Default: 0) - Optional, troop type to use (Default: "Normal") + Optional, true to only use tanks (Default: false) +// Optional, troop type to use (Default: "Normal") Return array: Resources spent @@ -24,7 +25,7 @@ Return array: #include "..\..\script_component.hpp" FIX_LINE_NUMBERS() -params ["_side", "_base", "_target", "_resPool", "_vehCount", "_vehAttackCount", ["_tierMod", 0]]; +params ["_side", "_base", "_target", "_resPool", "_vehCount", "_vehAttackCount", ["_tierMod", 0], ["_tanksOnly", false]]; private _targpos = if (_target isEqualType []) then { _target } else { markerPos _target }; private _transportRatio = 1 - _vehAttackCount / _vehCount; @@ -34,7 +35,7 @@ private _crewGroups = []; private _cargoGroups = []; private _transportPool = [_side, tierWar+_tierMod] call A3A_fnc_getVehiclesGroundTransport; -private _supportPool = [_side, tierWar+_tierMod] call A3A_fnc_getVehiclesGroundSupport; +private _supportPool = [_side, tierWar+_tierMod, _tanksOnly] call A3A_fnc_getVehiclesGroundSupport; private _numTransports = 0; private _isTransport = _vehAttackCount < _vehCount; // normal case, first vehicle should be a transport diff --git a/A3A/addons/core/functions/Supports/fn_SUP_tank.sqf b/A3A/addons/core/functions/Supports/fn_SUP_tank.sqf new file mode 100644 index 0000000000..ce8d1cdf5d --- /dev/null +++ b/A3A/addons/core/functions/Supports/fn_SUP_tank.sqf @@ -0,0 +1,51 @@ +/* Sets up a land QRF support + +Environment: Server, scheduled, internal + +Arguments: + The (unique) name of the support, mostly for logging + The side from which the support should be sent (occupants or invaders) + Resource pool used for this support. Should be "attack" or "defence" + Maximum resources to spend on this support. Must be greater than zero + Initial target, or "false" for none. + Estimated position of target, or center of target zone + Reveal value 0-1, higher values mean more information provided about support + Setup delay time in seconds, if negative will calculate based on war tier + +Returns: + Resource cost of support call, or -1 for failure +*/ + +#include "..\..\script_component.hpp" +FIX_LINE_NUMBERS() + +params ["_suppName", "_side", "_resPool", "_maxSpend", "_target", "_targPos", "_reveal", "_delay"]; + +private _base = [_side, _targPos] call A3A_fnc_availableBasesLand; +if (isNil "_base") exitWith { Info("Tanks cancelled because no land bases available"); -1 }; + +// Prevent ground QRFs spawning on top of each other. Should be gone after a minute. +[_base, 1] call A3A_fnc_addTimeForIdle; + +private _vehCount = 2 min ceil (_maxSpend / 200); +private _estResources = _vehCount * 200; + +// Land QRF delay is purely dependent on travel as they're slow enough already +if (_delay < 0) then { _delay = 0 }; // land QRFs slow enough already + +private _targArray = []; +if (_target isEqualType objNull and {!isNull _target}) then { + // Should probably put a partial "troops" entry in here too? + A3A_supportStrikes pushBack [_side, "TARGET", _target, time + 1800, 1800, 150*_vehCount]; + _targArray = [_target, _targPos]; +}; + +// name, side, suppType, center, radius, [target, targpos] +private _suppData = [_supportName, _side, "TANK", _targPos, 1000, _targArray]; +A3A_activeSupports pushBack _suppData; +[_suppData, _resPool, _base, _vehCount, _delay, _estResources] spawn A3A_fnc_SUP_tankRoutine; + +private _approxTime = _delay + (markerPos _base distance2D _targPos) / (30 / 3.6); // (badly) estimated travel time +[_reveal, _side, "TANK", _targPos, _approxTime] spawn A3A_fnc_showInterceptedSetupCall; + +_estResources; // *estimated* resource cost of vehicles diff --git a/A3A/addons/core/functions/Supports/fn_SUP_tankAvailable.sqf b/A3A/addons/core/functions/Supports/fn_SUP_tankAvailable.sqf new file mode 100644 index 0000000000..907b8c517c --- /dev/null +++ b/A3A/addons/core/functions/Supports/fn_SUP_tankAvailable.sqf @@ -0,0 +1,24 @@ +/* Get tank support selection weight against target + +Arguments: + Target object + Side to send support from + Max resource spend (not currently used) + Array of strings of available types for this faction + +Return value: + Weight value, 0 for unavailable or useless +*/ + +#include "..\..\script_component.hpp" +FIX_LINE_NUMBERS() + +params ["_target", "_side", "_maxSpend", "_availTypes"]; + +if (_target isKindOf "Air") exitWith { 0 }; // can't hit air + +if (_target isKindOf "Man") exitWith { 0.001 }; // Don't spawn to attack meatsacks, but re-use active supports + +// Against vehicles and statics, use more frequently against more dangerous stuff +private _threat = A3A_groundVehicleThreat getOrDefault [typeOf _target, 0]; +0.001 + _threat / 80; diff --git a/A3A/addons/core/functions/Supports/fn_SUP_tankRoutine.sqf b/A3A/addons/core/functions/Supports/fn_SUP_tankRoutine.sqf new file mode 100644 index 0000000000..6b9b1bb818 --- /dev/null +++ b/A3A/addons/core/functions/Supports/fn_SUP_tankRoutine.sqf @@ -0,0 +1,128 @@ +/* Create and maintain close air support bomber + +Environment: Server, must be spawned + +Arguments: + Active support data, see initSupports + Resource pool of support, "attack" or "defence" + Marker name of source land base + Number of tanks to send + Delay time in seconds + Estimated resources already spent on support +// Amount of information to reveal to rebels, 0-1 +*/ +#include "..\..\script_component.hpp" +FIX_LINE_NUMBERS() + +Debug_1("tankRoutine called with %1", _this); + +params ["_suppData", "_resPool", "_base", "_vehCount", "_sleepTime", "_estResources"]; +_suppData params ["_supportName", "_side", "_suppType", "_suppCenter", "_suppRadius", "_suppTarget"]; + +sleep _sleepTime; + +// Only spawn tanks +private _data = [_side, _base, _suppCenter, _resPool, _vehCount, _vehCount, 2, true] call A3A_fnc_createAttackForceLand; +_data params ["_resources", "_vehicles", "_crewGroups", "_cargoGroups"]; +Info_1("Spawn performed: Vehicles %1", _vehicles apply { typeOf _x }); + +// Update the resource usage for the final value +[_estResources - _resources, _side, _resPool] remoteExec ["A3A_fnc_addEnemyResources", 2]; + + +#define STATE_TRAVEL 1 +#define STATE_ACQUIRE 2 +#define STATE_ATTACK 3 + +private _timeOut = time + 1800; +private _remTargets = 2; +private _state = STATE_TRAVEL; +private _targetObj = objNull; + +while {true} do +{ + private _remVehicles = _vehicles select { canFire _x and canMove _x and side _x == _side }; + if (_remVehicles isEqualTo []) exitWith { + Info_1("%1 has been defeated, starting retreat", _supportName); + }; + if (time > _timeOut) exitWith { + Info_1("%1 has timed out, starting retreat", _supportName); + }; + if (_remTargets <= 0) exitWith { + Info_1("%1 has run out of targets, aborting routine", _supportName); + }; + + switch (_state) do + { + case STATE_TRAVEL: { + if (_remVehicles inAreaArray [_suppCenter, _suppRadius, _suppRadius] isEqualTo []) exitWith { sleep 5 }; + + Debug_1("%1 reached patrol zone, acquiring target", _supportName); + _state = STATE_ACQUIRE; + continue; + }; + + case STATE_ACQUIRE: { + if (_suppTarget isEqualTo []) exitWith { sleep 5 }; + + _targetObj = _suppTarget select 0; + if !(_targetObj call A3A_fnc_canFight) exitWith { + _suppTarget resize 0; + Debug_1("%1 skips target, as it is already dead", _supportName); + }; + Debug_2("Next target for %2 is %1", _suppTarget, _supportName); + + private _lastKnownPos = _suppTarget select 1; + private _knownDist = _lastKnownPos distance2d getPosATL _targetObj; + private _knowledge = random 0.3 + _knownDist / _suppRadius; + + { + // reveal based on proximity to last known pos + _x reveal [_targetObj, 4*_knowledge]; + + { deleteWaypoint _x } forEachReversed (waypoints _x); + private _attackWP = _x addWaypoint [_targetObj, 0]; + _attackWP setWaypointType "DESTROY"; + _attackWP waypointAttachVehicle _targetObj; + private _sadWP = _x addWaypoint [_lastKnownPos, 0]; + _sadWP setWaypointType "SAD"; + + _x setCurrentWaypoint ([_sadWP, _attackWP] select (_knowledge > random 0.5)); + _x setBehaviourStrong "COMBAT"; + _x setCombatMode "RED"; + + } forEach _crewGroups; + + _timeout = _timeout + 300; + _state = STATE_ATTACK; + continue; + }; + + case STATE_ATTACK: { + if (alive _targetObj and {_targetObj distance2D _suppCenter < _suppRadius}) exitWith { sleep 5 }; + + _remTargets = _remTargets - 1; + _suppTarget resize 0; // clear target array so support routines can add the next + + if !(alive _targetObj) then { + Debug_1("Target destroyed, %1 returns to cycle mode", _supportName); + } else { + Debug_1("Target evaded, %1 returns to cycle mode", _supportName); + }; + + { + _x setBehaviourStrong "AWARE"; + _x setCombatMode "YELLOW"; + } forEach _crewGroups; + + _timeout = _timeout - 300; + _state = STATE_ACQUIRE; + continue; + }; + }; +}; + +_suppData set [4, 0]; // Set activesupport radius to 0, enables cleanup + +{ [_x] spawn A3A_fnc_VEHDespawner } forEach _vehicles; +{ [_x] spawn A3A_fnc_enemyReturnToBase } forEach (_crewGroups + _cargoGroups); diff --git a/A3A/addons/core/functions/Supports/fn_initSupports.sqf b/A3A/addons/core/functions/Supports/fn_initSupports.sqf index b6ecf8c9ea..6b27477096 100644 --- a/A3A/addons/core/functions/Supports/fn_initSupports.sqf +++ b/A3A/addons/core/functions/Supports/fn_initSupports.sqf @@ -37,7 +37,8 @@ private _initData = [ ["ARTILLERY", "AREA", 0.5, 0.9, 150, 85, "", "vehiclesArtillery"], // balanced against mortars (50/50 at tier 10), total will be 0.5/0.9 ["MORTAR", "AREA", 0.5, 0.9, 100, 50, "", "staticMortars"], ["ASF", "TARGET", 1.0, 0.4, 0, 100, "", "vehiclesPlanesAA"], // balanced against SAMs (if available), 66/33 weighting - ["CAS", "TARGET", 1.0, 0.4, 0, 100, "", "vehiclesPlanesCAS"], + ["CAS", "TARGET", 0.5, 0.3, 0, 100, "", "vehiclesPlanesCAS"], + ["TANK", "TARGET", 0.5, 0.7, 0, 100, "", ""], // balanced against CAS, lowAir based ["QRFLAND", "TROOPS", 1.0, 1.4, 0, 0, "", ""], ["QRFAIR", "TROOPS", 0.5, 0.1, 0, 0, "", ""], ["CARPETBOMBS", "AREA", 0.5, 0.1, 200, 0, "u", ""], // balanced against airstrikes diff --git a/A3A/addons/core/functions/Supports/fn_showInterceptedSetupCall.sqf b/A3A/addons/core/functions/Supports/fn_showInterceptedSetupCall.sqf index fdc0a1efe1..b0336374cb 100644 --- a/A3A/addons/core/functions/Supports/fn_showInterceptedSetupCall.sqf +++ b/A3A/addons/core/functions/Supports/fn_showInterceptedSetupCall.sqf @@ -52,6 +52,10 @@ else { _text = format [localize "STR_A3A_fn_support_showIntStpCll_QRFLAND", _sideName]; }; + case ("TANK"): + { + _text = format [localize "STR_A3A_fn_support_showIntStpCll_TANK", _sideName]; + }; case ("AIRSTRIKE"): { _text = format [localize "STR_A3A_fn_support_showIntStpCll_AIRSTRIKE", _sideName]; @@ -109,7 +113,7 @@ private _timeStr = if(_setupTime < 60) then { "<1" } else { str round (_setup if(_reveal >= 0.8) then { - if(toupper _supportType in ["QRFLAND", "QRFAIR", "COUNTERATTACK", "MAJORATTACK"]) then + if(toupper _supportType in ["QRFLAND", "QRFAIR", "COUNTERATTACK", "MAJORATTACK", "TANK"]) then { _text = [_text,format[localize "STR_A3A_fn_support_showIntStpCll_arrivalTime",_timeStr]] joinString " "; }