Guest User

Untitled

a guest
Jan 7th, 2026
45
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 466.55 KB | None | 0 0
  1.  
  2.  
  3. ==================== START OF: AAF.sqf ====================
  4.  
  5. // REWORKED: Handles dynamic AAF patrol spawning.
  6. // Also manages initial AAF garrisons at strategic locations.
  7.  
  8. // AAF squads are now limited to only bandit compositions.
  9. AAF_SQUAD_COMPOSITIONS = [
  10. // Bandit squads
  11. ["I_C_Soldier_Bandit_2_F", "I_C_Soldier_Bandit_1_F", "I_C_Soldier_Bandit_4_F", "I_C_Soldier_Bandit_5_F", "I_C_Soldier_Bandit_3_F", "I_C_Soldier_Bandit_8_F"],
  12. ["I_C_Soldier_Bandit_6_F", "I_C_Soldier_Bandit_1_F", "I_C_Soldier_Bandit_4_F", "I_C_Soldier_Bandit_4_F", "I_C_Soldier_Bandit_2_F", "I_C_Soldier_Bandit_5_F"],
  13. ["I_C_Soldier_Bandit_7_F", "I_C_Soldier_Bandit_3_F", "I_C_Soldier_Bandit_4_F", "I_C_Soldier_Bandit_1_F", "I_C_Soldier_Bandit_2_F"]
  14. ];
  15.  
  16. // New function to make AAF groups strictly defend the strongpoint
  17. fnc_setAAFDefend = {
  18. params ["_group", "_centerPos"];
  19.  
  20. // Clear any existing waypoints
  21. while {count (waypoints _group) > 0} do {
  22. deleteWaypoint ((waypoints _group) select 0);
  23. };
  24.  
  25. // Defense settings: Keep units strictly within the strongpoint area
  26. private _defendRadius = 120;
  27.  
  28. // Set tactical mode for defense
  29. _group setBehaviour "COMBAT"; // Stay alert, use cover
  30. _group setCombatMode "RED"; // Engage enemies freely
  31. _group setSpeedMode "LIMITED"; // Walk/Crouch only, prevents running out of the zone
  32.  
  33. // Create a defensive patrol pattern within the strongpoint
  34. for "_i" from 1 to 4 do {
  35. // Find a valid position within the tight defense radius
  36. private _defendPos = [_centerPos, 0, _defendRadius, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
  37. if (count _defendPos == 0) then { _defendPos = _centerPos; };
  38.  
  39. private _wp = _group addWaypoint [_defendPos, 0];
  40. _wp setWaypointType "SAD"; // Search and Destroy allows engaging nearby threats
  41. _wp setWaypointCompletionRadius 20;
  42. // Important: Make them wait at each point to simulate guarding/holding ground
  43. _wp setWaypointTimeout [20, 40, 60];
  44. };
  45.  
  46. // Cycle back to the first waypoint to maintain the defense indefinitely
  47. private _cycleWp = _group addWaypoint [waypointPosition [_group, 1], 0];
  48. _cycleWp setWaypointType "CYCLE";
  49. };
  50.  
  51. // This function now specifically handles the INITIAL AAF patrol at mission start.
  52. fnc_spawnAAFForces = {
  53. // IMPROVED: Wait for mission_centralPoint to exist AND be valid (not default fallback)
  54. waitUntil {
  55. !isNil "mission_centralPoint" &&
  56. {
  57. private _pos = missionNamespace getVariable ["mission_centralPoint", [0,0,0]];
  58. !(_pos isEqualTo [0,0,0]) &&
  59. !(_pos isEqualTo [worldSize/2, worldSize/2, 0]) &&
  60. (_pos select 0) > 0 &&
  61. (_pos select 1) > 0
  62. }
  63. };
  64.  
  65. // IMPROVED: Additional delay to ensure base objects are fully initialized
  66. sleep 3; // Increased from 2 to 3 seconds
  67.  
  68. private _aafTargetSize = AAF_START_SIZE;
  69. if (_aafTargetSize <= 0) exitWith {};
  70.  
  71. private _centerPos = missionNamespace getVariable ["mission_centralPoint", [worldSize/2, worldSize/2, 0]];
  72.  
  73. // IMPROVED: Comprehensive position validation with multiple checks
  74. private _positionValid = true;
  75.  
  76. // Check 1: Not default fallback positions
  77. if (_centerPos isEqualTo [worldSize/2, worldSize/2, 0] || _centerPos isEqualTo [0,0,0]) then {
  78. _positionValid = false;
  79. diag_log format ["ERROR: AAF spawn position is default fallback: %1", _centerPos];
  80. };
  81.  
  82. // Check 2: Coordinates must be positive and within world bounds
  83. if ((_centerPos select 0) <= 0 || (_centerPos select 1) <= 0) then {
  84. _positionValid = false;
  85. diag_log format ["ERROR: AAF spawn position has invalid coordinates: %1", _centerPos];
  86. };
  87.  
  88. if ((_centerPos select 0) >= worldSize || (_centerPos select 1) >= worldSize) then {
  89. _positionValid = false;
  90. diag_log format ["ERROR: AAF spawn position exceeds world bounds: %1", _centerPos];
  91. };
  92.  
  93. // Check 3: Position must not be in water
  94. if (surfaceIsWater _centerPos) then {
  95. _positionValid = false;
  96. diag_log format ["ERROR: AAF spawn position is in water: %1", _centerPos];
  97. };
  98.  
  99. // IMPROVED: If position validation fails completely, abort and log
  100. if (!_positionValid) exitWith {
  101. diag_log format ["CRITICAL ERROR: AAF spawn aborted due to invalid mission_centralPoint: %1", _centerPos];
  102. systemChat "ERROR: AAF forces could not spawn - invalid stronghold position detected";
  103. };
  104.  
  105. // IMPROVED: Log the validated position being used
  106. diag_log format ["AAF spawning at validated mission_centralPoint: %1", _centerPos];
  107.  
  108. // Force Z to ground level for spawning (existing fix)
  109. _centerPos set [2, 0];
  110.  
  111. private _allPatrolGroups = [];
  112. private _spawnedCount = 0;
  113.  
  114. // Create a marker for the AAF Stronghold
  115. private _marker = createMarker ["aaf_stronghold_marker", _centerPos];
  116. _marker setMarkerType "mil_flag";
  117. _marker setMarkerColor "ColorGreen";
  118. _marker setMarkerText "AAF Stronghold";
  119.  
  120. while {_spawnedCount < _aafTargetSize} do {
  121. private _group = createGroup independent;
  122. private _squadComp = selectRandom AAF_SQUAD_COMPOSITIONS;
  123.  
  124. if ((_spawnedCount + count _squadComp) > _aafTargetSize) then {
  125. _squadComp = _squadComp select [0, _aafTargetSize - _spawnedCount];
  126. };
  127. if (count _squadComp == 0) then { break; };
  128.  
  129. for "_i" from 0 to (count _squadComp - 1) do {
  130. private _unitType = _squadComp select _i;
  131.  
  132. // Tighter spawn radius to keep units closer to the stronghold center.
  133. private _spawnPos = [_centerPos, 10, 75, 15, 0, 0.3, 0] call BIS_fnc_findSafePos;
  134.  
  135. // NEW: Robust sanity check and multi-stage fallback for spawn position.
  136. // This prevents units spawning far away if findSafePos fails or returns an invalid location.
  137. if (count _spawnPos == 0 || _spawnPos isEqualTo [0,0,0] || _spawnPos distance2D _centerPos > 200) then {
  138.  
  139. // STAGE 1 FALLBACK: Try again with a very tight radius.
  140. _spawnPos = [_centerPos, 0, 20, 5, 0, 0, 0] call BIS_fnc_findSafePos;
  141. diag_log format ["WARNING: findSafePos failed for AAF unit. Attempting fallback stage 1..."];
  142.  
  143. // STAGE 2 FALLBACK: If the tight search also fails, use a simple random offset as a last resort.
  144. if (count _spawnPos == 0 || _spawnPos isEqualTo [0,0,0]) then {
  145. _spawnPos = [
  146. (_centerPos select 0) + (random 20 - 10),
  147. (_centerPos select 1) + (random 20 - 10),
  148. 0
  149. ];
  150. diag_log format ["WARNING: Fallback stage 1 failed. Using final offset position: %1", _spawnPos];
  151. };
  152. };
  153.  
  154. private _unit = _group createUnit [_unitType, _spawnPos, [], 10, "NONE"];
  155. _unit setVariable ["isAAF", true, true];
  156. [_unit, 0.05, "REGULAR"] call fnc_enhanceIndividualAI;
  157. _spawnedCount = _spawnedCount + 1;
  158. };
  159.  
  160. // Assign the new defensive logic to hold the stronghold
  161. [_group, _centerPos] call fnc_setAAFDefend;
  162.  
  163. _allPatrolGroups pushBack _group;
  164. };
  165.  
  166. independent setFriend [west, 0];
  167. independent setFriend [east, 0];
  168. west setFriend [independent, 0];
  169. east setFriend [independent, 0];
  170.  
  171. missionNamespace setVariable ["AAF_Groups", _allPatrolGroups, true];
  172.  
  173. // IMPROVED: Final confirmation log with unit count verification
  174. diag_log format ["AAF spawn complete: %1 units spawned at %2 in %3 groups", _spawnedCount, _centerPos, count _allPatrolGroups];
  175.  
  176. // IMPROVED: Verify all groups have units
  177. {
  178. private _groupUnits = units _x select {alive _x};
  179. if (count _groupUnits == 0) then {
  180. diag_log format ["WARNING: AAF group %1 is empty after spawn", groupId _x];
  181. };
  182. } forEach _allPatrolGroups;
  183. };
  184.  
  185. if (isServer) then {
  186. [] spawn {
  187. // FIXED: Wait for mission setup to complete first, then wait for player teams
  188. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj" && !isNil "mission_centralPoint"};
  189. waitUntil {({side _x in [west, east]} count allPlayers > 0)};
  190. sleep 5;
  191. [] call fnc_spawnAAFForces;
  192. };
  193. };
  194.  
  195. // ==================== CHANGE START ====================
  196. // NEW: Standalone server-side loop to apply foliage/vision checks to AAF units.
  197. // This gives them smarter engagement logic without adding all the complex behaviors from botai.sqf.
  198. if (isServer) then {
  199. [] spawn {
  200. // Wait for AAF forces to be initialized
  201. waitUntil {!isNil "AAF_Groups"};
  202.  
  203. while {true} do {
  204. // Get all alive AAF units on the map
  205. private _allAAFUnits = allUnits select {alive _x && (_x getVariable ["isAAF", false])};
  206.  
  207. {
  208. private _unit = _x;
  209. // AAF are hostile to both BLUFOR and OPFOR
  210. private _enemySides = [west, east];
  211.  
  212. // Check for nearby targets
  213. private _nearTargets = _unit nearTargets 300; // Check targets within 300m
  214.  
  215. {
  216. private _targetInfo = _x;
  217. private _target = _targetInfo select 4;
  218.  
  219. // Check if the target is a valid enemy
  220. if (alive _target && (side _target in _enemySides)) then {
  221. // Use the global function from botai.sqf to check for obstruction
  222. private _obstructionLevel = [_unit, _target] call fnc_checkFoliageObstruction;
  223.  
  224. // Apply obstruction-based reveal and fire control
  225. if (_obstructionLevel > 0.6) then {
  226. // Heavy obstruction - hold fire completely
  227. (group _unit) reveal [_target, 1.0];
  228. (group _unit) setVariable ["AICanFire", false];
  229. } else {
  230. if (_obstructionLevel > 0.3) then {
  231. // Moderate obstruction - limited engagement
  232. (group _unit) reveal [_target, 2.5];
  233. (group _unit) setVariable ["AICanFire", false];
  234. } else {
  235. // Clear shot - full engagement
  236. (group _unit) reveal [_target, 4];
  237. (group _unit) setVariable ["AICanFire", true];
  238. };
  239. };
  240. };
  241. } forEach _nearTargets;
  242.  
  243. } forEach _allAAFUnits;
  244.  
  245. // Check every 3-5 seconds to balance performance and responsiveness
  246. sleep (3 + random 2);
  247. };
  248. };
  249. };
  250. // ===================== CHANGE END =====================
  251.  
  252. ==================== END OF: AAF.sqf ====================
  253.  
  254.  
  255.  
  256. ==================== START OF: AIstuff.sqf ====================
  257.  
  258. // AIstuff.sqf
  259.  
  260. // AI Unit Pools
  261. BLUFOR_INFANTRY_POOL = [
  262. ["B_Soldier_TL_F", "B_Soldier_F", "B_Soldier_LAT_F", "B_Soldier_AA_F", "B_medic_F", "B_Soldier_AR_F"],
  263. ["B_Soldier_SL_F", "B_Soldier_F", "B_Soldier_AT_F", "B_Soldier_AA_F", "B_medic_F", "B_engineer_F"],
  264. ["B_Soldier_TL_F", "B_Soldier_F", "B_Soldier_LAT_F", "B_Soldier_AA_F", "B_HeavyGunner_F", "B_Soldier_A_F"],
  265. ["B_Soldier_TL_F", "B_Soldier_F", "B_Soldier_AT_F", "B_Soldier_AA_F", "B_Soldier_AR_F", "B_Soldier_A_F"],
  266. ["B_Soldier_SL_F", "B_Soldier_LAT_F", "B_Soldier_AA_F", "B_medic_F", "B_Soldier_AR_F", "B_engineer_F"]
  267. ];
  268. OPFOR_INFANTRY_POOL = [
  269. ["O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_LAT_F", "O_Soldier_AA_F", "O_medic_F", "O_Soldier_AR_F"],
  270. ["O_Soldier_SL_F", "O_Soldier_F", "O_Soldier_AT_F", "O_Soldier_AA_F", "O_medic_F", "O_engineer_F"],
  271. ["O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_LAT_F", "O_Soldier_AA_F", "O_HeavyGunner_F", "O_Soldier_A_F"],
  272. ["O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_AT_F", "O_Soldier_AA_F", "O_Soldier_AR_F", "O_Soldier_A_F"],
  273. ["O_Soldier_SL_F", "O_Soldier_LAT_F", "O_Soldier_AA_F", "O_medic_F", "O_Soldier_AR_F", "O_engineer_F"]
  274. ];
  275. BLUFOR_SPECOPS_POOL = [
  276. ["B_recon_TL_F", "B_recon_medic_F", "B_recon_LAT_F", "B_recon_exp_F", "B_recon_M_F", "B_recon_F"],
  277. ["B_recon_TL_F", "B_recon_medic_F", "B_recon_LAT_F", "B_recon_JTAC_F", "B_recon_F", "B_recon_F"]
  278. ];
  279. OPFOR_SPECOPS_POOL = [
  280. ["O_recon_TL_F", "O_recon_medic_F", "O_recon_LAT_F", "O_recon_exp_F", "O_recon_M_F", "O_recon_F"],
  281. ["O_recon_TL_F", "O_recon_medic_F", "O_recon_LAT_F", "O_recon_JTAC_F", "O_recon_F", "O_recon_F"]
  282. ];
  283. BLUFOR_ELITE_POOL = [
  284. ["B_CTRG_Soldier_TL_tna_F", "B_CTRG_Soldier_AR_tna_F", "B_CTRG_Soldier_LAT_tna_F", "B_CTRG_Soldier_Medic_tna_F", "B_CTRG_Soldier_M_tna_F"]
  285. ];
  286. OPFOR_ELITE_POOL = [
  287. ["O_V_Soldier_TL_hex_F", "O_V_Soldier_M_hex_F", "O_V_Soldier_LAT_hex_F", "O_V_Soldier_Medic_hex_F", "O_V_Soldier_Exp_hex_F"]
  288. ];
  289. BLUFOR_UPGRADED_POOL = [
  290. ["B_Patrol_Soldier_TL_F", "B_Patrol_Soldier_AR_F", "B_Patrol_Medic_F", "B_Patrol_HeavyGunner_F", "B_Patrol_Soldier_M_F", "B_Patrol_Soldier_AT_F"],
  291. ["B_Patrol_Soldier_TL_F", "B_Patrol_Soldier_AR_F", "B_Patrol_Medic_F", "B_Patrol_Soldier_AR_F", "B_Patrol_Soldier_M_F", "B_Patrol_Soldier_AT_F"]
  292. ];
  293. OPFOR_UPGRADED_POOL = [
  294. ["O_R_Patrol_Soldier_TL_F", "O_R_Patrol_Soldier_AR2_F", "O_R_Patrol_Soldier_Medic", "O_R_Patrol_Soldier_AR_F", "O_R_Patrol_Soldier_M_F", "O_R_Patrol_Soldier_LAT_F"],
  295. ["O_R_Patrol_Soldier_TL_F", "O_R_Patrol_Soldier_AR_F", "O_R_Patrol_Soldier_Medic", "O_R_Patrol_Soldier_AR_F", "O_R_Patrol_Soldier_M_F", "O_R_Patrol_Soldier_LAT_F"]
  296. ];
  297.  
  298. // Vehicle Pools - Light Vehicles (APCs)
  299. BLUFOR_LIGHT_VEHICLE_POOL = [
  300. "B_APC_Wheeled_01_cannon_F" // AMV-7 Marshall
  301. ];
  302. OPFOR_LIGHT_VEHICLE_POOL = [
  303. "O_APC_Wheeled_02_rcws_v2_F" // MSE-3 Marid
  304. ];
  305.  
  306. // Vehicle Pools - Main Battle Tanks
  307. BLUFOR_TANK_POOL = [
  308. "B_MBT_01_TUSK_F" // M2A4 Slammer UP
  309. ];
  310. OPFOR_TANK_POOL = [
  311. "O_MBT_02_cannon_F" // T-100 Varsuk
  312. ];
  313.  
  314. // Vehicle Pools - Attack Helicopters
  315. BLUFOR_ATTACK_HELI_POOL = [
  316. "B_Heli_Attack_01_F" // AH-99 Blackfoot
  317. ];
  318. OPFOR_ATTACK_HELI_POOL = [
  319. "O_Heli_Attack_02_F" // Mi-48 Kajman
  320. ];
  321.  
  322.  
  323. // Vehicle tracking (runtime state variables)
  324. if (isNil "BLUFOR_ACTIVE_LIGHT_VEHICLES") then { BLUFOR_ACTIVE_LIGHT_VEHICLES = []; };
  325. if (isNil "OPFOR_ACTIVE_LIGHT_VEHICLES") then { OPFOR_ACTIVE_LIGHT_VEHICLES = []; };
  326. if (isNil "BLUFOR_ACTIVE_TANKS") then { BLUFOR_ACTIVE_TANKS = []; };
  327. if (isNil "OPFOR_ACTIVE_TANKS") then { OPFOR_ACTIVE_TANKS = []; };
  328. if (isNil "BLUFOR_ACTIVE_ATTACK_HELIS") then { BLUFOR_ACTIVE_ATTACK_HELIS = []; };
  329. if (isNil "OPFOR_ACTIVE_ATTACK_HELIS") then { OPFOR_ACTIVE_ATTACK_HELIS = []; };
  330.  
  331. // ==================== CHANGE START ====================
  332. // NEW: Finds a safe spawn position in a 600m radius around the base, avoiding enemies.
  333. fnc_getSafeSpawnPosition = {
  334. params ["_side"];
  335.  
  336. private _baseObj = if (_side == west) then { bluforSpawnObj } else { opforSpawnObj };
  337. if (isNil "_baseObj") exitWith {[0,0,0]}; // Should not happen
  338.  
  339. private _basePos = getPos _baseObj;
  340. private _enemySide = if (_side == west) then { east } else { west };
  341. private _spawnRadius = 500;
  342. private _enemyBuffer = 50; // Minimum distance from an enemy
  343.  
  344. private _finalPos = [];
  345.  
  346. // Try up to 20 times to find a suitable position
  347. for "_i" from 1 to 20 do {
  348. // Find a random, safe position within the base radius
  349. private _potentialPos = [_basePos, 10, _spawnRadius, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
  350.  
  351. if (count _potentialPos > 0) then {
  352. // Check for nearby enemies at the potential spawn point
  353. private _isNearEnemy = false;
  354. _nearby = _potentialPos nearEntities ["AllVehicles", _enemyBuffer];
  355.  
  356. // Use findIf for efficiency; it stops once an enemy is found
  357. if ((_nearby findIf {side _x == _enemySide && alive _x}) != -1) then {
  358. _isNearEnemy = true;
  359. };
  360.  
  361. // If no enemies are nearby, this position is valid
  362. if (!_isNearEnemy) then {
  363. _finalPos = _potentialPos;
  364. break; // Exit the loop
  365. };
  366. };
  367. };
  368.  
  369. // If a valid position was found, return it. Otherwise, fallback to the base center.
  370. if (count _finalPos > 0) then {
  371. _finalPos
  372. } else {
  373. _basePos
  374. }
  375. };
  376. // ===================== CHANGE END =====================
  377.  
  378. // AI Helper Functions
  379. fnc_updateStrengthTally = {
  380. params ["_entity", "_side"];
  381. if (!isServer) exitWith {};
  382.  
  383. private _unitStrength = 1; // Default strength
  384.  
  385. // Determine strength based on type
  386. if (_entity isKindOf "CAManBase") then {
  387. if (_entity getVariable ["isElite", false]) then { _unitStrength = 5; };
  388. if (_entity getVariable ["isSpecOps", false]) then { _unitStrength = 3; };
  389. if (_entity getVariable ["isSniper", false]) then { _unitStrength = 2; };
  390. } else {
  391. if (_entity isKindOf "Tank") then { _unitStrength = 15; };
  392. if (_entity isKindOf "APC") then { _unitStrength = 8; };
  393. if (_entity isKindOf "Air") then { _unitStrength = 20; };
  394. if (_entity isKindOf "Car" || _entity isKindOf "Ship") then { _unitStrength = 3; }; // For light vehicles etc.
  395. };
  396.  
  397. // Store the calculated strength on the unit/vehicle for later use (on death)
  398. _entity setVariable ["strengthValue", _unitStrength, true];
  399.  
  400. // Add the strength to the correct side's tally
  401. if (_side == west) then {
  402. BLUFOR_STRENGTH = BLUFOR_STRENGTH + _unitStrength;
  403. } else {
  404. OPFOR_STRENGTH = OPFOR_STRENGTH + _unitStrength;
  405. };
  406. };
  407.  
  408. fnc_markAsImportant = {
  409. params ["_entity"];
  410. _entity enableDynamicSimulation false;
  411. _entity setVariable ["gcImportant", true, true];
  412. if (_entity isKindOf "AllVehicles" && !(_entity isKindOf "CAManBase")) then {
  413. {_x enableDynamicSimulation false; _x setVariable ["gcImportant", true, true];} forEach (crew _entity);
  414. };
  415. };
  416.  
  417. fnc_getPatrolPosition = {
  418. params ["_centerPos", "_minRadius", "_maxRadius"];
  419.  
  420. // Validate centerPos is an array with at least 2 elements
  421. if (typeName _centerPos != "ARRAY" || count _centerPos < 2) exitWith {
  422. [0, 0, 0]
  423. };
  424.  
  425. private _searchRadius = _maxRadius;
  426. private _coverPositions = selectBestPlaces [_centerPos, _searchRadius, "forest + houses", 10, 5];
  427. private _landCoverPositions = _coverPositions select { !surfaceIsWater (_x select 0) };
  428. private _patrolPos = [];
  429. if (count _landCoverPositions > 0) then {
  430. _patrolPos = selectRandom _landCoverPositions select 0;
  431. } else {
  432. // Fallback to original method
  433. private _distance = _minRadius + (random (_maxRadius - _minRadius));
  434. private _direction = random 360;
  435. private _potentialPos = [(_centerPos select 0) + (_distance * cos _direction), (_centerPos select 1) + (_distance * sin _direction), 0];
  436. _patrolPos = [_potentialPos, 0, _maxRadius, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
  437. if (count _patrolPos == 0) then { _patrolPos = _centerPos; };
  438. };
  439. _patrolPos
  440. };
  441.  
  442. fnc_getBattleHotspot = {
  443. params ["_side"];
  444.  
  445. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  446. private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
  447. private _targetPos = _enemyBase; // Default target is enemy base
  448.  
  449. private _enemyPositions = [];
  450. {
  451. // Consider intel from the last 3 minutes (180s) to be relevant for a hotspot
  452. if (time - (_y get "time") < 180) then {
  453. _enemyPositions pushBack (_y get "position");
  454. };
  455. } forEach _intel;
  456.  
  457. // If we have recent intel, find the largest concentration of enemies
  458. if (count _enemyPositions > 0) then {
  459. // Use the globally available clustering function from highcommand.sqf
  460. private _clusters = [_enemyPositions, 400] call fnc_clusterPositions;
  461. if (count _clusters > 0) then {
  462. // Find the largest cluster
  463. _clusters = [_clusters, [], {count _x}, "DESCEND"] call BIS_fnc_sortBy;
  464. private _largestCluster = _clusters select 0;
  465.  
  466. // Calculate the center of the largest cluster to use as the target
  467. private _avgX = 0; private _avgY = 0;
  468. { _avgX = _avgX + (_x select 0); _avgY = _avgY + (_x select 1); } forEach _largestCluster;
  469. _targetPos = [_avgX / count _largestCluster, _avgY / count _largestCluster, 0];
  470. };
  471. };
  472.  
  473. _targetPos // Return the calculated hotspot or the enemy base as a fallback
  474. };
  475.  
  476. // ==================== OPTIMIZED FUNCTION ====================
  477. fnc_getRandomWaveType = {
  478. params ["_availablePoints", "_side"];
  479. private [
  480. "_waveHistory", "_affordableWaves", "_waveType", "_weight",
  481. "_canAfford", "_wasRecentlyUsed", "_totalWeight",
  482. "_random", "_currentWeight", "_selectedWave"
  483. ];
  484.  
  485. _waveHistory = if (_side == west) then {BLUFOR_LAST_WAVES} else {OPFOR_LAST_WAVES};
  486. _affordableWaves = [];
  487.  
  488. // --- OPTIMIZATION: Pre-calculate all vehicle counts once ---
  489. private _aliveLightVehicles = 0;
  490. private _aliveTanks = 0;
  491. private _aliveHelis = 0;
  492.  
  493. if (_side == west) then {
  494. _aliveLightVehicles = count (BLUFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x && alive _x});
  495. _aliveTanks = count (BLUFOR_ACTIVE_TANKS select {!isNull _x && alive _x});
  496. _aliveHelis = count (BLUFOR_ACTIVE_ATTACK_HELIS select {!isNull _x && alive _x});
  497. } else {
  498. _aliveLightVehicles = count (OPFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x && alive _x});
  499. _aliveTanks = count (OPFOR_ACTIVE_TANKS select {!isNull _x && alive _x});
  500. _aliveHelis = count (OPFOR_ACTIVE_ATTACK_HELIS select {!isNull _x && alive _x});
  501. };
  502. // --- END OPTIMIZATION ---
  503.  
  504. {
  505. _waveType = _x select 0;
  506. _weight = _x select 1;
  507. _canAfford = false;
  508.  
  509. switch (_waveType) do {
  510. case "infantry": {if (!isNil "INFANTRY_VALUE") then {_canAfford = (_availablePoints >= (2 * 6 * INFANTRY_VALUE));};};
  511. case "upgraded": {if (!isNil "UPGRADED_VALUE") then {_canAfford = (_availablePoints >= (2 * 6 * UPGRADED_VALUE));};};
  512. case "specops": {if (!isNil "SPECOPS_VALUE") then {_canAfford = (_availablePoints >= (2 * 6 * SPECOPS_VALUE));};};
  513. case "sniper": {if (!isNil "SNIPER_VALUE") then {_canAfford = (_availablePoints >= (2 * 2 * SNIPER_VALUE));};};
  514. case "elite": {if (!isNil "ELITE_VALUE") then {_canAfford = (_availablePoints >= (2 * 5 * ELITE_VALUE));};};
  515. case "light_vehicle": {
  516. if (!isNil "VEHICLE_VALUE" && !isNil "maxLightVehiclesPerSide") then {
  517. _canAfford = (time > 600) && (_availablePoints >= (2 * VEHICLE_VALUE)) && (_aliveLightVehicles < maxLightVehiclesPerSide);
  518. };
  519. };
  520. case "tank": {
  521. if (!isNil "TANK_VALUE" && !isNil "maxTanksPerSide") then {
  522. _canAfford = (time > 1200) && (_availablePoints >= (2 * TANK_VALUE)) && (_aliveTanks < maxTanksPerSide);
  523. };
  524. };
  525. case "attack_heli": {
  526. if (!isNil "ATTACK_HELI_VALUE" && !isNil "maxAttackHelisPerSide") then {
  527. _canAfford = (time > 1200) && (_availablePoints >= (2 * ATTACK_HELI_VALUE)) && (_aliveHelis < maxAttackHelisPerSide);
  528. };
  529. };
  530. };
  531.  
  532. if (_canAfford) then {
  533. _wasRecentlyUsed = _waveType in _waveHistory;
  534. if (_wasRecentlyUsed) then {
  535. _weight = _weight * 0.2;
  536. } else {
  537. _weight = _weight * 1.5;
  538. };
  539. _affordableWaves pushBack [_waveType, _weight];
  540. };
  541. } forEach WAVE_WEIGHTS;
  542.  
  543. if (count _affordableWaves == 0) exitWith {nil};
  544.  
  545. _totalWeight = 0;
  546. {_totalWeight = _totalWeight + (_x select 1);} forEach _affordableWaves;
  547.  
  548. _random = random _totalWeight;
  549. _currentWeight = 0;
  550. _selectedWave = "infantry";
  551.  
  552. {
  553. _currentWeight = _currentWeight + (_x select 1);
  554. if (_random <= _currentWeight) exitWith { _selectedWave = _x select 0; };
  555. } forEach _affordableWaves;
  556.  
  557. _selectedWave
  558. };
  559.  
  560. // AI Patrol Functions
  561. fnc_createPatrolWaypoints = {
  562. params ["_group", "_enemyBasePos", ["_behavior", "AWARE"], ["_formation", "LINE"]];
  563.  
  564. while {count (waypoints _group) > 0} do {
  565. deleteWaypoint ((waypoints _group) select 0);
  566. };
  567.  
  568. // First waypoint: Move closer to enemy base (reduced minimum distance from 500 to 200)
  569. private _patrolPos = [_enemyBasePos, 200, PATROL_RADIUS] call fnc_getPatrolPosition;
  570.  
  571. private _wp = _group addWaypoint [_patrolPos, 50];
  572. _wp setWaypointType "MOVE";
  573. _wp setWaypointStatements ["true", "{_x setUnitPos 'UP';} forEach units group this;"];
  574. _wp setWaypointBehaviour _behavior;
  575. _wp setWaypointSpeed "NORMAL";
  576. _wp setWaypointFormation _formation;
  577. _wp setWaypointCombatMode "YELLOW";
  578.  
  579. // Add enemy base position directly as a SAD waypoint
  580. _wp = _group addWaypoint [_enemyBasePos, 100];
  581. _wp setWaypointType "SAD";
  582. _wp setWaypointBehaviour "COMBAT";
  583. _wp setWaypointSpeed "NORMAL";
  584. _wp setWaypointFormation _formation;
  585. _wp setWaypointCombatMode "RED";
  586. _wp setWaypointCompletionRadius 150;
  587.  
  588. // Additional patrol points around enemy base (reduced minimum from 300 to 100)
  589. for "_i" from 1 to 2 do {
  590. _patrolPos = [_enemyBasePos, 100, PATROL_RADIUS] call fnc_getPatrolPosition;
  591. _wp = _group addWaypoint [_patrolPos, 75];
  592. _wp setWaypointType "SAD";
  593. _wp setWaypointBehaviour "AWARE";
  594. _wp setWaypointSpeed "NORMAL";
  595. _wp setWaypointFormation _formation;
  596. _wp setWaypointCombatMode "RED";
  597. _wp setWaypointCompletionRadius 100;
  598. };
  599.  
  600. if (count (waypoints _group) > 1) then {
  601. _wp = _group addWaypoint [waypointPosition [_group, 1], 0];
  602. _wp setWaypointType "CYCLE";
  603. };
  604.  
  605. _group setBehaviour _behavior;
  606. _group setCombatMode "RED";
  607. _group setSpeedMode "NORMAL";
  608. };
  609.  
  610. fnc_createVehiclePatrolWaypoints = {
  611. params ["_group", "_enemyBasePos", "_ownBasePos"];
  612.  
  613. while {count (waypoints _group) > 0} do {
  614. deleteWaypoint ((waypoints _group) select 0);
  615. };
  616.  
  617. private _vehiclePatrolRadius = PATROL_RADIUS * 1.5;
  618.  
  619. for "_i" from 0 to 3 do {
  620. private _patrolPos = [_enemyBasePos, 200, _vehiclePatrolRadius, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
  621.  
  622. // MODIFIED: Robust fallback to prevent water waypoints.
  623. if (count _patrolPos == 0) then {
  624. // Try up to 10 times to find a random land position.
  625. for "_j" from 0 to 9 do {
  626. private _dir = random 360;
  627. private _dist = 200 + random (_vehiclePatrolRadius - 200);
  628. private _potentialPos = _enemyBasePos getPos [_dist, _dir];
  629. if (!surfaceIsWater _potentialPos) then {
  630. _patrolPos = _potentialPos;
  631. break;
  632. };
  633. };
  634. // If still no valid position, fallback to the base itself.
  635. if (count _patrolPos == 0) then {
  636. _patrolPos = _enemyBasePos;
  637. };
  638. };
  639.  
  640. private _wp = _group addWaypoint [_patrolPos, 50];
  641. _wp setWaypointType "SAD";
  642. _wp setWaypointBehaviour "AWARE";
  643. _wp setWaypointSpeed "NORMAL";
  644. _wp setWaypointCombatMode "RED";
  645. _wp setWaypointCompletionRadius 150;
  646. };
  647.  
  648. if (count (waypoints _group) > 0) then {
  649. private _wpCycle = _group addWaypoint [waypointPosition [_group, 0], 0];
  650. _wpCycle setWaypointType "CYCLE";
  651. };
  652.  
  653. _group setBehaviour "AWARE";
  654. _group setCombatMode "YELLOW";
  655. _group setSpeedMode "NORMAL";
  656. };
  657.  
  658. // Orders helicopters to patrol and attack targets from a safe altitude.
  659. fnc_createHeliPatrolWaypoints = {
  660. params ["_group", "_enemyBasePos"];
  661.  
  662. while {count (waypoints _group) > 0} do {
  663. deleteWaypoint ((waypoints _group) select 0);
  664. };
  665.  
  666. private _patrolRadius = PATROL_RADIUS * 3; // Larger radius for air patrols
  667. private _patrolAltitude = 150; // Fly 150m above ground
  668.  
  669. for "_i" from 0 to 3 do {
  670. // Find a random position in the patrol area
  671. private _patrolPosGround = [_enemyBasePos, 500, _patrolRadius] call fnc_getPatrolPosition;
  672. private _patrolPosAir = _patrolPosGround vectorAdd [0,0,_patrolAltitude];
  673.  
  674. private _wp = _group addWaypoint [_patrolPosAir, 50];
  675. _wp setWaypointType "SAD"; // Search and Destroy
  676. _wp setWaypointBehaviour "COMBAT"; // Immediately engage enemies
  677. _wp setWaypointSpeed "NORMAL";
  678. _wp setWaypointCombatMode "RED";
  679. _wp setWaypointCompletionRadius 300; // Larger radius to engage targets
  680. };
  681.  
  682. // Cycle waypoints for continuous patrol
  683. if (count (waypoints _group) > 0) then {
  684. private _wpCycle = _group addWaypoint [waypointPosition [_group, 0], 0];
  685. _wpCycle setWaypointType "CYCLE";
  686. };
  687.  
  688. // Set group combat parameters
  689. _group setBehaviour "COMBAT";
  690. _group setCombatMode "RED";
  691. _group setSpeedMode "NORMAL";
  692. };
  693.  
  694. // Orders vehicles to a defensive staging area near their own base to await HC orders.
  695. fnc_createVehicleStagingWaypoints = {
  696. params ["_group", "_ownBasePos"];
  697.  
  698. while {count (waypoints _group) > 0} do {
  699. deleteWaypoint ((waypoints _group) select 0);
  700. };
  701.  
  702. // Staging area radius, around own base
  703. private _stagingRadius = 600;
  704.  
  705. // Find a good defensive position near the base
  706. private _stagingPos = [_ownBasePos, 300, _stagingRadius, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
  707.  
  708. // MODIFIED: Robust fallback to prevent water waypoints.
  709. if (count _stagingPos == 0) then {
  710. // Try up to 10 times to find a random land position.
  711. for "_j" from 0 to 9 do {
  712. private _dir = random 360;
  713. private _dist = 300 + random (_stagingRadius - 300);
  714. private _potentialPos = _ownBasePos getPos [_dist, _dir];
  715. if (!surfaceIsWater _potentialPos) then {
  716. _stagingPos = _potentialPos;
  717. break;
  718. };
  719. };
  720. // If still no valid position, fallback to the base itself.
  721. if (count _stagingPos == 0) then {
  722. _stagingPos = _ownBasePos;
  723. };
  724. };
  725.  
  726. // Move to staging position
  727. private _wp = _group addWaypoint [_stagingPos, 50];
  728. _wp setWaypointType "MOVE";
  729. _wp setWaypointBehaviour "AWARE";
  730. _wp setWaypointSpeed "NORMAL";
  731. _wp setWaypointCombatMode "YELLOW"; // Be cautious
  732. _wp setWaypointCompletionRadius 150;
  733.  
  734. // After arriving, patrol the staging area defensively
  735. private _wp2 = _group addWaypoint [_stagingPos, 50];
  736. _wp2 setWaypointType "SAD"; // Search and Destroy in the local area
  737. _wp2 setWaypointBehaviour "AWARE";
  738. _wp2 setWaypointSpeed "NORMAL";
  739. _wp2 setWaypointCombatMode "RED"; // Engage threats
  740. _wp2 setWaypointCompletionRadius 200; // Large radius for defensive scan
  741.  
  742. // Create a small patrol loop in the staging area
  743. private _patrolPos2 = [_ownBasePos, 300, _stagingRadius, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
  744. if (count _patrolPos2 == 0) then { _patrolPos2 = _stagingPos; };
  745.  
  746. private _wp3 = _group addWaypoint [_patrolPos2, 50];
  747. _wp3 setWaypointType "SAD";
  748. _wp3 setWaypointBehaviour "AWARE";
  749. _wp3 setWaypointSpeed "NORMAL";
  750. _wp3 setWaypointCombatMode "RED";
  751. _wp3 setWaypointCompletionRadius 200;
  752.  
  753. // Cycle between the two SAD waypoints
  754. if (count (waypoints _group) > 2) then {
  755. private _wpCycle = _group addWaypoint [waypointPosition [_group, 1], 0]; // Cycle back to the first SAD WP
  756. _wpCycle setWaypointType "CYCLE";
  757. };
  758.  
  759. _group setBehaviour "AWARE";
  760. _group setCombatMode "YELLOW";
  761. _group setSpeedMode "NORMAL";
  762. };
  763.  
  764.  
  765. fnc_createStealthPatrol = {
  766. params ["_group", "_enemyBasePos"];
  767.  
  768. while {count (waypoints _group) > 0} do {
  769. deleteWaypoint ((waypoints _group) select 0);
  770. };
  771.  
  772. private _approachPos = [_enemyBasePos, PATROL_RADIUS, PATROL_RADIUS + 300, 10, 2, 0, 10] call BIS_fnc_findSafePos;
  773.  
  774. private _wp = _group addWaypoint [_approachPos, 30];
  775. _wp setWaypointType "MOVE";
  776. _wp setWaypointBehaviour "STEALTH";
  777. _wp setWaypointSpeed "LIMITED";
  778. _wp setWaypointFormation "FILE";
  779.  
  780. for "_i" from 1 to 3 do {
  781. private _patrolPos = [_enemyBasePos, 400, PATROL_RADIUS, 10, 2, 0, 10] call BIS_fnc_findSafePos;
  782. _wp = _group addWaypoint [_patrolPos, 50];
  783. _wp setWaypointType "MOVE";
  784. _wp setWaypointBehaviour "STEALTH";
  785. _wp setWaypointSpeed "LIMITED";
  786. _wp setWaypointFormation "FILE";
  787. _wp setWaypointCombatMode "GREEN";
  788. };
  789.  
  790. private _patrolPos = [_enemyBasePos, 200, 800, 10, 2, 0, 10] call BIS_fnc_findSafePos;
  791. _wp = _group addWaypoint [_patrolPos, 30];
  792. _wp setWaypointType "SAD";
  793. _wp setWaypointBehaviour "COMBAT";
  794. _wp setWaypointSpeed "NORMAL";
  795. _wp setWaypointFormation "LINE";
  796. _wp setWaypointCombatMode "RED";
  797.  
  798. if (count (waypoints _group) > 1) then {
  799. _wp = _group addWaypoint [waypointPosition [_group, 1], 0];
  800. _wp setWaypointType "CYCLE";
  801. };
  802. };
  803.  
  804. // Wave Spawning Functions
  805. fnc_spawnBluforWave = {
  806. params ["_waveType", ["_isInitialWave", false], ["_flankIndex", 0]];
  807. if (isNil "bluforSpawnObj") exitWith {grpNull};
  808. private _group = grpNull;
  809. switch (_waveType) do {
  810. case "infantry": {
  811. if (BLUFOR_POINTS >= (2 * 6 * INFANTRY_VALUE)) then {
  812. _group = [_isInitialWave, _flankIndex] call fnc_spawnBluforSquad;
  813. };
  814. };
  815. case "upgraded": {
  816. if (BLUFOR_POINTS >= (2 * 6 * UPGRADED_VALUE)) then {
  817. _group = [] call fnc_spawnBluforUpgradedSquad;
  818. };
  819. };
  820. case "specops": {
  821. if (BLUFOR_POINTS >= (2 * 6 * SPECOPS_VALUE)) then {
  822. _group = [] call fnc_spawnBluforSpecOps;
  823. };
  824. };
  825. case "sniper": {
  826. if (BLUFOR_POINTS >= (2 * 2 * SNIPER_VALUE)) then {
  827. _group = [] call fnc_spawnBluforSnipers;
  828. };
  829. };
  830. case "elite": {
  831. if (BLUFOR_POINTS >= (2 * 5 * ELITE_VALUE)) then {
  832. _group = [] call fnc_spawnBluforElite;
  833. };
  834. };
  835. case "light_vehicle": {
  836. if (BLUFOR_POINTS >= (2 * VEHICLE_VALUE)) then {
  837. _group = [] call fnc_spawnBluforLightVehicle;
  838. };
  839. };
  840. case "tank": {
  841. if (BLUFOR_POINTS >= (2 * TANK_VALUE)) then {
  842. _group = [] call fnc_spawnBluforTank;
  843. };
  844. };
  845. case "attack_heli": {
  846. if (BLUFOR_POINTS >= (2 * ATTACK_HELI_VALUE)) then {
  847. _group = [] call fnc_spawnBluforAttackHeli;
  848. };
  849. };
  850. };
  851. if (!isNull _group) then {
  852. private _waveHistory = BLUFOR_LAST_WAVES;
  853. _waveHistory pushBack _waveType;
  854. if (count _waveHistory > WAVE_HISTORY_SIZE) then {_waveHistory deleteAt 0;};
  855. BLUFOR_LAST_WAVES = _waveHistory;
  856. };
  857. _group
  858. };
  859.  
  860. fnc_spawnOpforWave = {
  861. params ["_waveType", ["_isInitialWave", false], ["_flankIndex", 0]];
  862. if (isNil "opforSpawnObj") exitWith {grpNull};
  863. private _group = grpNull;
  864. switch (_waveType) do {
  865. case "infantry": {
  866. if (OPFOR_POINTS >= (2 * 6 * INFANTRY_VALUE)) then {
  867. _group = [_isInitialWave, _flankIndex] call fnc_spawnOpforSquad;
  868. };
  869. };
  870. case "upgraded": {
  871. if (OPFOR_POINTS >= (2 * 6 * UPGRADED_VALUE)) then {
  872. _group = [] call fnc_spawnOpforUpgradedSquad;
  873. };
  874. };
  875. case "specops": {
  876. if (OPFOR_POINTS >= (2 * 6 * SPECOPS_VALUE)) then {
  877. _group = [] call fnc_spawnOpforSpecOps;
  878. };
  879. };
  880. case "sniper": {
  881. if (OPFOR_POINTS >= (2 * 2 * SNIPER_VALUE)) then {
  882. _group = [] call fnc_spawnOpforSnipers;
  883. };
  884. };
  885. case "elite": {
  886. if (OPFOR_POINTS >= (2 * 5 * ELITE_VALUE)) then {
  887. _group = [] call fnc_spawnOpforElite;
  888. };
  889. };
  890. case "light_vehicle": {
  891. if (OPFOR_POINTS >= (2 * VEHICLE_VALUE)) then {
  892. _group = [] call fnc_spawnOpforLightVehicle;
  893. };
  894. };
  895. case "tank": {
  896. if (OPFOR_POINTS >= (2 * TANK_VALUE)) then {
  897. _group = [] call fnc_spawnOpforTank;
  898. };
  899. };
  900. case "attack_heli": {
  901. if (OPFOR_POINTS >= (2 * ATTACK_HELI_VALUE)) then {
  902. _group = [] call fnc_spawnOpforAttackHeli;
  903. };
  904. };
  905. };
  906. if (!isNull _group) then {
  907. private _waveHistory = OPFOR_LAST_WAVES;
  908. _waveHistory pushBack _waveType;
  909. if (count _waveHistory > WAVE_HISTORY_SIZE) then {_waveHistory deleteAt 0;};
  910. OPFOR_LAST_WAVES = _waveHistory;
  911. };
  912. _group
  913. };
  914.  
  915. // BLUFOR Spawn Functions
  916. fnc_spawnBluforSquad = {
  917. params ["_isInitialWave", "_flankIndex"];
  918. if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 6 * INFANTRY_VALUE)) exitWith {grpNull};
  919. BLUFOR_POINTS = BLUFOR_POINTS - (2 * 6 * INFANTRY_VALUE);
  920. private ["_spawnPos", "_group", "_units"];
  921. _spawnPos = [west] call fnc_getSafeSpawnPosition;
  922. _group = createGroup west;
  923. _units = selectRandom BLUFOR_INFANTRY_POOL;
  924. {
  925. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  926. [_unit, 0.6, "REGULAR"] call fnc_enhanceIndividualAI;
  927. _unit setVariable ["unitValue", INFANTRY_VALUE, true];
  928. [_unit] call fnc_markAsImportant;
  929. [_unit, west] call fnc_updateStrengthTally;
  930. } forEach _units;
  931. if (_isInitialWave) then {
  932. [_group, west, _flankIndex] call fnc_createInitialFlankWaypoints;
  933. } else {
  934. [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
  935. };
  936. _group setVariable ["HC_FORCED_LOCK", true];
  937. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  938. _group
  939. };
  940.  
  941. fnc_spawnBluforUpgradedSquad = {
  942. if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 6 * UPGRADED_VALUE)) exitWith {grpNull};
  943. BLUFOR_POINTS = BLUFOR_POINTS - (2 * 6 * UPGRADED_VALUE);
  944. private ["_spawnPos", "_group", "_units"];
  945. _spawnPos = [west] call fnc_getSafeSpawnPosition;
  946. _group = createGroup west;
  947. _units = selectRandom BLUFOR_UPGRADED_POOL;
  948. {
  949. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  950. [_unit, 0.68, "UPGRADED"] call fnc_enhanceIndividualAI;
  951. _unit setVariable ["unitValue", UPGRADED_VALUE, true];
  952. [_unit] call fnc_markAsImportant;
  953. [_unit, west] call fnc_updateStrengthTally;
  954. } forEach _units;
  955. [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
  956. _group setVariable ["HC_FORCED_LOCK", true];
  957. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  958. _group
  959. };
  960.  
  961. fnc_spawnBluforSpecOps = {
  962. if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 6 * SPECOPS_VALUE)) exitWith {grpNull};
  963. BLUFOR_POINTS = BLUFOR_POINTS - (2 * 6 * SPECOPS_VALUE);
  964. private ["_spawnPos", "_group", "_units"];
  965. _spawnPos = [west] call fnc_getSafeSpawnPosition;
  966. _group = createGroup west;
  967. _units = selectRandom BLUFOR_SPECOPS_POOL;
  968. {
  969. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  970. [_unit, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
  971. _unit setVariable ["isSpecOps", true, true];
  972. _unit setVariable ["unitValue", SPECOPS_VALUE, true];
  973. [_unit] call fnc_markAsImportant;
  974. [_unit, west] call fnc_updateStrengthTally;
  975. } forEach _units;
  976. [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
  977. _group setVariable ["HC_FORCED_LOCK", true];
  978. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  979. _group
  980. };
  981.  
  982. fnc_spawnBluforSnipers = {
  983. if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 2 * SNIPER_VALUE)) exitWith {grpNull};
  984. BLUFOR_POINTS = BLUFOR_POINTS - (2 * 2 * SNIPER_VALUE);
  985. private ["_spawnPos", "_group", "_sniper", "_spotter"];
  986. _spawnPos = [west] call fnc_getSafeSpawnPosition;
  987. _group = createGroup west;
  988. _sniper = _group createUnit ["B_sniper_F", _spawnPos, [], 20, "NONE"];
  989. [_sniper, 0.8, "SNIPER"] call fnc_enhanceIndividualAI;
  990. _spotter = _group createUnit ["B_spotter_F", _spawnPos, [], 20, "NONE"];
  991. [_spotter, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
  992. _sniper setVariable ["isSniper", true, true]; _sniper setVariable ["unitValue", SNIPER_VALUE, true];
  993. _spotter setVariable ["isSniper", true, true]; _spotter setVariable ["unitValue", SNIPER_VALUE, true];
  994. [_sniper] call fnc_markAsImportant; [_spotter] call fnc_markAsImportant;
  995. [_sniper, west] call fnc_updateStrengthTally;
  996. [_spotter, west] call fnc_updateStrengthTally;
  997. // CHANGED: Use standard patrol waypoints to increase movement speed and impact
  998. [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
  999. _group setVariable ["HC_FORCED_LOCK", true];
  1000. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1001. _group
  1002. };
  1003.  
  1004. fnc_spawnBluforElite = {
  1005. if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 5 * ELITE_VALUE)) exitWith {grpNull};
  1006. BLUFOR_POINTS = BLUFOR_POINTS - (2 * 5 * ELITE_VALUE);
  1007. private ["_spawnPos", "_group", "_units"];
  1008. _spawnPos = [west] call fnc_getSafeSpawnPosition;
  1009. _group = createGroup west;
  1010. _units = selectRandom BLUFOR_ELITE_POOL;
  1011. {
  1012. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  1013. [_unit, 0.9, "ELITE"] call fnc_enhanceIndividualAI;
  1014. _unit setVariable ["isElite", true, true];
  1015. _unit setVariable ["unitValue", ELITE_VALUE, true];
  1016. [_unit] call fnc_markAsImportant;
  1017. [_unit, west] call fnc_updateStrengthTally;
  1018. } forEach _units;
  1019. [_group, getPos opforSpawnObj, "COMBAT"] call fnc_createPatrolWaypoints;
  1020. _group setVariable ["HC_FORCED_LOCK", true];
  1021. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1022. _group
  1023. };
  1024.  
  1025. // ===================================================================================
  1026. // ==================== OPTIMIZATION: GENERIC VEHICLE SPAWN LOGIC ====================
  1027. // ===================================================================================
  1028.  
  1029. // NEW: Generic vehicle spawning function to reduce code duplication and improve maintainability.
  1030. fnc_spawnVehicleGeneric = {
  1031. params [
  1032. "_side",
  1033. "_vehiclePool",
  1034. "_activeVehicleArrayName",
  1035. "_maxVehicleVarName",
  1036. "_vehicleValue",
  1037. "_crewSkill",
  1038. "_crewSkillRole",
  1039. "_spawnMode", // "NONE" for ground, "FLY" for air
  1040. "_taskType", // "GROUND_PATROL", "HELI_PATROL"
  1041. "_hcLockDuration"
  1042. ];
  1043.  
  1044. private _spawnObj = if (_side == west) then {bluforSpawnObj} else {opforSpawnObj};
  1045. if (isNil "_spawnObj") exitWith {grpNull};
  1046.  
  1047. // Dynamically get side-specific points and vehicle arrays
  1048. private _pointsVarName = if (_side == west) then {"BLUFOR_POINTS"} else {"OPFOR_POINTS"};
  1049. private _points = missionNamespace getVariable [_pointsVarName, 0];
  1050. private _activeVehicles = missionNamespace getVariable [_activeVehicleArrayName, []];
  1051. private _maxVehicleCount = missionNamespace getVariable [_maxVehicleVarName, 0];
  1052.  
  1053. // Check if affordable
  1054. if (_points < (2 * _vehicleValue)) exitWith {grpNull};
  1055.  
  1056. // Clean the array of destroyed/null vehicles and check the cap
  1057. _activeVehicles = _activeVehicles select {!isNull _x && alive _x};
  1058. if (count _activeVehicles >= _maxVehicleCount) exitWith {grpNull};
  1059.  
  1060. // Deduct points
  1061. missionNamespace setVariable [_pointsVarName, _points - (2 * _vehicleValue)];
  1062.  
  1063. private _basePos = getPos _spawnObj;
  1064. private _spawnPos = [];
  1065. if (_spawnMode == "FLY") then {
  1066. _spawnPos = _basePos vectorAdd [((random 200) - 100), ((random 200) - 100), 100];
  1067. } else {
  1068. _spawnPos = [_basePos, 50, 150, 15, 0, 0.4, 0] call BIS_fnc_findSafePos;
  1069. if (count _spawnPos == 0) then {
  1070. _spawnPos = _basePos; // Fallback to base position
  1071. };
  1072. };
  1073.  
  1074. // Create vehicle and crew
  1075. private _vehicleClass = selectRandom _vehiclePool;
  1076. private _vehicle = createVehicle [_vehicleClass, _spawnPos, [], 0, _spawnMode];
  1077. _vehicle setVectorUp [0,0,1];
  1078. if (_spawnMode == "FLY") then { _vehicle engineOn true; };
  1079.  
  1080. private _group = createGroup _side;
  1081. createVehicleCrew _vehicle;
  1082. (crew _vehicle) joinSilent _group;
  1083.  
  1084. // Setup crew
  1085. {
  1086. private _currentSkill = _crewSkill;
  1087. // Special case for pilots
  1088. if (_spawnMode == "FLY" && {_x == driver _vehicle}) then { _currentSkill = 0.95; };
  1089.  
  1090. [_x, _currentSkill, _crewSkillRole] call fnc_enhanceIndividualAI;
  1091. _x setVariable ["unitValue", _vehicleValue / (count (crew _vehicle)), true];
  1092. [_x] call fnc_markAsImportant;
  1093. } forEach (crew _vehicle);
  1094.  
  1095. // Setup vehicle
  1096. _vehicle setVariable ["unitValue", _vehicleValue, true];
  1097. [_vehicle] call fnc_markAsImportant;
  1098.  
  1099. // Update strength tally for the vehicle and its crew
  1100. [_vehicle, _side] call fnc_updateStrengthTally;
  1101. {
  1102. [_x, _side] call fnc_updateStrengthTally;
  1103. } forEach (crew _vehicle);
  1104.  
  1105. _activeVehicles pushBack _vehicle;
  1106. missionNamespace setVariable [_activeVehicleArrayName, _activeVehicles, true]; // Update the global array
  1107.  
  1108. // Assign task
  1109. switch (_taskType) do {
  1110. case "GROUND_PATROL": {
  1111. private _hotspot = [_side] call fnc_getBattleHotspot;
  1112. [_group, _hotspot, _basePos] call fnc_createVehiclePatrolWaypoints;
  1113. };
  1114. case "HELI_PATROL": {
  1115. private _hotspot = [_side] call fnc_getBattleHotspot;
  1116. [_group, _hotspot] call fnc_createHeliPatrolWaypoints;
  1117. };
  1118. };
  1119.  
  1120. // Set HC lock
  1121. _group setVariable ["HC_FORCED_LOCK", true];
  1122. [_group, _hcLockDuration] spawn {
  1123. params ["_grp", "_duration"];
  1124. sleep _duration;
  1125. if (!isNull _grp) then {
  1126. _grp setVariable ["HC_FORCED_LOCK", false];
  1127. };
  1128. };
  1129.  
  1130. _group
  1131. };
  1132.  
  1133. // ==================== REFACTORED VEHICLE SPAWN FUNCTIONS ====================
  1134.  
  1135. fnc_spawnBluforLightVehicle = {
  1136. [
  1137. west,
  1138. BLUFOR_LIGHT_VEHICLE_POOL,
  1139. "BLUFOR_ACTIVE_LIGHT_VEHICLES",
  1140. "maxLightVehiclesPerSide",
  1141. VEHICLE_VALUE,
  1142. 0.7,
  1143. "UPGRADED_INF",
  1144. "NONE",
  1145. "GROUND_PATROL",
  1146. 90
  1147. ] call fnc_spawnVehicleGeneric;
  1148. };
  1149.  
  1150. fnc_spawnBluforTank = {
  1151. [
  1152. west,
  1153. BLUFOR_TANK_POOL,
  1154. "BLUFOR_ACTIVE_TANKS",
  1155. "maxTanksPerSide",
  1156. TANK_VALUE,
  1157. 0.8,
  1158. "ELITE",
  1159. "NONE",
  1160. "GROUND_PATROL",
  1161. 90
  1162. ] call fnc_spawnVehicleGeneric;
  1163. };
  1164.  
  1165. fnc_spawnBluforAttackHeli = {
  1166. [
  1167. west,
  1168. BLUFOR_ATTACK_HELI_POOL,
  1169. "BLUFOR_ACTIVE_ATTACK_HELIS",
  1170. "maxAttackHelisPerSide",
  1171. ATTACK_HELI_VALUE,
  1172. 0.85,
  1173. "ELITE",
  1174. "FLY",
  1175. "HELI_PATROL",
  1176. 180
  1177. ] call fnc_spawnVehicleGeneric;
  1178. };
  1179.  
  1180. // OPFOR Spawn Functions
  1181. fnc_spawnOpforSquad = {
  1182. params ["_isInitialWave", "_flankIndex"];
  1183. if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 6 * INFANTRY_VALUE)) exitWith {grpNull};
  1184. OPFOR_POINTS = OPFOR_POINTS - (2 * 6 * INFANTRY_VALUE);
  1185. private ["_spawnPos", "_group", "_units"];
  1186. _spawnPos = [east] call fnc_getSafeSpawnPosition;
  1187. _group = createGroup east;
  1188. _units = selectRandom OPFOR_INFANTRY_POOL;
  1189. {
  1190. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  1191. [_unit, 0.6, "REGULAR"] call fnc_enhanceIndividualAI;
  1192. _unit setVariable ["unitValue", INFANTRY_VALUE, true];
  1193. [_unit] call fnc_markAsImportant;
  1194. [_unit, east] call fnc_updateStrengthTally;
  1195. } forEach _units;
  1196. if (_isInitialWave) then {
  1197. [_group, east, _flankIndex] call fnc_createInitialFlankWaypoints;
  1198. } else {
  1199. [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
  1200. };
  1201. _group setVariable ["HC_FORCED_LOCK", true];
  1202. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1203. _group
  1204. };
  1205.  
  1206. fnc_spawnOpforUpgradedSquad = {
  1207. if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 6 * UPGRADED_VALUE)) exitWith {grpNull};
  1208. OPFOR_POINTS = OPFOR_POINTS - (2 * 6 * UPGRADED_VALUE);
  1209. private ["_spawnPos", "_group", "_units"];
  1210. _spawnPos = [east] call fnc_getSafeSpawnPosition;
  1211. _group = createGroup east;
  1212. _units = selectRandom OPFOR_UPGRADED_POOL;
  1213. {
  1214. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  1215. [_unit, 0.68, "UPGRADED"] call fnc_enhanceIndividualAI;
  1216. _unit setVariable ["unitValue", UPGRADED_VALUE, true];
  1217. [_unit] call fnc_markAsImportant;
  1218. [_unit, east] call fnc_updateStrengthTally;
  1219. } forEach _units;
  1220. [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
  1221. _group setVariable ["HC_FORCED_LOCK", true];
  1222. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1223. _group
  1224. };
  1225.  
  1226. fnc_spawnOpforSpecOps = {
  1227. if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 6 * SPECOPS_VALUE)) exitWith {grpNull};
  1228. OPFOR_POINTS = OPFOR_POINTS - (2 * 6 * SPECOPS_VALUE);
  1229. private ["_spawnPos", "_group", "_units"];
  1230. _spawnPos = [east] call fnc_getSafeSpawnPosition;
  1231. _group = createGroup east;
  1232. _units = selectRandom OPFOR_SPECOPS_POOL;
  1233. {
  1234. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  1235. [_unit, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
  1236. _unit setVariable ["isSpecOps", true, true];
  1237. _unit setVariable ["unitValue", SPECOPS_VALUE, true];
  1238. [_unit] call fnc_markAsImportant;
  1239. [_unit, east] call fnc_updateStrengthTally;
  1240. } forEach _units;
  1241. [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
  1242. _group setVariable ["HC_FORCED_LOCK", true];
  1243. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1244. _group
  1245. };
  1246.  
  1247. fnc_spawnOpforSnipers = {
  1248. if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 2 * SNIPER_VALUE)) exitWith {grpNull};
  1249. OPFOR_POINTS = OPFOR_POINTS - (2 * 2 * SNIPER_VALUE);
  1250. private ["_spawnPos", "_group", "_sniper", "_spotter"];
  1251. _spawnPos = [east] call fnc_getSafeSpawnPosition;
  1252. _group = createGroup east;
  1253. _sniper = _group createUnit ["O_sniper_F", _spawnPos, [], 20, "NONE"];
  1254. [_sniper, 0.8, "SNIPER"] call fnc_enhanceIndividualAI;
  1255. _spotter = _group createUnit ["O_spotter_F", _spawnPos, [], 20, "NONE"];
  1256. [_spotter, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
  1257. _sniper setVariable ["isSniper", true, true]; _sniper setVariable ["unitValue", SNIPER_VALUE, true];
  1258. _spotter setVariable ["isSniper", true, true]; _spotter setVariable ["unitValue", SNIPER_VALUE, true];
  1259. [_sniper] call fnc_markAsImportant; [_spotter] call fnc_markAsImportant;
  1260. [_sniper, east] call fnc_updateStrengthTally;
  1261. [_spotter, east] call fnc_updateStrengthTally;
  1262. // CHANGED: Use standard patrol waypoints to increase movement speed and impact
  1263. [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
  1264. _group setVariable ["HC_FORCED_LOCK", true];
  1265. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1266. _group
  1267. };
  1268.  
  1269. fnc_spawnOpforElite = {
  1270. if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 5 * ELITE_VALUE)) exitWith {grpNull};
  1271. OPFOR_POINTS = OPFOR_POINTS - (2 * 5 * ELITE_VALUE);
  1272. private ["_spawnPos", "_group", "_units"];
  1273. _spawnPos = [east] call fnc_getSafeSpawnPosition;
  1274. _group = createGroup east;
  1275. _units = selectRandom OPFOR_ELITE_POOL;
  1276. {
  1277. private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
  1278. [_unit, 0.9, "ELITE"] call fnc_enhanceIndividualAI;
  1279. _unit setVariable ["isElite", true, true];
  1280. _unit setVariable ["unitValue", ELITE_VALUE, true];
  1281. [_unit] call fnc_markAsImportant;
  1282. [_unit, east] call fnc_updateStrengthTally;
  1283. } forEach _units;
  1284. [_group, getPos bluforSpawnObj, "COMBAT"] call fnc_createPatrolWaypoints;
  1285. _group setVariable ["HC_FORCED_LOCK", true];
  1286. [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
  1287. _group
  1288. };
  1289.  
  1290. fnc_spawnOpforLightVehicle = {
  1291. [
  1292. east,
  1293. OPFOR_LIGHT_VEHICLE_POOL,
  1294. "OPFOR_ACTIVE_LIGHT_VEHICLES",
  1295. "maxLightVehiclesPerSide",
  1296. VEHICLE_VALUE,
  1297. 0.7,
  1298. "UPGRADED_INF",
  1299. "NONE",
  1300. "GROUND_PATROL",
  1301. 90
  1302. ] call fnc_spawnVehicleGeneric;
  1303. };
  1304.  
  1305. fnc_spawnOpforTank = {
  1306. [
  1307. east,
  1308. OPFOR_TANK_POOL,
  1309. "OPFOR_ACTIVE_TANKS",
  1310. "maxTanksPerSide",
  1311. TANK_VALUE,
  1312. 0.8,
  1313. "ELITE",
  1314. "NONE",
  1315. "GROUND_PATROL",
  1316. 90
  1317. ] call fnc_spawnVehicleGeneric;
  1318. };
  1319.  
  1320. fnc_spawnOpforAttackHeli = {
  1321. [
  1322. east,
  1323. OPFOR_ATTACK_HELI_POOL,
  1324. "OPFOR_ACTIVE_ATTACK_HELIS",
  1325. "maxAttackHelisPerSide",
  1326. ATTACK_HELI_VALUE,
  1327. 0.85,
  1328. "ELITE",
  1329. "FLY",
  1330. "HELI_PATROL",
  1331. 180
  1332. ] call fnc_spawnVehicleGeneric;
  1333. };
  1334.  
  1335. fnc_createInitialFlankWaypoints = {
  1336. params ["_group", "_side", "_flankIndex"];
  1337.  
  1338. private _capturePos = (CAPTURE_LOCATIONS select 0) select 1;
  1339. private _basePos = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  1340.  
  1341. private _dirFromCaptureToBase = _capturePos getDir _basePos;
  1342. private _frontLineDistance = 200; // Distance from capture zone towards base
  1343. private _frontLinePos = _capturePos getPos [_frontLineDistance, _dirFromCaptureToBase];
  1344.  
  1345. private _flankOffset = 100; // Offset for left and right flanks
  1346. private _targetPos = _frontLinePos;
  1347.  
  1348. switch (_flankIndex) do {
  1349. case 0: { // Left flank
  1350. _targetPos = _frontLinePos getPos [_flankOffset, _dirFromCaptureToBase - 90];
  1351. };
  1352. case 1: { // Center
  1353. _targetPos = _frontLinePos;
  1354. };
  1355. case 2: { // Right flank
  1356. _targetPos = _frontLinePos getPos [_flankOffset, _dirFromCaptureToBase + 90];
  1357. };
  1358. };
  1359.  
  1360. private _safePos = [_targetPos, 0, 50, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
  1361. if (count _safePos == 0) then { _safePos = _targetPos; };
  1362.  
  1363. while {count (waypoints _group) > 0} do {
  1364. deleteWaypoint ((waypoints _group) select 0);
  1365. };
  1366.  
  1367. private _wp = _group addWaypoint [_safePos, 20];
  1368. _wp setWaypointType "MOVE";
  1369. _wp setWaypointStatements ["true", "{_x setUnitPos 'UP';} forEach units group this;"];
  1370. _wp setWaypointBehaviour "AWARE";
  1371. _wp setWaypointSpeed "NORMAL";
  1372. _wp setWaypointFormation "LINE";
  1373. _wp setWaypointCombatMode "YELLOW";
  1374.  
  1375. private _patrolWp1 = _group addWaypoint [_safePos, 75];
  1376. _patrolWp1 setWaypointType "SAD";
  1377. _patrolWp1 setWaypointBehaviour "COMBAT";
  1378. _patrolWp1 setWaypointCombatMode "RED";
  1379. _patrolWp1 setWaypointCompletionRadius 100;
  1380.  
  1381. private _patrolPos2 = [_targetPos, 50, 150, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
  1382. if (count _patrolPos2 == 0) then { _patrolPos2 = _targetPos; };
  1383.  
  1384. private _patrolWp2 = _group addWaypoint [_patrolPos2, 75];
  1385. _patrolWp2 setWaypointType "SAD";
  1386. _patrolWp2 setWaypointBehaviour "COMBAT";
  1387. _patrolWp2 setWaypointCombatMode "RED";
  1388. _patrolWp2 setWaypointCompletionRadius 100;
  1389.  
  1390. if (count (waypoints _group) > 2) then {
  1391. private _cycleWp = _group addWaypoint [waypointPosition [_group, 1], 0];
  1392. _cycleWp setWaypointType "CYCLE";
  1393. };
  1394.  
  1395. _group setBehaviour "AWARE";
  1396. _group setCombatMode "RED";
  1397. _group setSpeedMode "NORMAL";
  1398. };
  1399.  
  1400.  
  1401. // Function to find closest suitable infantry group for merging
  1402. fnc_findClosestInfantryGroup = {
  1403. params ["_smallGroup"];
  1404.  
  1405. private _smallGroupSide = side _smallGroup;
  1406. private _leader = leader _smallGroup;
  1407. if (isNull _leader) exitWith {grpNull};
  1408.  
  1409. private _smallGroupPos = getPos _leader;
  1410. private _closestGroup = grpNull;
  1411. private _closestDistance = 99999;
  1412.  
  1413. // ==================== CHANGE START: OPTIMIZED GROUP FINDING ====================
  1414. // Instead of looping allGroups, we search a 500m radius for friendly units. This is much faster.
  1415. private _nearbyFriendlies = _smallGroupPos nearEntities [["CAManBase"], 500];
  1416. private _candidateGroups = [];
  1417.  
  1418. {
  1419. private _unit = _x;
  1420. if (side _unit == _smallGroupSide && alive _unit) then {
  1421. private _grp = group _unit;
  1422. // Add the group to our candidates list, but only if it's not already there.
  1423. _candidateGroups pushBackUnique _grp;
  1424. };
  1425. } forEach _nearbyFriendlies;
  1426.  
  1427. // Now, we iterate the much smaller list of nearby candidate groups.
  1428. {
  1429. private _group = _x;
  1430.  
  1431. // Filter out unsuitable groups
  1432. if (
  1433. _group != _smallGroup &&
  1434. count (units _x select {alive _x}) > 2 &&
  1435. !([_group] call fnc_isGroupLocked) &&
  1436. ({isPlayer _x} count (units _group) == 0)
  1437. ) then {
  1438. private _grpLeader = leader _group;
  1439. if (!isNull _grpLeader && alive _grpLeader) then {
  1440. // Check if it's an infantry group (not in vehicles)
  1441. private _isInfantryGroup = true;
  1442. {
  1443. if (vehicle _x != _x) then { _isInfantryGroup = false; };
  1444. } forEach (units _group select {alive _x});
  1445.  
  1446. if (_isInfantryGroup) then {
  1447. private _distance = _smallGroupPos distance (getPos _grpLeader);
  1448. if (_distance < _closestDistance) then {
  1449. _closestDistance = _distance;
  1450. _closestGroup = _group;
  1451. };
  1452. };
  1453. };
  1454. };
  1455. } forEach _candidateGroups;
  1456. // ===================== CHANGE END =====================
  1457.  
  1458. _closestGroup
  1459. };
  1460.  
  1461. // Function to merge a small group into a larger one
  1462. fnc_mergeSmallGroup = {
  1463. params ["_smallGroup", "_targetGroup"];
  1464.  
  1465. if (isNull _smallGroup || isNull _targetGroup) exitWith {false};
  1466. if (_smallGroup == _targetGroup) exitWith {false};
  1467.  
  1468. private _unitsToMove = units _smallGroup select {alive _x};
  1469. if (count _unitsToMove == 0) exitWith {false};
  1470.  
  1471. // Move all alive units from small group to target group
  1472. {
  1473. [_x] joinSilent _targetGroup;
  1474. } forEach _unitsToMove;
  1475.  
  1476. // Clean up the old group from HC tracking if it exists
  1477. HC_Active_Groups deleteAt (groupId _smallGroup);
  1478.  
  1479. // Delete the now-empty group
  1480. deleteGroup _smallGroup;
  1481.  
  1482. true
  1483. };
  1484.  
  1485. // Function to check and process small groups for merging
  1486. fnc_checkAndMergeSmallGroups = {
  1487. params ["_group"];
  1488.  
  1489. if (isNull _group) exitWith {};
  1490.  
  1491. private _aliveUnits = units _group select {alive _x};
  1492. private _unitCount = count _aliveUnits;
  1493.  
  1494. // Check if group qualifies for merging (1-2 alive members)
  1495. if (_unitCount >= 1 && _unitCount <= 2) then {
  1496. // Don't merge player groups
  1497. if (({isPlayer _x} count _aliveUnits) > 0) exitWith {};
  1498.  
  1499. // Don't merge sniper squads (they're supposed to be 2-man teams)
  1500. if (({_x getVariable ["isSniper", false]} count _aliveUnits) > 0) exitWith {};
  1501.  
  1502. // Don't merge vehicle crews
  1503. private _isInfantryGroup = true;
  1504. {
  1505. if (vehicle _x != _x) then {
  1506. _isInfantryGroup = false;
  1507. };
  1508. } forEach _aliveUnits;
  1509.  
  1510. if (!_isInfantryGroup) exitWith {};
  1511.  
  1512. // Don't merge if HC locked
  1513. if ([_group] call fnc_isGroupLocked) exitWith {};
  1514.  
  1515. // Find closest suitable group
  1516. private _targetGroup = [_group] call fnc_findClosestInfantryGroup;
  1517.  
  1518. if (!isNull _targetGroup) then {
  1519. // Perform the merge
  1520. private _success = [_group, _targetGroup] call fnc_mergeSmallGroup;
  1521.  
  1522. if (_success) then {
  1523. // Add a small delay before the merged units can get new HC orders
  1524. [_targetGroup] spawn {
  1525. params ["_grp"];
  1526. sleep 5;
  1527. if (!isNull _grp) then {
  1528. _grp setVariable ["HC_FORCED_LOCK", false];
  1529. };
  1530. };
  1531. };
  1532. };
  1533. };
  1534. };
  1535.  
  1536. ==================== END OF: AIstuff.sqf ====================
  1537.  
  1538.  
  1539.  
  1540. ==================== START OF: arsenal.sqf ====================
  1541.  
  1542. // arsenal.sqf
  1543. // Purpose: Creates arsenal boxes for BLUFOR and OPFOR with purchase menu and clear effects actions.
  1544. // REVISED: Added action to clear blurry post-processing effects.
  1545. // REVISED: Fixed action code to use string to prevent race conditions.
  1546. // REVISED: Removed player-purchased AI squad functionality.
  1547. // REVISED: Fixed RadialBlur ppEffectAdjust to use 4 parameters to resolve scripting error.
  1548.  
  1549. // Function to create an arsenal box at a given position for a specific side
  1550. fnc_createBaseArsenal = {
  1551. params ["_pos", "_side"];
  1552.  
  1553. private _arsenalBox = createVehicle ["B_supplyCrate_F", _pos, [], 0, "CAN_COLLIDE"];
  1554.  
  1555. _arsenalBox allowDamage false;
  1556. _arsenalBox enableSimulationGlobal false;
  1557. _arsenalBox enableDynamicSimulation false;
  1558.  
  1559. _arsenalBox setVariable ["gcImportant", true, true];
  1560. _arsenalBox setVariable ["arsenalSide", _side, true];
  1561.  
  1562. // Clear all cargo
  1563. clearWeaponCargoGlobal _arsenalBox;
  1564. clearMagazineCargoGlobal _arsenalBox;
  1565. clearItemCargoGlobal _arsenalBox;
  1566. clearBackpackCargoGlobal _arsenalBox;
  1567.  
  1568. [_arsenalBox, [], true] call BIS_fnc_removeVirtualItemCargo;
  1569.  
  1570. // Load uniform restrictions if not defined
  1571. if (isNil "BLUFOR_UNIFORMS") then {
  1572. [] call compile preprocessFileLineNumbers "loadoutlimitations.sqf";
  1573. };
  1574.  
  1575. // Add virtual cargo
  1576. [_arsenalBox, true, true] call BIS_fnc_addVirtualWeaponCargo;
  1577. [_arsenalBox, true, true] call BIS_fnc_addVirtualMagazineCargo;
  1578. [_arsenalBox, true, true] call BIS_fnc_addVirtualBackpackCargo;
  1579.  
  1580. // Filter clothing items
  1581. private _allItems = ("getNumber (_x >> 'scope') >= 2" configClasses (configFile >> "CfgWeapons")) apply {configName _x};
  1582. private _allUniforms = _allItems select { isClass (configFile >> "CfgWeapons" >> _x >> "ItemInfo") && { getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "type") == 801 } };
  1583. private _allVests = _allItems select { isClass (configFile >> "CfgWeapons" >> _x >> "ItemInfo") && { getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "type") == 701 } };
  1584. private _allHeadgear = _allItems select { isClass (configFile >> "CfgWeapons" >> _x >> "ItemInfo") && { getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "type") == 605 } };
  1585.  
  1586. private _allClothing = _allUniforms + _allVests + _allHeadgear;
  1587. private _itemsWithoutClothing = _allItems - _allClothing;
  1588.  
  1589. [_arsenalBox, _itemsWithoutClothing, true] call BIS_fnc_addVirtualItemCargo;
  1590.  
  1591. // Set side-specific clothing and arsenal name
  1592. private _allowedClothing = [];
  1593. if (_side == west) then {
  1594. _allowedClothing = BLUFOR_UNIFORMS + BLUFOR_VESTS + BLUFOR_HEADGEAR;
  1595. _arsenalBox setVariable ["arsenalName", "BLUFOR Arsenal", true];
  1596. } else {
  1597. _allowedClothing = OPFOR_UNIFORMS + OPFOR_VESTS + OPFOR_HEADGEAR;
  1598. _arsenalBox setVariable ["arsenalName", "OPFOR Arsenal", true];
  1599. };
  1600.  
  1601. [_arsenalBox, _allowedClothing, true] call BIS_fnc_addVirtualItemCargo;
  1602.  
  1603. // Ensure box is grounded
  1604. private _groundPos = _pos;
  1605. _groundPos set [2, 0];
  1606. _arsenalBox setPosATL _groundPos;
  1607. _arsenalBox setVectorUp [0,0,1];
  1608.  
  1609. _arsenalBox
  1610. };
  1611.  
  1612. // Function to add purchase menu action to arsenal box (client-side)
  1613. fnc_client_addPurchaseAction = {
  1614. params ["_arsenalBox"];
  1615. if (!hasInterface) exitWith {};
  1616.  
  1617. _arsenalBox addAction [
  1618. "<t color='#FFD700'>[Open Purchase Menu]</t>",
  1619. {
  1620. [] call fnc_client_openPlayerMenu;
  1621. },
  1622. [],
  1623. 6,
  1624. true,
  1625. true,
  1626. "",
  1627. "side _this == (_target getVariable ['arsenalSide', sideUnknown]) && alive _this",
  1628. 10
  1629. ];
  1630. };
  1631.  
  1632. // Function to add clear effects action to arsenal box (client-side)
  1633. fnc_client_addClearEffectsAction = {
  1634. params ["_arsenalBox"];
  1635. if (!hasInterface) exitWith {};
  1636.  
  1637. _arsenalBox addAction [
  1638. "<t color='#00FF00'>[Clear Blurry Effects]</t>",
  1639. {
  1640. // Destroy all post-processing effects across all priority ranges
  1641. for "_i" from 0 to 2000 do {
  1642. ppEffectDestroy _i;
  1643. };
  1644.  
  1645. // Create and immediately disable clean effects to reset them
  1646. "DynamicBlur" ppEffectEnable false;
  1647. "RadialBlur" ppEffectEnable false;
  1648. "chromAberration" ppEffectEnable false;
  1649. "ColorCorrections" ppEffectEnable false;
  1650. "FilmGrain" ppEffectEnable false;
  1651. "WetDistortion" ppEffectEnable false;
  1652. "ColorInversion" ppEffectEnable false;
  1653.  
  1654. // Reset camera shake
  1655. enableCamShake false;
  1656. resetCamShake;
  1657.  
  1658. // Reset any potential BIS effects
  1659. BIS_fnc_feedback_allowPP = true;
  1660. BIS_fnc_feedback_allowDeathScreen = true;
  1661.  
  1662. hint "All visual effects cleared.";
  1663. },
  1664. [],
  1665. 5,
  1666. true,
  1667. true,
  1668. "",
  1669. "side _this == (_target getVariable ['arsenalSide', sideUnknown]) && alive _this",
  1670. 10
  1671. ];
  1672. };
  1673.  
  1674. // ==================== CHANGE START ====================
  1675. // NEW: Function to add settings menu action to arsenal box (client-side)
  1676. fnc_client_addSettingsAction = {
  1677. params ["_arsenalBox"];
  1678. if (!hasInterface) exitWith {};
  1679.  
  1680. _arsenalBox addAction [
  1681. "<t color='#ADD8E6'>[Open Settings Menu]</t>",
  1682. {
  1683. createDialog "AdminSettingsMenu";
  1684. },
  1685. [],
  1686. 4, // Priority below purchase and clear effects
  1687. true,
  1688. true,
  1689. "",
  1690. // Condition: Show if in singleplayer, or if the player is the server (host) in multiplayer.
  1691. "(!isMultiplayer) || (isServer)"
  1692. ];
  1693. };
  1694. // ===================== CHANGE END =====================
  1695.  
  1696. // Server-side execution to create arsenals and broadcast actions
  1697. if (isServer) then {
  1698. [] spawn {
  1699. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  1700. sleep 1;
  1701.  
  1702. private _bluforBasePos = getPosATL bluforSpawnObj;
  1703. _bluforBasePos set [2, 0];
  1704. private _bluforArsenal = [_bluforBasePos, west] call fnc_createBaseArsenal;
  1705.  
  1706. private _opforBasePos = getPosATL opforSpawnObj;
  1707. _opforBasePos set [2, 0];
  1708. private _opforArsenal = [_opforBasePos, east] call fnc_createBaseArsenal;
  1709.  
  1710. missionNamespace setVariable ["BLUFOR_Arsenal", _bluforArsenal, true];
  1711. missionNamespace setVariable ["OPFOR_Arsenal", _opforArsenal, true];
  1712.  
  1713. // Broadcast purchase menu action
  1714. [_bluforArsenal] remoteExecCall ["fnc_client_addPurchaseAction", 0, true];
  1715. [_opforArsenal] remoteExecCall ["fnc_client_addPurchaseAction", 0, true];
  1716.  
  1717. // Broadcast clear effects action
  1718. [_bluforArsenal] remoteExecCall ["fnc_client_addClearEffectsAction", 0, true];
  1719. [_opforArsenal] remoteExecCall ["fnc_client_addClearEffectsAction", 0, true];
  1720.  
  1721. // ==================== CHANGE START ====================
  1722. // NEW: Broadcast settings menu action
  1723. [_bluforArsenal] remoteExecCall ["fnc_client_addSettingsAction", 0, true];
  1724. [_opforArsenal] remoteExecCall ["fnc_client_addSettingsAction", 0, true];
  1725. // ===================== CHANGE END =====================
  1726. };
  1727. };
  1728.  
  1729. ==================== END OF: arsenal.sqf ====================
  1730.  
  1731.  
  1732.  
  1733. ==================== START OF: botai.sqf ====================
  1734.  
  1735. // botai.sqf
  1736. // Centralized AI manager for improved performance and tactical behaviors
  1737.  
  1738. // ==================== PERFORMANCE MONITORING SYSTEM ====================
  1739. // Global performance state - accessed by all systems
  1740. if (isNil "PERF_STATE") then {
  1741. PERF_STATE = "EXCELLENT"; // EXCELLENT, GOOD, MODERATE, POOR, CRITICAL, EMERGENCY
  1742. };
  1743. if (isNil "PERF_MULTIPLIER") then {
  1744. PERF_MULTIPLIER = 1.0; // Multiplier for sleep intervals (1.0 = normal, up to 8.0 = extreme slowdown)
  1745. };
  1746.  
  1747. // Monitor server performance and adjust intervals dynamically
  1748. fnc_monitorPerformance = {
  1749. private _deltaHistory = [];
  1750. private _execTimeHistory = [];
  1751. private _historySize = 10;
  1752.  
  1753. while {true} do {
  1754. // Measure scheduler frame delta time (works in SP and MP)
  1755. private _schedulerDelta = diag_deltaTime;
  1756.  
  1757. // Get current script execution time from main AI loop
  1758. private _lastExecTime = missionNamespace getVariable ["BOTAI_lastExecTime", 0];
  1759.  
  1760. // Add to histories
  1761. _deltaHistory pushBack _schedulerDelta;
  1762. _execTimeHistory pushBack _lastExecTime;
  1763.  
  1764. if (count _deltaHistory > _historySize) then {
  1765. _deltaHistory deleteAt 0;
  1766. };
  1767. if (count _execTimeHistory > _historySize) then {
  1768. _execTimeHistory deleteAt 0;
  1769. };
  1770.  
  1771. // Calculate averages
  1772. private _avgDelta = 0;
  1773. {_avgDelta = _avgDelta + _x;} forEach _deltaHistory;
  1774. _avgDelta = _avgDelta / count _deltaHistory;
  1775.  
  1776. private _avgExecTime = 0;
  1777. {_avgExecTime = _avgExecTime + _x;} forEach _execTimeHistory;
  1778. _avgExecTime = _avgExecTime / count _execTimeHistory;
  1779.  
  1780. // Determine performance state based on scheduler delta
  1781. // Excellent: <0.025s (40+ scheduler fps)
  1782. // Good: 0.025-0.040s (25-40 scheduler fps) - LOWERED from 0.035
  1783. // Moderate: 0.040-0.060s (16-25 scheduler fps) - LOWERED from 0.050
  1784. // Poor: 0.060-0.090s (11-16 scheduler fps) - LOWERED from 0.075
  1785. // Critical: 0.090-0.120s (8-11 scheduler fps) - LOWERED from 0.100
  1786. // Emergency: >0.120s (<8 scheduler fps) - LOWERED from 0.100
  1787.  
  1788. private _deltaState = "EXCELLENT";
  1789. private _deltaMultiplier = 1.0;
  1790.  
  1791. if (_avgDelta < 0.025) then {
  1792. _deltaState = "EXCELLENT";
  1793. _deltaMultiplier = 1.0;
  1794. } else {
  1795. if (_avgDelta < 0.040) then {
  1796. _deltaState = "GOOD";
  1797. _deltaMultiplier = 1.5;
  1798. } else {
  1799. if (_avgDelta < 0.060) then {
  1800. _deltaState = "MODERATE";
  1801. _deltaMultiplier = 2.5;
  1802. } else {
  1803. if (_avgDelta < 0.090) then {
  1804. _deltaState = "POOR";
  1805. _deltaMultiplier = 4.0;
  1806. } else {
  1807. if (_avgDelta < 0.120) then {
  1808. _deltaState = "CRITICAL";
  1809. _deltaMultiplier = 6.0;
  1810. } else {
  1811. _deltaState = "EMERGENCY";
  1812. _deltaMultiplier = 10.0;
  1813. };
  1814. };
  1815. };
  1816. };
  1817. };
  1818.  
  1819. // Check script execution time (secondary metric)
  1820. // If AI loop takes >50ms per cycle, we're in trouble
  1821. private _execState = "EXCELLENT";
  1822. private _execMultiplier = 1.0;
  1823.  
  1824. if (_avgExecTime < 0.020) then {
  1825. _execState = "EXCELLENT";
  1826. _execMultiplier = 1.0;
  1827. } else {
  1828. if (_avgExecTime < 0.040) then {
  1829. _execState = "GOOD";
  1830. _execMultiplier = 1.5;
  1831. } else {
  1832. if (_avgExecTime < 0.060) then {
  1833. _execState = "MODERATE";
  1834. _execMultiplier = 2.5;
  1835. } else {
  1836. if (_avgExecTime < 0.090) then {
  1837. _execState = "POOR";
  1838. _execMultiplier = 4.0;
  1839. } else {
  1840. if (_avgExecTime < 0.120) then {
  1841. _execState = "CRITICAL";
  1842. _execMultiplier = 6.0;
  1843. } else {
  1844. _execState = "EMERGENCY";
  1845. _execMultiplier = 10.0;
  1846. };
  1847. };
  1848. };
  1849. };
  1850. };
  1851.  
  1852. // Use the WORST of the two metrics
  1853. private _oldState = PERF_STATE;
  1854. private _oldMultiplier = PERF_MULTIPLIER;
  1855.  
  1856. private _worstMultiplier = _deltaMultiplier max _execMultiplier;
  1857.  
  1858. if (_worstMultiplier == _deltaMultiplier) then {
  1859. PERF_STATE = _deltaState;
  1860. PERF_MULTIPLIER = _deltaMultiplier;
  1861. } else {
  1862. PERF_STATE = _execState;
  1863. PERF_MULTIPLIER = _execMultiplier;
  1864. };
  1865.  
  1866. // Set skip flags for non-critical operations
  1867. PERF_SKIP_NONCRITICAL = (PERF_STATE in ["CRITICAL", "EMERGENCY"]);
  1868. PERF_SKIP_MODERATE = (PERF_STATE == "EMERGENCY");
  1869.  
  1870. // Log state changes
  1871. if (_oldState != PERF_STATE) then {
  1872. diag_log format ["[PERF] State: %1 -> %2 (x%3) | Delta: %4ms (%5) | Exec: %6ms (%7)",
  1873. _oldState, PERF_STATE, PERF_MULTIPLIER,
  1874. round (_avgDelta * 1000), _deltaState,
  1875. round (_avgExecTime * 1000), _execState];
  1876. };
  1877.  
  1878. // Adaptive monitoring interval
  1879. if (PERF_STATE == "EXCELLENT") then {
  1880. sleep 3;
  1881. } else {
  1882. if (PERF_STATE in ["CRITICAL", "EMERGENCY"]) then {
  1883. sleep 0.5; // Monitor very frequently when critical
  1884. } else {
  1885. sleep 1.5;
  1886. };
  1887. };
  1888. };
  1889. };
  1890.  
  1891. // Helper function to get adaptive sleep time
  1892. fnc_getAdaptiveSleep = {
  1893. params ["_baseSleep"];
  1894. (_baseSleep * PERF_MULTIPLIER)
  1895. };
  1896. // ==================== END PERFORMANCE MONITORING ====================
  1897.  
  1898. // Set AI skills based on level and role
  1899. fnc_botSetSkills = {
  1900. params ["_unit", "_skillLevel", "_unitRole"];
  1901.  
  1902. _unit setSkill ["aimingAccuracy", (_skillLevel + 0.5) min 1];
  1903. _unit setSkill ["aimingSpeed", (_skillLevel + 0.2) min 1];
  1904. _unit setSkill ["aimingShake", 0];
  1905. _unit setSkill ["courage", 1.0];
  1906. _unit setSkill ["commanding", 1.0];
  1907. _unit setSkill ["reloadSpeed", 1.0];
  1908. _unit setSkill ["spotDistance", 1.0];
  1909. _unit setSkill ["spotTime", 1.0];
  1910. _unit setSkill ["endurance", 1.0];
  1911. };
  1912.  
  1913. // Force AI to throw smoke grenade using correct muzzle
  1914. fnc_botUseSmoke = {
  1915. params ["_unit", "_purpose"];
  1916.  
  1917. if ("SmokeShell" in (magazines _unit)) then {
  1918. _unit forceWeaponFire ["SmokeShellMuzzle", "SmokeShellMuzzle"];
  1919. };
  1920. };
  1921.  
  1922. // Enhance individual AI unit during spawning
  1923. fnc_enhanceIndividualAI = {
  1924. params ["_unit", "_skillLevel", "_unitRole"];
  1925.  
  1926. if (isNil "_unit" || {isNull _unit} || {!alive _unit}) exitWith {
  1927. diag_log "Error: _unit is invalid in fnc_enhanceIndividualAI";
  1928. };
  1929.  
  1930. if (_unit getVariable ["BOTAI_isEnhanced", false]) exitWith {};
  1931. _unit setVariable ["BOTAI_isEnhanced", true, true];
  1932.  
  1933. // Check if unit is a helicopter pilot/gunner
  1934. private _vehicle = vehicle _unit;
  1935. if (_vehicle != _unit && {_vehicle isKindOf "Air"}) then {
  1936. // Enhanced skills for helicopter crews
  1937. _unit setSkill ["aimingAccuracy", 0.85];
  1938. _unit setSkill ["aimingSpeed", 0.9];
  1939. _unit setSkill ["aimingShake", 0.1];
  1940. _unit setSkill ["courage", 1.0];
  1941. _unit setSkill ["commanding", 1.0];
  1942. _unit setSkill ["reloadSpeed", 1.0];
  1943. _unit setSkill ["spotDistance", 1.0];
  1944. _unit setSkill ["spotTime", 0.9];
  1945. _unit setSkill ["endurance", 1.0];
  1946.  
  1947. // Add flares to helicopters if not present
  1948. if (driver _vehicle == _unit) then {
  1949. private _hasFlares = (magazinesAllTurrets _vehicle) findIf {(_x select 0) find "CMFlare" >= 0} != -1;
  1950. if (!_hasFlares) then {
  1951. _vehicle addMagazineTurret ["120Rnd_CMFlare_Chaff_Magazine", [-1]];
  1952. };
  1953. };
  1954. } else {
  1955. // Original ground unit skills
  1956. [_unit, _skillLevel, _unitRole] call fnc_botSetSkills;
  1957. };
  1958.  
  1959. // Ensure unit has a smoke grenade for tactical use
  1960. private _hasSmoke = false;
  1961. { if (_x find "SmokeShell" >= 0) exitWith { _hasSmoke = true; }; } forEach (magazines _unit);
  1962. if (!_hasSmoke && vehicle _unit == _unit) then {
  1963. _unit addMagazine "SmokeShell";
  1964. };
  1965. };
  1966.  
  1967. fnc_checkFoliageObstruction = {
  1968. params ["_unit", "_target"];
  1969.  
  1970. private _cacheKey = format ["%1_%2", netId _unit, netId _target];
  1971. private _cachedResult = _unit getVariable ["foliageCache_" + _cacheKey, []];
  1972. if (count _cachedResult > 0 && {time - (_cachedResult select 1) < 2}) exitWith {
  1973. _cachedResult select 0
  1974. };
  1975.  
  1976. private _unitEyePos = eyePos _unit;
  1977. private _targetEyePos = eyePos _target;
  1978.  
  1979. // --- Phase 1: Fast Engine Checks ---
  1980. private _visibility = [_unit, "VIEW", _target] checkVisibility [_unitEyePos, _targetEyePos];
  1981. if (_visibility < 0.2) exitWith {
  1982. _unit setVariable ["foliageCache_" + _cacheKey, [1, time]];
  1983. 1
  1984. };
  1985.  
  1986. private _terrainBlocked = terrainIntersectASL [_unitEyePos, _targetEyePos];
  1987. if (_terrainBlocked) exitWith {
  1988. _unit setVariable ["foliageCache_" + _cacheKey, [1, time]];
  1989. 1
  1990. };
  1991.  
  1992. // --- Phase 2: Detailed (Perf-Scaled) ---
  1993. private _intersectionsCount = 0;
  1994. private _foliageHits = 0;
  1995. private _dist = _unit distance2D _target;
  1996.  
  1997. private _perfScale = 1.0;
  1998. if (!isNil "PERF_STATE") then {
  1999. if (typeName PERF_STATE == "SCALAR") then {
  2000. _perfScale = (PERF_STATE max 1 min 10) / 10;
  2001. };
  2002. };
  2003. private _maxCheckDistance = 150 + 850 * _perfScale;
  2004.  
  2005. if (_dist < _maxCheckDistance) then {
  2006. // OPTIMIZATION: Reduced from 4 points to 2 (Head and Torso/Center)
  2007. // This provides sufficient data for AI decision making while saving performance
  2008. private _targetPositions = [
  2009. _targetEyePos,
  2010. (getPosASL _target) vectorAdd [0, 0, 0.8]
  2011. ];
  2012.  
  2013. // Surfaces: true=all LODs, 3=terrain(1)+objects(2)
  2014. {
  2015. if (count (lineIntersectsSurfaces [_unitEyePos, _x, _unit, _target, true, 3]) > 0) then {
  2016. _intersectionsCount = _intersectionsCount + 1;
  2017. };
  2018. } forEach _targetPositions;
  2019.  
  2020. // Foliage proxy: Reduced from 3 to 2 raymarch samples
  2021. private _dirVec = _targetEyePos vectorDiff _unitEyePos;
  2022. private _stepVec = _dirVec vectorMultiply (1 / 3);
  2023. private _samplePos = _unitEyePos vectorAdd _stepVec;
  2024. for "_s" from 1 to 2 do {
  2025. private _nearFoliage = nearestTerrainObjects [_samplePos, ["BUSH", "TREE", "SMALL TREE"], 2.2, false, true];
  2026. if (count _nearFoliage > 0) then { _foliageHits = _foliageHits + 1; };
  2027. _samplePos = _samplePos vectorAdd _stepVec;
  2028. };
  2029. };
  2030.  
  2031. // --- Phase 3: Score ---
  2032. // Adjusted scoring for reduced sample count
  2033. private _surfacePenalty = switch (_intersectionsCount) do {
  2034. case 0: {0};
  2035. case 1: {0.5}; // Adjusted from 0.4
  2036. default {1.0};
  2037. };
  2038. private _foliagePenalty = if (_foliageHits > 0) then {0.75} else {0};
  2039.  
  2040. private _totalObstruction = (_surfacePenalty max _foliagePenalty) max (1 - _visibility);
  2041. _totalObstruction = _totalObstruction min 1 max 0;
  2042.  
  2043. _unit setVariable ["foliageCache_" + _cacheKey, [_totalObstruction, time]];
  2044. _totalObstruction
  2045. };
  2046.  
  2047. // Predicts enemy future pos using last known velocity (for ambushes/flanks)
  2048. fnc_predictEnemyMove = {
  2049. params ["_unit", "_enemyID"];
  2050.  
  2051. private _sideIntel = if (side _unit == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  2052. private _report = _sideIntel get _enemyID;
  2053. if (isNil "_report") exitWith {[0,0,0,0]};
  2054.  
  2055. private _lastPos = _report get "position";
  2056. private _vel = _report get "velocity";
  2057. private _lastTime = _report getOrDefault ["lastUpdate", 0];
  2058.  
  2059. private _deltaT = (time - _lastTime) min 15; // Cap at 15s prediction
  2060. private _predictedPos = _lastPos vectorAdd (_vel vectorMultiply _deltaT);
  2061.  
  2062. _predictedPos // [x,y,z]
  2063. };
  2064.  
  2065. // Leader-only: If recent enemy lost LOS, predict move & flank to intercept/cover
  2066. fnc_attemptPredictionAmbush = {
  2067. params ["_unit"];
  2068.  
  2069. private _group = group _unit;
  2070. private _lastAmbushCheck = _group getVariable ["lastAmbushCheck", 0];
  2071. if (time - _lastAmbushCheck < (20 * PERF_MULTIPLIER)) exitWith {}; // Perf cooldown
  2072. _group setVariable ["lastAmbushCheck", time];
  2073.  
  2074. private _sideIntel = if (side _unit == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  2075. private _recentEnemies = [];
  2076.  
  2077. // Scan recent intel (last 30s) for "lost" enemies (knowsAbout low but fresh)
  2078. {
  2079. private _report = _y;
  2080. private _age = time - (_report get "time");
  2081. private _enemyID = _x;
  2082. if (_age < 30 && {(_group knowsAbout (objectFromNetId _enemyID)) < 1.5}) then {
  2083. _recentEnemies pushBack _enemyID;
  2084. };
  2085. } forEach _sideIntel;
  2086.  
  2087. if (count _recentEnemies == 0) exitWith {};
  2088.  
  2089. private _targetID = selectRandom _recentEnemies;
  2090. private _predictedPos = [_unit, _targetID] call fnc_predictEnemyMove;
  2091. if (count _predictedPos < 3) exitWith {};
  2092.  
  2093. // Find flank intercept: 45deg offset + cover
  2094. private _unitPos = getPosATL _unit;
  2095. private _dirToPred = _unitPos getDir _predictedPos;
  2096. private _flank1 = _predictedPos getPos [50, _dirToPred - 45];
  2097. private _flank2 = _predictedPos getPos [50, _dirToPred + 45];
  2098.  
  2099. private _interceptPos = [_flank1, _flank2] select {
  2100. private _coverScore = ([_x] call fnc_evaluateTerrainCover) get "score";
  2101. _coverScore > 25 // Reuse existing cover eval
  2102. } select 0;
  2103.  
  2104. if (isNil "_interceptPos") then { _interceptPos = _flank1; };
  2105.  
  2106. // Quick safe pos + waypoint (overrides if CQB/evade not active)
  2107. if !(_group getVariable ["isDoingCQB", false]) then {
  2108. _interceptPos = [_interceptPos, 0, 20, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
  2109. while {count (waypoints _group) > 1} do { deleteWaypoint ((waypoints _group) select 0); };
  2110. private _wp = _group addWaypoint [_interceptPos, 30];
  2111. _wp setWaypointType "SAD";
  2112. _wp setWaypointBehaviour "STEALTH";
  2113. _wp setWaypointSpeed "LIMITED";
  2114. diag_log format ["[BOTAI] %1 predicts & ambushes at %2", groupId _group, _interceptPos];
  2115. };
  2116. };
  2117.  
  2118. // Dynamic morale: Losses → retreat to cover. Recovers after safe time.
  2119. fnc_manageDynamicMorale = {
  2120. params ["_group"];
  2121.  
  2122. private _lastMoraleCheck = _group getVariable ["lastMoraleCheck", 0];
  2123. if (time - _lastMoraleCheck < (25 * PERF_MULTIPLIER)) exitWith {}; // Cooldown
  2124. _group setVariable ["lastMoraleCheck", time];
  2125.  
  2126. private _initialUnits = _group getVariable ["initialGroupStrength", -1];
  2127. if (_initialUnits == -1) then {
  2128. _initialUnits = {alive _x} count units _group;
  2129. _group setVariable ["initialGroupStrength", _initialUnits];
  2130. };
  2131.  
  2132. private _currentUnits = {alive _x && vehicle _x == _x} count units _group;
  2133. if (_initialUnits <= 0) then { _initialUnits = 1; }; // Prevent division by zero
  2134. private _moralePct = (_currentUnits / _initialUnits) max 0;
  2135.  
  2136. // Recover initial if full strength >30s (no recent losses)
  2137. if (_moralePct > 0.7 && time - (_group getVariable ["lastLossTime", 0]) > 30) then {
  2138. _group setVariable ["initialGroupStrength", _currentUnits];
  2139. _group setVariable ["moraleLow", false];
  2140. _group setVariable ["retreatActive", false];
  2141. };
  2142.  
  2143. if (_moralePct < 0.3 && !(_group getVariable ["moraleLow", false])) then {
  2144. _group setVariable ["moraleLow", true];
  2145. _group setVariable ["lastLossTime", time];
  2146.  
  2147. private _leader = leader _group;
  2148. if (isNull _leader) exitWith {}; // Safety check
  2149.  
  2150. // Retreat: Hard cover 100-200m back
  2151. private _retreatPos = [_leader] call fnc_botFindHardCover;
  2152.  
  2153. // Fallback if hard cover not found or invalid
  2154. if (count _retreatPos < 2) then {
  2155. private _retreatDir = (direction _leader) + 180;
  2156. // Calculate offset position
  2157. _retreatPos = (getPosATL _leader) getPos [150 + random 50, _retreatDir];
  2158. // Ensure we have a Z coordinate (2D -> 3D) for consistency
  2159. if (count _retreatPos == 2) then { _retreatPos pushBack 0; };
  2160. };
  2161.  
  2162. // Final sanity check
  2163. if (count _retreatPos < 2) then { _retreatPos = getPosATL _leader; };
  2164.  
  2165. // Execute retreat only if we have a valid position
  2166. if (count _retreatPos >= 2) then {
  2167. while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
  2168.  
  2169. private _wp = _group addWaypoint [_retreatPos, 40];
  2170. _wp setWaypointType "MOVE";
  2171. _wp setWaypointBehaviour "AWARE";
  2172. _wp setWaypointSpeed "FULL";
  2173. _group setCombatMode "GREEN"; // Hold fire while running
  2174.  
  2175. // Self-clear after 60s or safe
  2176. [_group] spawn {
  2177. sleep 60;
  2178. params ["_grp"];
  2179. if (!isNull _grp && {alive leader _grp} && !(_grp getVariable ["moraleLow", false])) then {
  2180. _grp setVariable ["retreatActive", false];
  2181. _grp setCombatMode "YELLOW";
  2182. };
  2183. };
  2184.  
  2185. diag_log format ["[BOTAI] %1 morale broken (%2% left) - RETREAT!", groupId _group, round(_moralePct*100)];
  2186. };
  2187. };
  2188. };
  2189.  
  2190. // NEW: Manages group-level CQB behavior when a close threat is known.
  2191. fnc_manageCQB = {
  2192. params ["_group"];
  2193.  
  2194. // Cooldown check for performance, runs every 3-5 seconds per group.
  2195. if (time < (_group getVariable ["lastCQBCheck", 0]) + (3 + random 2)) exitWith {};
  2196. _group setVariable ["lastCQBCheck", time];
  2197.  
  2198. // Do not run if the group is already executing a temporary CQB task.
  2199. if (_group getVariable ["isDoingCQB", false]) exitWith {};
  2200.  
  2201. private _leader = leader _group;
  2202. if (isNull _leader || !alive _leader) exitWith {};
  2203.  
  2204. // Find the closest enemy that the group already knows about within 100m.
  2205. private _closestEnemy = objNull;
  2206. private _minDist = 101;
  2207.  
  2208. private _nearbyEntities = _leader nearEntities [["CAManBase", "LandVehicle"], 100];
  2209. {
  2210. // Check that the unit is an enemy AND that the group is aware of it.
  2211. if (side _x != side _leader && (_group knowsAbout _x) > 1.5) then {
  2212. private _dist = _leader distance2D _x;
  2213. if (_dist < _minDist) then {
  2214. _minDist = _dist;
  2215. _closestEnemy = _x;
  2216. };
  2217. };
  2218. } forEach _nearbyEntities;
  2219.  
  2220. // If no valid close-quarters threat is found, exit.
  2221. if (isNull _closestEnemy) exitWith {};
  2222.  
  2223. // --- CQB THREAT CONFIRMED ---
  2224.  
  2225. // Immediately set aggressive behavior.
  2226. _group setBehaviour "COMBAT";
  2227. _group setCombatMode "RED";
  2228.  
  2229. // Calculate flanking positions 60m to the left and right of the enemy's position, relative to the group leader.
  2230. private _enemyPos = getPos _closestEnemy;
  2231. private _dirToEnemy = _leader getDir _enemyPos;
  2232. private _flankLeftPos = _enemyPos getPos [60, _dirToEnemy - 90];
  2233. private _flankRightPos = _enemyPos getPos [60, _dirToEnemy + 90];
  2234.  
  2235. // ==================== CHANGE START: BUG FIX ====================
  2236. // The engine expects 5 arguments for selectBestPlaces, not 7.
  2237. // To achieve the same goal, we search for cover around each flanking point separately and combine the results.
  2238. private _leftCover = selectBestPlaces [_flankLeftPos, 40, "trees+houses", 10, 1];
  2239. private _rightCover = selectBestPlaces [_flankRightPos, 40, "trees+houses", 10, 1];
  2240. private _coverPositions = _leftCover + _rightCover;
  2241. // ===================== CHANGE END =====================
  2242.  
  2243. private _targetPos = _enemyPos; // Default to the enemy's position if no good cover is found.
  2244. if (count _coverPositions > 0) then {
  2245. // Select the best found position.
  2246. _targetPos = (_coverPositions select 0) select 0;
  2247. };
  2248.  
  2249. // Immediately override any existing waypoints with the new CQB task.
  2250. while {count (waypoints _group) > 0} do {
  2251. deleteWaypoint ((waypoints _group) select 0);
  2252. };
  2253.  
  2254. // Add a single "Search and Destroy" waypoint to the calculated flanking position.
  2255. private _wp = _group addWaypoint [_targetPos, 5];
  2256. _wp setWaypointType "SAD";
  2257. _wp setWaypointBehaviour "COMBAT";
  2258. _wp setWaypointSpeed "FULL"; // Move with urgency.
  2259. _wp setWaypointCompletionRadius 30; // A small radius for a precise maneuver.
  2260.  
  2261. // Set a flag that this group is performing a CQB action. This prevents other logic from overriding it.
  2262. _group setVariable ["isDoingCQB", true];
  2263.  
  2264. // Spawn a script to automatically clear the flag after 45 seconds, allowing the AI commander to issue new orders.
  2265. [_group] spawn {
  2266. params ["_grp"];
  2267. sleep 45;
  2268. if (!isNull _grp) then {
  2269. _grp setVariable ["isDoingCQB", false];
  2270. };
  2271. };
  2272. };
  2273.  
  2274. // Check if a group has anti-tank capability
  2275. fnc_groupHasAT = {
  2276. params ["_group"];
  2277.  
  2278. private _hasAT = false;
  2279. private _units = units _group select {alive _x};
  2280.  
  2281. {
  2282. private _secondaryWeapon = secondaryWeapon _x;
  2283. if (_secondaryWeapon != "") then {
  2284. _hasAT = true;
  2285. break;
  2286. };
  2287. } forEach _units;
  2288.  
  2289. _hasAT
  2290. };
  2291.  
  2292. // Make AI infantry evade from enemy vehicles if they lack AT weapons
  2293. fnc_evadeEnemyVehicles = {
  2294. params ["_unit"];
  2295.  
  2296. if (vehicle _unit != _unit) exitWith {}; // Skip if in vehicle
  2297.  
  2298. // Only process for group leaders to manage group behavior
  2299. private _group = group _unit;
  2300. if (_unit != leader _group) exitWith {};
  2301.  
  2302. // Use a single cooldown for this entire logic block to prevent spamming actions.
  2303. private _lastVehicleResponseCheck = _group getVariable ["lastVehicleResponseCheck", 0];
  2304. if (time < _lastVehicleResponseCheck + (5 + random 3)) exitWith {};
  2305. _group setVariable ["lastVehicleResponseCheck", time];
  2306.  
  2307. // Find the closest enemy vehicle the group knows about within the 100m radius.
  2308. private _closestThreat = objNull;
  2309. private _minDist = 101;
  2310. private _nearbyVehicles = (position _unit) nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F", "Car"], 100];
  2311. private _enemySide = if (side _unit == west) then {east} else {west};
  2312.  
  2313. {
  2314. if (alive _x && side _x == _enemySide && (_group knowsAbout _x) > 1.5) then {
  2315. private _dist = _unit distance2D _x;
  2316. if (_dist < _minDist) then {
  2317. _minDist = _dist;
  2318. _closestThreat = _x;
  2319. };
  2320. };
  2321. } forEach _nearbyVehicles;
  2322.  
  2323. // If no close vehicle threat is found, exit the function.
  2324. if (isNull _closestThreat) exitWith {};
  2325.  
  2326. // If the group already has living AT soldiers, they can handle the threat. Exit.
  2327. if ([_group] call fnc_groupHasAT) exitWith {};
  2328.  
  2329. // --- GROUP HAS NO AT - BEGIN SCAVENGE/EVADE LOGIC ---
  2330.  
  2331. // 1. Attempt to scavenge an AT weapon. This function has its own internal checks.
  2332. [_group] call fnc_bot_scavengeATWeapon;
  2333.  
  2334. // 2. If scavenging was not initiated (no suitable bodies found), then evade.
  2335. // The scavenge function sets "isScavengingAT" to true if it starts a task.
  2336. if !(_group getVariable ["isScavengingAT", false]) then {
  2337. // Do not issue a new evade order if the group is already evading.
  2338. if (_group getVariable ["evadingVehicle", false]) exitWith {};
  2339.  
  2340. // Mark group as evading to prevent this logic from running again immediately.
  2341. _group setVariable ["evadingVehicle", true];
  2342.  
  2343. // Find a safe position away from the vehicle threat.
  2344. private _unitPos = getPosATL _unit;
  2345. private _dirAwayFromVehicle = _closestThreat getDir _unit;
  2346. private _coverPos = [];
  2347.  
  2348. // First, try to find "hard" cover like buildings or walls.
  2349. _coverPos = [_unit] call fnc_botFindHardCover;
  2350.  
  2351. // If no hard cover, find general cover (trees, rocks, terrain).
  2352. if (count _coverPos == 0) then {
  2353. _coverPos = [_unit, 150] call fnc_findNearestCover;
  2354. };
  2355.  
  2356. // As a last resort, just pick a point far away from the vehicle.
  2357. if (count _coverPos == 0) then {
  2358. _coverPos = _unitPos getPos [150, _dirAwayFromVehicle];
  2359. };
  2360.  
  2361. if (count _coverPos > 0 && !surfaceIsWater _coverPos) then {
  2362. // Clear any existing waypoints to prioritize this evasion move.
  2363. while {count (waypoints _group) > 0} do {
  2364. deleteWaypoint ((waypoints _group) select 0);
  2365. };
  2366.  
  2367. // Order the group to move to the safe position with urgency.
  2368. private _wp = _group addWaypoint [_coverPos, 20];
  2369. _wp setWaypointType "MOVE";
  2370. _wp setWaypointCompletionRadius 30;
  2371. _wp setWaypointSpeed "FULL"; // Move fast
  2372. _wp setWaypointBehaviour "AWARE";
  2373.  
  2374. // Order all units to go prone to hide upon arrival.
  2375. {_x setUnitPos "DOWN";} forEach (units _group);
  2376.  
  2377. // Spawn a monitor to clear the "evadingVehicle" flag once the group is safe.
  2378. [_group, _coverPos, _closestThreat] spawn {
  2379. params ["_evadingGroup", "_targetCover", "_threatVehicle"];
  2380.  
  2381. private _timeout = time + 45; // Action times out after 45 seconds.
  2382. waitUntil {
  2383. sleep 1;
  2384. // Exit condition: group is gone, reached cover, threat is gone, or timeout.
  2385. isNull _evadingGroup || (leader _evadingGroup distance2D _targetCover < 40) || (!alive _threatVehicle) || (leader _evadingGroup distance2D _threatVehicle > 200) || time > _timeout
  2386. };
  2387.  
  2388. // Once finished, clear the flag and allow AI to return to normal behavior.
  2389. if (!isNull _evadingGroup) then {
  2390. _evadingGroup setVariable ["evadingVehicle", false];
  2391. {_x setUnitPos "AUTO";} forEach (units _evadingGroup);
  2392. };
  2393. };
  2394. };
  2395. };
  2396. };
  2397.  
  2398. // Find nearest cover position for AI unit - OPTIMIZED
  2399. fnc_findNearestCover = {
  2400. params ["_unit", ["_maxDistance", 50]];
  2401.  
  2402. private _unitPos = getPosATL _unit;
  2403.  
  2404. // OPTIMIZATION: Cache cover position for much longer
  2405. private _cachedCover = _unit getVariable ["cachedCoverPos", []];
  2406. private _cachedCoverTime = _unit getVariable ["cachedCoverTime", 0];
  2407.  
  2408. // Use cached position if less than 45 seconds old
  2409. if (time - _cachedCoverTime < 45 && count _cachedCover > 0) exitWith {
  2410. _cachedCover
  2411. };
  2412.  
  2413. private _bestCoverPos = [];
  2414. private _bestCoverScore = -1;
  2415.  
  2416. // OPTIMIZATION: Smaller search radius for terrain objects
  2417. private _searchRadius = 25; // Reduced from 30
  2418.  
  2419. private _terrainObjects = nearestTerrainObjects [_unitPos, ["TREE", "ROCK", "WALL"], _searchRadius, false, true];
  2420.  
  2421. // Only check first 5 objects
  2422. private _checkCount = (count _terrainObjects) min 5;
  2423.  
  2424. for "_i" from 0 to (_checkCount - 1) do {
  2425. private _object = _terrainObjects select _i;
  2426. private _objectPos = getPosATL _object;
  2427. private _distanceToObject = _unitPos distance2D _objectPos;
  2428.  
  2429. if (_distanceToObject > 3 && _distanceToObject < _searchRadius) then {
  2430. // Simplified scoring
  2431. private _coverScore = 10 / _distanceToObject;
  2432.  
  2433. if (_coverScore > _bestCoverScore) then {
  2434. private _dirToObject = [_unitPos, _objectPos] call BIS_fnc_dirTo;
  2435. private _coverPos = _objectPos getPos [2, _dirToObject + 180];
  2436.  
  2437. if (!surfaceIsWater _coverPos) then {
  2438. _bestCoverScore = _coverScore;
  2439. _bestCoverPos = _coverPos;
  2440. };
  2441. };
  2442. };
  2443. };
  2444.  
  2445. // OPTIMIZATION: Simplified building check - only check very close buildings
  2446. if (_bestCoverScore < 3) then {
  2447. private _nearBuildings = _unitPos nearObjects ["House", 20]; // Reduced from 25
  2448.  
  2449. // Only check first 3 buildings
  2450. private _buildingCount = (count _nearBuildings) min 3;
  2451.  
  2452. for "_i" from 0 to (_buildingCount - 1) do {
  2453. private _building = _nearBuildings select _i;
  2454. private _buildingPos = getPosATL _building;
  2455. private _distanceToBuilding = _unitPos distance2D _buildingPos;
  2456.  
  2457. if (_distanceToBuilding > 5) then {
  2458. private _coverScore = 15 / _distanceToBuilding;
  2459.  
  2460. if (_coverScore > _bestCoverScore) then {
  2461. _bestCoverScore = _coverScore;
  2462. _bestCoverPos = _buildingPos;
  2463. };
  2464. };
  2465. };
  2466. };
  2467.  
  2468. // OPTIMIZATION: Simplified terrain check - only 4 directions
  2469. if (count _bestCoverPos == 0) then {
  2470. for "_angle" from 0 to 270 step 90 do {
  2471. private _testPos = _unitPos getPos [15, _angle];
  2472. private _heightDiff = (getTerrainHeightASL _unitPos) - (getTerrainHeightASL _testPos);
  2473.  
  2474. if (_heightDiff > 1 && !surfaceIsWater _testPos) then {
  2475. if (_heightDiff > _bestCoverScore) then {
  2476. _bestCoverScore = _heightDiff;
  2477. _bestCoverPos = _testPos;
  2478. };
  2479. };
  2480. };
  2481. };
  2482.  
  2483. // Cache result for longer
  2484. _unit setVariable ["cachedCoverPos", _bestCoverPos];
  2485. _unit setVariable ["cachedCoverTime", time];
  2486.  
  2487. _bestCoverPos
  2488. };
  2489.  
  2490. // Move AI unit to tactical position when under fire - OPTIMIZED
  2491. fnc_moveToTacticalPosition = {
  2492. params ["_unit"];
  2493.  
  2494. if (!alive _unit || vehicle _unit != _unit) exitWith {};
  2495.  
  2496. private _lastRepositionTime = _unit getVariable ["lastRepositionTime", 0];
  2497.  
  2498. // ==================== CHANGE START ====================
  2499. // OPTIMIZATION: Increased minimum interval and tied to performance scaling
  2500. if (time - _lastRepositionTime < (15 * PERF_COOLDOWN_MULTIPLIER)) exitWith {};
  2501. // ===================== CHANGE END =====================
  2502.  
  2503. private _suppression = getSuppression _unit;
  2504. private _damage = damage _unit;
  2505. private _isUnderFire = (time - (_unit getVariable ["lastShotAtUnit_self", 0])) < 5;
  2506.  
  2507. // Check if unit needs to reposition
  2508. private _needsReposition = _suppression > 0.3 || _damage > 0.2 || _isUnderFire;
  2509.  
  2510. if (_needsReposition) then {
  2511. private _cachedCover = _unit getVariable ["cachedCoverPos", []];
  2512. private _cachedCoverTime = _unit getVariable ["cachedCoverTime", 0];
  2513.  
  2514. private _coverPos = [];
  2515.  
  2516. // Use cached position if less than 30 seconds old
  2517. if (time - _cachedCoverTime < 30 && count _cachedCover > 0) then {
  2518. _coverPos = _cachedCover;
  2519. } else {
  2520. _coverPos = [_unit, 40] call fnc_findNearestCover;
  2521. _unit setVariable ["cachedCoverPos", _coverPos];
  2522. _unit setVariable ["cachedCoverTime", time];
  2523. };
  2524.  
  2525. if (count _coverPos > 0) then {
  2526. _unit doMove _coverPos;
  2527. _unit setSpeedMode "FULL";
  2528.  
  2529. if (_suppression > 0.6 || _damage > 0.5) then {
  2530. _unit setUnitPos "DOWN";
  2531. } else {
  2532. _unit setUnitPos "MIDDLE";
  2533. };
  2534.  
  2535. _unit setVariable ["lastRepositionTime", time];
  2536. _unit setVariable ["movingToCover", true];
  2537. _unit setVariable ["coverPosition", _coverPos];
  2538.  
  2539. [_unit, _coverPos] spawn {
  2540. params ["_unit", "_coverPos"];
  2541.  
  2542. waitUntil {sleep 1; !alive _unit || _unit distance2D _coverPos < 5 || time > ((_unit getVariable ["lastRepositionTime", 0]) + 15)};
  2543.  
  2544. if (alive _unit) then {
  2545. _unit setVariable ["movingToCover", false];
  2546. sleep (5 + random 5);
  2547.  
  2548. if (getSuppression _unit < 0.2 && damage _unit < 0.3) then {
  2549. _unit setUnitPos "AUTO";
  2550. };
  2551. };
  2552. };
  2553. };
  2554. };
  2555. };
  2556.  
  2557. // Manage AI combat stance based on situation
  2558. fnc_manageCombatStance = {
  2559. params ["_unit"];
  2560.  
  2561. if (!alive _unit || vehicle _unit != _unit) exitWith {};
  2562.  
  2563. private _suppression = getSuppression _unit;
  2564. private _damage = damage _unit;
  2565. // ==================== CHANGE START ====================
  2566. private _nearEnemies = _unit nearEntities [["CAManBase", "LandVehicle"], (100 * PERF_SEARCH_RADIUS_MULTIPLIER)];
  2567. // ===================== CHANGE END =====================
  2568. private _enemyCount = {side _x != side _unit && alive _x} count _nearEnemies;
  2569. private _isMovingToCover = _unit getVariable ["movingToCover", false];
  2570.  
  2571. if (_isMovingToCover) exitWith {};
  2572.  
  2573. if (_suppression > 0.7 || _damage > 0.6) then {
  2574. _unit setUnitPos "DOWN";
  2575. } else {
  2576. if (_suppression > 0.3 || _enemyCount > 3) then {
  2577. _unit setUnitPos "MIDDLE";
  2578. } else {
  2579. if (_enemyCount > 0 && _enemyCount <= 3) then {
  2580. if (random 1 > 0.3) then {
  2581. _unit setUnitPos "MIDDLE";
  2582. } else {
  2583. _unit setUnitPos "AUTO";
  2584. };
  2585. } else {
  2586. _unit setUnitPos "AUTO";
  2587. };
  2588. };
  2589. };
  2590.  
  2591. if (_enemyCount > 0) then {
  2592. if (_suppression > 0.5) then {
  2593. _unit setBehaviour "SAFE";
  2594. _unit setSpeedMode "FULL";
  2595. } else {
  2596. _unit setBehaviour "COMBAT";
  2597. _unit setSpeedMode "NORMAL";
  2598. };
  2599. } else {
  2600. if (behaviour _unit != "STEALTH") then {
  2601. _unit setBehaviour "AWARE";
  2602. };
  2603. };
  2604. };
  2605.  
  2606. // Assign flank watch sectors to group members
  2607. fnc_assignFlankWatchSectors = {
  2608. params ["_group"];
  2609.  
  2610. if (isNull _group || count (units _group) == 0) exitWith {};
  2611.  
  2612. private _leader = leader _group;
  2613. if (isNull _leader || !alive _leader) exitWith {};
  2614.  
  2615. private _groupUnits = units _group select {alive _x && vehicle _x == _x};
  2616. private _unitCount = count _groupUnits;
  2617.  
  2618. if (_unitCount == 0) exitWith {};
  2619.  
  2620. private _leaderDir = direction _leader;
  2621. private _currentWP = currentWaypoint _group;
  2622.  
  2623. if (_currentWP > 0) then {
  2624. private _wpPos = waypointPosition [_group, _currentWP];
  2625. if (count _wpPos > 0 && !(_wpPos isEqualTo [0,0,0])) then {
  2626. _leaderDir = _leader getDir _wpPos;
  2627. };
  2628. };
  2629.  
  2630. if (_unitCount == 1) then {
  2631. _groupUnits select 0 setVariable ["watchSector", _leaderDir];
  2632. _groupUnits select 0 setVariable ["sectorWidth", 180];
  2633. } else {
  2634. if (_unitCount == 2) then {
  2635. _groupUnits select 0 setVariable ["watchSector", _leaderDir];
  2636. _groupUnits select 0 setVariable ["sectorWidth", 180];
  2637. _groupUnits select 1 setVariable ["watchSector", _leaderDir + 180];
  2638. _groupUnits select 1 setVariable ["sectorWidth", 180];
  2639. } else {
  2640. if (_unitCount == 3) then {
  2641. _groupUnits select 0 setVariable ["watchSector", _leaderDir];
  2642. _groupUnits select 0 setVariable ["sectorWidth", 120];
  2643. _groupUnits select 1 setVariable ["watchSector", _leaderDir - 90];
  2644. _groupUnits select 1 setVariable ["sectorWidth", 120];
  2645. _groupUnits select 2 setVariable ["watchSector", _leaderDir + 90];
  2646. _groupUnits select 2 setVariable ["sectorWidth", 120];
  2647. } else {
  2648. private _sectorSize = 360 / _unitCount;
  2649.  
  2650. for "_i" from 0 to (_unitCount - 1) do {
  2651. private _unit = _groupUnits select _i;
  2652. private _sectorDir = _leaderDir + (_i * _sectorSize);
  2653.  
  2654. _unit setVariable ["watchSector", _sectorDir];
  2655. _unit setVariable ["sectorWidth", _sectorSize + 20];
  2656. };
  2657. };
  2658. };
  2659. };
  2660.  
  2661. _group setVariable ["sectorsAssigned", true];
  2662. _group setVariable ["sectorAssignmentTime", time];
  2663. };
  2664.  
  2665. // Make AI unit scan their assigned sector
  2666. fnc_scanAssignedSector = {
  2667. params ["_unit"];
  2668.  
  2669. if (!alive _unit || vehicle _unit != _unit) exitWith {};
  2670. if (behaviour _unit == "COMBAT") exitWith {};
  2671.  
  2672. private _assignedSector = _unit getVariable ["watchSector", -1];
  2673. private _sectorWidth = _unit getVariable ["sectorWidth", 90];
  2674.  
  2675. if (_assignedSector < 0) exitWith {};
  2676.  
  2677. private _lastScanTime = _unit getVariable ["lastSectorScan", 0];
  2678.  
  2679. if (time - _lastScanTime > (3 + random 3)) then {
  2680. private _scanDir = _assignedSector + (random _sectorWidth - (_sectorWidth / 2));
  2681. private _unitPos = getPosATL _unit;
  2682. private _lookAtPos = _unitPos getPos [50, _scanDir];
  2683.  
  2684. _unit doWatch _lookAtPos;
  2685. _unit setVariable ["lastSectorScan", time];
  2686.  
  2687. [_unit] spawn {
  2688. params ["_unit"];
  2689. sleep (2 + random 2);
  2690. if (alive _unit && behaviour _unit != "COMBAT") then {
  2691. _unit doWatch objNull;
  2692. };
  2693. };
  2694. };
  2695. };
  2696.  
  2697. // Maintain tactical spacing between group members - OPTIMIZED
  2698. fnc_maintainTacticalSpacing = {
  2699. params ["_group"];
  2700.  
  2701. if (isNull _group || count (units _group) == 0) exitWith {};
  2702.  
  2703. private _leader = leader _group;
  2704. if (isNull _leader || !alive _leader) exitWith {};
  2705.  
  2706. private _groupUnits = units _group select {alive _x && vehicle _x == _x && _x != _leader};
  2707.  
  2708. if (count _groupUnits == 0) exitWith {};
  2709.  
  2710. private _leaderPos = getPosATL _leader;
  2711. private _optimalSpacing = 5;
  2712. private _maxSpacing = 15;
  2713.  
  2714. {
  2715. private _unit = _x;
  2716. private _unitPos = getPosATL _unit;
  2717. private _distToLeader = _unitPos distance2D _leaderPos;
  2718.  
  2719. // Early exit if already well-positioned
  2720. if (_distToLeader > _optimalSpacing && _distToLeader < _maxSpacing) then {
  2721. continue;
  2722. };
  2723.  
  2724. // ==================== CHANGE START ====================
  2725. // OPTIMIZATION: Replaced nested loop with a single, fast nearEntities check.
  2726. private _tooClose = count (_unit nearEntities ["CAManBase", _optimalSpacing]) > 1; // >1 to not count self
  2727.  
  2728. if (_tooClose && !(_unit getVariable ["movingToCover", false])) then {
  2729. // ===================== CHANGE END =====================
  2730. private _lastSpacingAdjust = _unit getVariable ["lastSpacingAdjust", 0];
  2731.  
  2732. if (time - _lastSpacingAdjust > 8) then {
  2733. private _repositionDir = _leaderPos getDir _unitPos;
  2734. private _repositionPos = _leaderPos getPos [_optimalSpacing + random 3, _repositionDir + (random 40 - 20)];
  2735.  
  2736. _repositionPos = [_repositionPos, 0, 5, 2, 0, 0.3, 0] call BIS_fnc_findSafePos;
  2737.  
  2738. if (count _repositionPos > 0 && !surfaceIsWater _repositionPos) then {
  2739. _unit doMove _repositionPos;
  2740. _unit setVariable ["lastSpacingAdjust", time];
  2741. };
  2742. };
  2743. };
  2744.  
  2745. if (_distToLeader > _maxSpacing && behaviour _unit != "COMBAT") then {
  2746. private _lastSpacingAdjust = _unit getVariable ["lastSpacingAdjust", 0];
  2747.  
  2748. if (time - _lastSpacingAdjust > 10) then {
  2749. _unit doFollow _leader;
  2750. _unit setVariable ["lastSpacingAdjust", time];
  2751. };
  2752. };
  2753. } forEach _groupUnits;
  2754. };
  2755.  
  2756.  
  2757. // NEW: Finds the nearest "hard" cover, prioritizing buildings and walls.
  2758. fnc_botFindHardCover = {
  2759. params ["_unit"];
  2760. private _unitPos = getPosATL _unit;
  2761. private _bestCoverPos = [];
  2762. private _maxScanRadius = 75;
  2763.  
  2764. // Search for nearest buildings first
  2765. private _nearBuildings = nearestObjects [_unitPos, ["House"], _maxScanRadius];
  2766. if (count _nearBuildings > 0) then {
  2767. private _closestBuilding = objNull;
  2768. private _minDist = 9999;
  2769. {
  2770. private _dist = _unit distance _x;
  2771. // FIXED: nearObjects [_x, 1] -> distance2D _x > 3 (avoids current building)
  2772. if (_dist < _minDist && (_unit distance2D _x > 3)) then {
  2773. _minDist = _dist;
  2774. _closestBuilding = _x;
  2775. };
  2776. } forEach _nearBuildings;
  2777.  
  2778. if (!isNull _closestBuilding) then {
  2779. // FIXED: buildingPos -1 returns an array of ALL positions. We must select ONE.
  2780. private _buildingPositions = _closestBuilding buildingPos -1;
  2781. if (count _buildingPositions > 0) then {
  2782. _bestCoverPos = selectRandom _buildingPositions;
  2783. } else {
  2784. // If building has no indexed positions, use its center
  2785. _bestCoverPos = getPosATL _closestBuilding;
  2786. };
  2787. };
  2788. };
  2789.  
  2790. // If no building, look for walls
  2791. if (count _bestCoverPos == 0) then {
  2792. private _nearWalls = nearestTerrainObjects [_unitPos, ["WALL"], _maxScanRadius / 2];
  2793. if (count _nearWalls > 0) then {
  2794. private _closestWall = _nearWalls select 0;
  2795. private _wallPos = getPosATL _closestWall;
  2796. private _dirToWall = [_unitPos, _wallPos] call BIS_fnc_dirTo;
  2797. _bestCoverPos = _wallPos getPos [2, _dirToWall + 180];
  2798. };
  2799. };
  2800.  
  2801. _bestCoverPos
  2802. };
  2803.  
  2804. // NEW: Make infantry units seek cover and heal when damaged
  2805. fnc_botSeekCoverAndHeal = {
  2806. params ["_unit"];
  2807.  
  2808. // Only for infantry units
  2809. if (vehicle _unit != _unit) exitWith {};
  2810.  
  2811. // Check damage threshold (20%)
  2812. private _damage = damage _unit;
  2813. if (_damage <= 0.2) exitWith {};
  2814.  
  2815. // Check if unit has a FirstAidKit
  2816. if (!("FirstAidKit" in items _unit)) exitWith {};
  2817.  
  2818. // Check cooldown to prevent constant healing attempts
  2819. private _lastHealAttempt = _unit getVariable ["lastHealAttempt", 0];
  2820. if (time - _lastHealAttempt < 30) exitWith {}; // 30 second cooldown
  2821.  
  2822. // Check if already healing
  2823. if (_unit getVariable ["isHealing", false]) exitWith {};
  2824.  
  2825. _unit setVariable ["lastHealAttempt", time];
  2826. _unit setVariable ["isHealing", true];
  2827.  
  2828. private _unitPos = getPosATL _unit;
  2829. private _coverFound = false;
  2830. private _coverPos = [];
  2831.  
  2832. // Search for hard cover within 20 meters
  2833. private _nearBuildings = nearestObjects [_unitPos, ["House", "Building"], 20];
  2834. private _nearWalls = nearestTerrainObjects [_unitPos, ["WALL"], 20];
  2835.  
  2836. // Try to find building cover first
  2837. if (count _nearBuildings > 0) then {
  2838. // Find closest building that's not the one we're already in
  2839. {
  2840. private _building = _x;
  2841. private _distance = _unit distance _building;
  2842. if (_distance > 2 && _distance <= 20) then {
  2843. // Try to get a building position
  2844. private _buildingPos = _building buildingPos 0;
  2845. if (count _buildingPos > 0) then {
  2846. _coverPos = _buildingPos;
  2847. _coverFound = true;
  2848. } else {
  2849. // If no interior position, use position behind building
  2850. _coverPos = getPosATL _building;
  2851. _coverFound = true;
  2852. };
  2853. };
  2854. if (_coverFound) exitWith {};
  2855. } forEach _nearBuildings;
  2856. };
  2857.  
  2858. // If no building, try walls
  2859. if (!_coverFound && count _nearWalls > 0) then {
  2860. private _closestWall = _nearWalls select 0;
  2861. if (_unit distance _closestWall <= 20) then {
  2862. private _wallPos = getPosATL _closestWall;
  2863. // Position behind wall relative to unit's current position
  2864. private _dirToWall = _unitPos getDir _wallPos;
  2865. _coverPos = _wallPos getPos [2, _dirToWall + 180];
  2866. _coverFound = true;
  2867. };
  2868. };
  2869.  
  2870. // Execute healing behavior
  2871. if (_coverFound) then {
  2872. // Move to cover and heal
  2873. [_unit, _coverPos] spawn {
  2874. params ["_unit", "_coverPos"];
  2875.  
  2876. _unit doMove _coverPos;
  2877. _unit setSpeedMode "FULL";
  2878.  
  2879. // Wait until unit reaches cover or times out
  2880. private _timeout = time + 15;
  2881. waitUntil {
  2882. sleep 0.5;
  2883. !alive _unit ||
  2884. (_unit distance2D _coverPos) < 3 ||
  2885. time > _timeout
  2886. };
  2887.  
  2888. if (alive _unit && "FirstAidKit" in items _unit) then {
  2889. // Go prone and heal
  2890. _unit setUnitPos "DOWN";
  2891. sleep 1;
  2892. _unit action ["HealSoldierSelf", _unit];
  2893. _unit removeItem "FirstAidKit";
  2894.  
  2895. // Stay in cover for a few seconds
  2896. sleep 5;
  2897. _unit setUnitPos "AUTO";
  2898. };
  2899.  
  2900. _unit setVariable ["isHealing", false];
  2901. };
  2902. } else {
  2903. // No cover found - go prone and heal at current position
  2904. [_unit] spawn {
  2905. params ["_unit"];
  2906.  
  2907. if (alive _unit && "FirstAidKit" in items _unit) then {
  2908. _unit setUnitPos "DOWN";
  2909. sleep 1;
  2910. _unit action ["HealSoldierSelf", _unit];
  2911. _unit removeItem "FirstAidKit";
  2912.  
  2913. // Stay prone for a few seconds
  2914. sleep 5;
  2915. _unit setUnitPos "AUTO";
  2916. };
  2917.  
  2918. _unit setVariable ["isHealing", false];
  2919. };
  2920. };
  2921. };
  2922.  
  2923.  
  2924. // NEW: Prevents AI units from clipping/sticking inside each other by applying a gentle push.
  2925. fnc_resolveClipping = {
  2926. params ["_unit"];
  2927. if (vehicle _unit != _unit) exitWith {}; // Only for infantry
  2928.  
  2929. // Check for other infantry units extremely close (0.6m)
  2930. private _nearby = _unit nearEntities ["CAManBase", 0.6];
  2931.  
  2932. if (count _nearby > 1) then {
  2933. // Find a neighbor that isn't self
  2934. private _neighborIndex = _nearby findIf {_x != _unit};
  2935.  
  2936. if (_neighborIndex != -1) then {
  2937. private _neighbor = _nearby select _neighborIndex;
  2938.  
  2939. // Calculate direction away from the neighbor
  2940. private _dir = _neighbor getDir _unit;
  2941.  
  2942. // Apply a small velocity push away from the neighbor
  2943. // This uses the physics engine to separate them smoothly rather than teleporting
  2944. private _pushSpeed = 1.5;
  2945. private _currentVel = velocity _unit;
  2946.  
  2947. _unit setVelocity [
  2948. (_currentVel select 0) + (sin _dir * _pushSpeed),
  2949. (_currentVel select 1) + (cos _dir * _pushSpeed),
  2950. _currentVel select 2
  2951. ];
  2952. };
  2953. };
  2954. };
  2955.  
  2956. // NEW: Makes AI squads attempt to scavenge an AT launcher from a fallen teammate if a vehicle threat is present.
  2957. fnc_bot_scavengeATWeapon = {
  2958. params ["_group"];
  2959.  
  2960. // Cooldown: Only check every 10-15 seconds per group
  2961. if (time < (_group getVariable ["lastATScavengeCheck", 0]) + (10 + random 5)) exitWith {};
  2962. _group setVariable ["lastATScavengeCheck", time];
  2963.  
  2964. // Exit if another scavenger is already tasked for this group
  2965. if (_group getVariable ["isScavengingAT", false]) exitWith {};
  2966.  
  2967. private _leader = leader _group;
  2968. if (isNull _leader || !alive _leader) exitWith {};
  2969.  
  2970. // 1. Find the primary vehicle threat
  2971. private _enemySide = if (side _group == west) then {east} else {west};
  2972. private _threats = _leader nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F", "Car"], 400];
  2973. private _targetVehicle = objNull;
  2974.  
  2975. {
  2976. if (side _x == _enemySide && alive _x && count (crew _x) > 0) exitWith {
  2977. _targetVehicle = _x;
  2978. };
  2979. } forEach _threats;
  2980.  
  2981. // If no vehicle threat, exit
  2982. if (isNull _targetVehicle) exitWith {};
  2983.  
  2984. // 2. Check for living AT soldiers in the group
  2985. private _livingUnits = units _group select {alive _x && vehicle _x == _x};
  2986. private _hasLivingAT = false;
  2987. {
  2988. if (secondaryWeapon _x != "") exitWith {
  2989. _hasLivingAT = true;
  2990. };
  2991. } forEach _livingUnits;
  2992.  
  2993. if (_hasLivingAT) exitWith {}; // Group already has AT capability
  2994.  
  2995. // 3. Search for nearby dead bodies with AT weapons
  2996. // FIXED: Used 'allDeadMen' instead of 'nearEntities' because nearEntities only finds alive units.
  2997. private _leaderPos = getPos _leader;
  2998. private _nearbyBodies = allDeadMen select {
  2999. (_x distance _leaderPos < 100) &&
  3000. (secondaryWeapon _x != "")
  3001. };
  3002.  
  3003. if (count _nearbyBodies == 0) exitWith {};
  3004.  
  3005. // Find the closest body with a launcher and ammo
  3006. private _deadBody = objNull;
  3007. private _launcherClass = "";
  3008. private _closestDist = 9999;
  3009.  
  3010. {
  3011. private _launcher = secondaryWeapon _x;
  3012. if (_launcher != "") then {
  3013. // Check if body has launcher ammo
  3014. private _hasAmmo = false;
  3015. private _launcherMags = getArray (configFile >> "CfgWeapons" >> _launcher >> "magazines");
  3016.  
  3017. {
  3018. if (_x in _launcherMags) exitWith {
  3019. _hasAmmo = true;
  3020. };
  3021. } forEach (magazines _x);
  3022.  
  3023. if (_hasAmmo) then {
  3024. private _dist = _leader distance2D _x;
  3025. if (_dist < _closestDist) then {
  3026. _closestDist = _dist;
  3027. _deadBody = _x;
  3028. _launcherClass = _launcher;
  3029. };
  3030. };
  3031. };
  3032. } forEach _nearbyBodies;
  3033.  
  3034. // If no suitable body found, exit
  3035. if (isNull _deadBody || _launcherClass == "") exitWith {};
  3036.  
  3037. // 4. Find the closest living non-AT soldier to be the scavenger
  3038. private _scavenger = objNull;
  3039. private _minDist = 9999;
  3040.  
  3041. {
  3042. if (secondaryWeapon _x == "" && !(_x getVariable ["isScavenger", false])) then {
  3043. private _dist = _x distance2D _deadBody;
  3044. if (_dist < _minDist) then {
  3045. _minDist = _dist;
  3046. _scavenger = _x;
  3047. };
  3048. };
  3049. } forEach _livingUnits;
  3050.  
  3051. // If we found a valid scavenger, task them
  3052. if (!isNull _scavenger) then {
  3053. _group setVariable ["isScavengingAT", true, true];
  3054. _scavenger setVariable ["isScavenger", true, true];
  3055.  
  3056. // Spawn the scavenge process
  3057. [_scavenger, _deadBody, _launcherClass, _targetVehicle, _group] spawn {
  3058. params ["_scavenger", "_deadBody", "_launcherClass", "_targetVehicle", "_group"];
  3059.  
  3060. // Disable auto combat to prevent AI getting distracted
  3061. _scavenger disableAI "AUTOCOMBAT";
  3062. _scavenger setSpeedMode "FULL";
  3063. _scavenger doMove (getPos _deadBody);
  3064.  
  3065. // Wait until close to body or timeout
  3066. private _timeout = time + 30;
  3067. waitUntil {
  3068. sleep 0.5;
  3069. !alive _scavenger ||
  3070. isNull _deadBody ||
  3071. (_scavenger distance2D _deadBody < 3) ||
  3072. time > _timeout
  3073. };
  3074.  
  3075. if (alive _scavenger && !isNull _deadBody && (_scavenger distance2D _deadBody < 3)) then {
  3076. // Play grab animation
  3077. _scavenger playActionNow "PutDown";
  3078. sleep 1;
  3079.  
  3080. // Get compatible magazines from body
  3081. private _launcherMags = getArray (configFile >> "CfgWeapons" >> _launcherClass >> "magazines");
  3082. private _ammoToTake = magazines _deadBody select {_x in _launcherMags};
  3083.  
  3084. // Transfer weapon and ammo
  3085. _scavenger addWeapon _launcherClass;
  3086. {
  3087. _scavenger addMagazine _x;
  3088. } forEach _ammoToTake;
  3089.  
  3090. // Re-enable combat and engage
  3091. _scavenger enableAI "AUTOCOMBAT";
  3092. sleep 0.5;
  3093.  
  3094. if (alive _scavenger && !isNull _targetVehicle) then {
  3095. _scavenger selectWeapon (secondaryWeapon _scavenger);
  3096. _scavenger setBehaviour "COMBAT";
  3097. _scavenger setUnitPos "MIDDLE"; // Kneel to shoot
  3098. (group _scavenger) reveal [_targetVehicle, 4];
  3099. _scavenger doTarget _targetVehicle;
  3100. _scavenger doFire _targetVehicle;
  3101. };
  3102. };
  3103.  
  3104. // Clean up flags
  3105. if (alive _scavenger) then {
  3106. _scavenger enableAI "AUTOCOMBAT";
  3107. _scavenger setVariable ["isScavenger", false, true];
  3108. };
  3109. if (!isNull _group) then {
  3110. _group setVariable ["isScavengingAT", false, true];
  3111. };
  3112. };
  3113. };
  3114. };
  3115.  
  3116. // NEW: Core function to make units react to suppression in the open and to friendly casualties.
  3117. fnc_botReactToThreats = {
  3118. params ["_unit"];
  3119. if (vehicle _unit != _unit) exitWith {}; // Exit if in vehicle.
  3120.  
  3121. // --- Suppression Response ---
  3122. private _isUnderFire = (getSuppression _unit > 0.4) || (time - (_unit getVariable ["lastShotAtUnit_self", 0])) < 3;
  3123. private _isInCombat = behaviour _unit == "COMBAT";
  3124. private _isTakingCover = time - (_unit getVariable ["BOTAI_takingCoverTime", 0]) < 20; // 20 second cooldown
  3125.  
  3126. if (_isUnderFire && !_isInCombat && !_isTakingCover) then {
  3127. // Check if unit is in the open (more than 15m from any cover object).
  3128. private _coverObjects = nearestTerrainObjects [getPosATL _unit, ["TREE", "ROCK", "WALL", "BUSH"], 15];
  3129. private _nearBuildings = nearestObjects [getPosATL _unit, ["House"], 15];
  3130.  
  3131. if (count _coverObjects == 0 && count _nearBuildings == 0) then {
  3132. // Unit is in the open, find hard cover.
  3133. private _coverPos = [_unit] call fnc_botFindHardCover;
  3134. if (count _coverPos > 0) then {
  3135. _unit setVariable ["BOTAI_takingCoverTime", time]; // Set cooldown.
  3136. _unit setSpeedMode "FULL"; // Sprint!
  3137. _unit doMove _coverPos;
  3138.  
  3139. // Once in cover, hold position and wait for leader.
  3140. [_unit, _coverPos] spawn {
  3141. params ["_unit", "_pos"];
  3142. waitUntil {sleep 1; !alive _unit || _unit distance _pos < 5};
  3143. if (alive _unit) then {
  3144. // Clear waypoints to make the unit hold. The main HC loop will re-task them eventually.
  3145. while {count (waypoints (group _unit)) > 0} do {
  3146. deleteWaypoint ((waypoints (group _unit)) select 0);
  3147. };
  3148. _unit setSpeedMode "NORMAL";
  3149. };
  3150. };
  3151. };
  3152. };
  3153. };
  3154.  
  3155. // --- Casualty Awareness ---
  3156. private _lastBodyCheck = _unit getVariable ["BOTAI_lastBodyCheck", 0];
  3157. if (time - _lastBodyCheck > 10) then { // Check every 10 seconds.
  3158. _unit setVariable ["BOTAI_lastBodyCheck", time];
  3159.  
  3160. private _friendlyBodies = (getPosATL _unit) nearEntities ["CAManBase", 40] select {
  3161. side _x == side _unit && !alive _x
  3162. };
  3163. private _bodyCount = count _friendlyBodies;
  3164.  
  3165. if (_bodyCount > 0) then {
  3166. // If any friendly body is nearby, go to max alert.
  3167. if (behaviour _unit != "COMBAT") then { _unit setBehaviour "COMBAT"; };
  3168. if (combatMode _unit != "RED") then { _unit setCombatMode "RED"; };
  3169.  
  3170. // If more than one body is found, try to move away from the cluster.
  3171. if (_bodyCount > 1) then {
  3172. private _isAvoiding = time - (_unit getVariable ["BOTAI_avoidingBodiesTime", 0]) < 60;
  3173. if (!_isAvoiding) then {
  3174. _unit setVariable ["BOTAI_avoidingBodiesTime", time]; // Set cooldown.
  3175.  
  3176. // Find center of the bodies.
  3177. private _avgX = 0; private _avgY = 0;
  3178. { _avgX = _avgX + ((getPos _x) select 0); _avgY = _avgY + ((getPos _x) select 1); } forEach _friendlyBodies;
  3179. private _bodyCenter = [_avgX / _bodyCount, _avgY / _bodyCount, 0];
  3180.  
  3181. // Find a new position 50-75m away from the danger zone.
  3182. private _dirAway = [_bodyCenter, getPosATL _unit] call BIS_fnc_dirTo;
  3183. private _newPos = (getPosATL _unit) getPos [50 + (random 25), _dirAway];
  3184.  
  3185. // Order a move to the safer position.
  3186. _unit doMove _newPos;
  3187. };
  3188. };
  3189. };
  3190. };
  3191. };
  3192.  
  3193. // Main server-side AI loop - REWORKED WITH NEW PERFORMANCE SCALING
  3194. if (isServer) then {
  3195. [] spawn {
  3196. waitUntil { time > 20 };
  3197.  
  3198. private _unitIndex = 0;
  3199. private _allAiUnits = [];
  3200. private _lastUnitRefresh = 0;
  3201.  
  3202. while {true} do {
  3203. private _cycleStartTime = diag_tickTime;
  3204.  
  3205. // Refresh unit list less frequently
  3206. if (time > _lastUnitRefresh + 45 || count _allAiUnits == 0) then {
  3207. _allAiUnits = allUnits select {
  3208. !isNull _x && alive _x && !isPlayer _x && side _x in [west, east]
  3209. };
  3210. _lastUnitRefresh = time;
  3211. };
  3212.  
  3213. // This now uses the definitive batch size from performance.sqf
  3214. private _batchSize = (PERF_AI_BATCH_SIZE min (count _allAiUnits));
  3215. if (_batchSize == 0) then { _batchSize = 1; };
  3216.  
  3217. private _endIndex = (_unitIndex + _batchSize) min (count _allAiUnits);
  3218.  
  3219. // This frame-time budget prevents a single AI cycle from causing a major lag spike.
  3220. private _frameTimeBudget = 0.030; // 30ms max per cycle
  3221.  
  3222. for "_i" from _unitIndex to (_endIndex - 1) do {
  3223. if ((diag_tickTime - _cycleStartTime) > _frameTimeBudget) exitWith {
  3224. diag_log format ["[BOTAI] Frame budget exceeded at unit %1/%2", _i - _unitIndex, _batchSize];
  3225. };
  3226.  
  3227. private _unit = _allAiUnits select _i;
  3228. if (!isNull _unit && alive _unit) then {
  3229. private _vehicle = vehicle _unit;
  3230. private _isInfantry = (_vehicle == _unit);
  3231.  
  3232. private _enemySide = if (side _unit == west) then {east} else {west};
  3233. private _unitPos = getPos _unit;
  3234.  
  3235. if (_isInfantry) then {
  3236. // NEW: Resolve clipping for infantry units
  3237. [_unit] call fnc_resolveClipping;
  3238.  
  3239. private _group = group _unit;
  3240. // REWORKED BINOCULAR FIX: Prevents AI from being stuck with binoculars, but allows AT soldiers to aim their launchers.
  3241. if (currentWeapon _unit isKindOf "Binocular" && behaviour _unit == "COMBAT") then {
  3242. private _isPreparingToFireLauncher = false;
  3243. // Check if the AI has a launcher and a valid vehicle target nearby.
  3244. if (secondaryWeapon _unit != "") then {
  3245. // Use findIf for efficiency; it stops once a target is found.
  3246. private _foundTarget = (_unit nearEntities [["Tank", "Armored"], (500 * PERF_SEARCH_RADIUS_MULTIPLIER)]) findIf {
  3247. side _x == _enemySide && alive _x && _unit knowsAbout _x > 1.5
  3248. };
  3249.  
  3250. // If a valid target was found (findIf returns index >= 0), then the AI is likely aiming its launcher.
  3251. if (_foundTarget != -1) then {
  3252. _isPreparingToFireLauncher = true;
  3253. };
  3254. };
  3255.  
  3256. // If the AI is NOT preparing to fire a launcher, THEN it's safe to force a switch to the primary rifle.
  3257. if (!_isPreparingToFireLauncher) then {
  3258. _unit selectWeapon (primaryWeapon _unit);
  3259. };
  3260. };
  3261.  
  3262. // NEW: CQB Management. Call only for the leader to manage the whole group's tactical response.
  3263. if (_unit == leader _group) then {
  3264. [_group] call fnc_manageCQB;
  3265. };
  3266.  
  3267. // If the group is busy with a CQB maneuver, skip other movement/behavior logic for this unit.
  3268. if (_group getVariable ["isDoingCQB", false]) then { continue; };
  3269.  
  3270. // NEW: High-priority reactions to immediate threats.
  3271. [_unit] call fnc_botReactToThreats;
  3272. // Do not process other behaviors if the unit is busy taking cover.
  3273. if (time - (_unit getVariable ["BOTAI_takingCoverTime", 0]) < 20) then { continue; };
  3274.  
  3275. // NEW POLISH: Morale & Prediction (leaders only, perf-safe)
  3276. if (_unit == leader _group) then {
  3277. [_group] call fnc_manageDynamicMorale;
  3278. [_unit] call fnc_attemptPredictionAmbush;
  3279. };
  3280.  
  3281. // NEW: Vehicle evasion for infantry without AT (scales with performance)
  3282. [_unit] call fnc_evadeEnemyVehicles;
  3283.  
  3284. private _unitBehaviour = behaviour _unit;
  3285.  
  3286. // CRITICAL: Combat behaviors are always processed.
  3287. if (_unitBehaviour == "COMBAT") then {
  3288. [_unit] call fnc_moveToTacticalPosition;
  3289. [_unit] call fnc_manageCombatStance;
  3290. };
  3291.  
  3292. // NEW: Check if the group needs to scavenge an AT weapon.
  3293. if (_unit == leader _group) then {
  3294. [_group] call fnc_bot_scavengeATWeapon;
  3295. };
  3296.  
  3297. // HIGH PRIORITY: Check if unit needs to heal
  3298. if (damage _unit > 0.2 && !(_unit getVariable ["isHealing", false])) then {
  3299. [_unit] call fnc_botSeekCoverAndHeal;
  3300. };
  3301.  
  3302. if (!isNull _group && _unitBehaviour in ["AWARE", "SAFE"]) then
  3303. {
  3304. // Sector assignment is infrequent (every 60s)
  3305. if (time > ((_group getVariable ["sectorAssignmentTime", 0]) + (60 * PERF_COOLDOWN_MULTIPLIER))) then {
  3306. [_group] call fnc_assignFlankWatchSectors;
  3307. };
  3308.  
  3309. // Scanning is staggered between units
  3310. if (_i % 2 == 0) then {
  3311. [_unit] call fnc_scanAssignedSector;
  3312. };
  3313.  
  3314. // Spacing is infrequent and only for leaders
  3315. if (_unit == leader _group && (time - (_group getVariable ["lastSpacingCheck", 0])) > 20) then {
  3316. _group setVariable ["lastSpacingCheck", time];
  3317. [_group] call fnc_maintainTacticalSpacing;
  3318. };
  3319. };
  3320.  
  3321. private _lastVehicleAvoidCheck = _unit getVariable ["lastVehicleAvoidCheck", 0];
  3322. if (time > _lastVehicleAvoidCheck + (8 * PERF_COOLDOWN_MULTIPLIER)) then {
  3323. _unit setVariable ["lastVehicleAvoidCheck", time];
  3324.  
  3325. private _nearbyVehicles = _unit nearEntities ["LandVehicle", 10];
  3326. if (count _nearbyVehicles > 0) then {
  3327. private _friendlyVehicle = _nearbyVehicles findIf {side _x == side _unit};
  3328. if (_friendlyVehicle != -1) then {
  3329. private _closestVehicle = _nearbyVehicles select _friendlyVehicle;
  3330. private _dirFromVehicle = _closestVehicle getDir _unit;
  3331. _unit doMove (_unit getPos [5, _dirFromVehicle]);
  3332. };
  3333. };
  3334. };
  3335.  
  3336. // ==================== AT ENGAGEMENT START ====================
  3337. if (secondaryWeapon _unit != "") then {
  3338. private _lastATCheck = _unit getVariable ["lastATCheck", 0];
  3339. if (time > _lastATCheck + (2 * PERF_COOLDOWN_MULTIPLIER)) then {
  3340. _unit setVariable ["lastATCheck", time];
  3341.  
  3342. // Search for ALL vehicle types, not just Land/Air
  3343. private _nearVehicles = _unit nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F", "Car", "Helicopter"], (500 * PERF_SEARCH_RADIUS_MULTIPLIER)];
  3344.  
  3345. // Find closest enemy vehicle
  3346. private _closestVehicle = objNull;
  3347. private _closestDist = 9999;
  3348.  
  3349. {
  3350. if (side _x == _enemySide && alive _x && count (crew _x) > 0) then {
  3351. private _dist = _unit distance2D _x;
  3352. if (_dist < _closestDist) then {
  3353. _closestDist = _dist;
  3354. _closestVehicle = _x;
  3355. };
  3356. };
  3357. } forEach _nearVehicles;
  3358.  
  3359. if (!isNull _closestVehicle) then {
  3360. // Force immediate reveal
  3361. (group _unit) reveal [_closestVehicle, 4];
  3362.  
  3363. // Check obstruction
  3364. private _obstructionLevel = [_unit, _closestVehicle] call fnc_checkFoliageObstruction;
  3365.  
  3366. // Only engage if obstruction is low enough
  3367. if (_obstructionLevel < 0.5) then {
  3368. // Clear shot - aggressive engagement
  3369. _unit setBehaviour "COMBAT";
  3370. _unit setUnitPos "MIDDLE";
  3371. _unit selectWeapon (secondaryWeapon _unit);
  3372. _unit doTarget _closestVehicle;
  3373. _unit doFire _closestVehicle;
  3374. (group _unit) setVariable ["AICanFire", true];
  3375.  
  3376. // Keep launcher ready flag
  3377. _unit setVariable ["keepLauncherReady", time];
  3378. } else {
  3379. // Too much obstruction - hold fire and try to find better position
  3380. (group _unit) setVariable ["AICanFire", false];
  3381. _unit doWatch _closestVehicle;
  3382.  
  3383. private _betterPos = [_unit, 30] call fnc_findNearestCover;
  3384. if (count _betterPos > 0) then {
  3385. _unit doMove _betterPos;
  3386. _unit setSpeedMode "FULL";
  3387. };
  3388. (group _unit) reveal [_closestVehicle, 2];
  3389. };
  3390. } else {
  3391. // No vehicle threat - can switch back to primary if no recent engagement
  3392. private _keepReady = _unit getVariable ["keepLauncherReady", 0];
  3393. if (time - _keepReady > 10 && currentWeapon _unit == secondaryWeapon _unit) then {
  3394. _unit selectWeapon (primaryWeapon _unit);
  3395. };
  3396. };
  3397. };
  3398. };
  3399. // ===================== AT ENGAGEMENT END =====================
  3400.  
  3401. private _lastCombinedCheck = _unit getVariable ["lastCombinedCheck", 0];
  3402. if (time > _lastCombinedCheck + (6 * PERF_COOLDOWN_MULTIPLIER)) then {
  3403. _unit setVariable ["lastCombinedCheck", time];
  3404.  
  3405. private _nearTargets = _unit nearTargets (120 * PERF_SEARCH_RADIUS_MULTIPLIER);
  3406. private _targetCount = (count _nearTargets) min 5;
  3407.  
  3408. for "_t" from 0 to (_targetCount - 1) do {
  3409. private _targetData = _nearTargets select _t;
  3410. private _target = _targetData select 4;
  3411.  
  3412. if (alive _target && side _target == _enemySide && _target isKindOf "CAManBase") then {
  3413. private _distance = _unit distance _target;
  3414. if (_distance < (120 * PERF_SEARCH_RADIUS_MULTIPLIER) && _unit knowsAbout _target > 0) then {
  3415. private _obstructionLevel = [_unit, _target] call fnc_checkFoliageObstruction;
  3416. private _isBeingShotAt = (time - (_target getVariable ["lastShotAtUnit_" + str _unit, 0])) < 10;
  3417.  
  3418. if (_obstructionLevel > 0.6) then {
  3419. // Heavy obstruction - very low reveal, hold fire
  3420. (group _unit) reveal [_target, 1.0];
  3421. (group _unit) setVariable ["AICanFire", false];
  3422. _unit doWatch objNull;
  3423. } else {
  3424. if (_obstructionLevel > 0.3) then {
  3425. // Moderate obstruction
  3426. if (_isBeingShotAt) then {
  3427. // Under fire - engage anyway with caution
  3428. (group _unit) reveal [_target, 3.5];
  3429. (group _unit) setVariable ["AICanFire", true];
  3430. } else {
  3431. // Not under fire - hold position and watch
  3432. (group _unit) reveal [_target, 1.5];
  3433. (group _unit) setVariable ["AICanFire", false];
  3434. _unit doWatch (getPos _target);
  3435. };
  3436. } else {
  3437. // Clear shot - full engagement
  3438. (group _unit) reveal [_target, 4];
  3439. (group _unit) setVariable ["AICanFire", true];
  3440. };
  3441. };
  3442. };
  3443. };
  3444. };
  3445.  
  3446. private _lastDroneCheck = _unit getVariable ["lastDroneCheck", 0];
  3447. if (time > _lastDroneCheck + (10 * PERF_COOLDOWN_MULTIPLIER)) then {
  3448. _unit setVariable ["lastDroneCheck", time];
  3449. private _nearbyAir = _unit nearEntities ["Air", (350 * PERF_SEARCH_RADIUS_MULTIPLIER)];
  3450. if (count _nearbyAir > 0) then {
  3451. private _enemyDrone = _nearbyAir findIf {
  3452. alive _x && side _x == _enemySide &&
  3453. (_x isKindOf "UAV_01_base_F" || _x isKindOf "UAV_02_base_F" || _x isKindOf "UAV")
  3454. };
  3455. if (_enemyDrone != -1) then {
  3456. private _drone = _nearbyAir select _enemyDrone;
  3457. if (_unit distance _drone < (350 * PERF_SEARCH_RADIUS_MULTIPLIER)) then {
  3458. private _visibility = [_unit, "VIEW", _drone] checkVisibility [eyePos _unit, getPosASL _drone];
  3459. if (_visibility > 0.3) then {
  3460. (group _unit) reveal [_drone, 4];
  3461. (group _unit) setVariable ["AICanFire", true];
  3462. _unit doWatch _drone;
  3463. _unit doTarget _drone;
  3464. _unit doFire _drone;
  3465. };
  3466. };
  3467. };
  3468. };
  3469. };
  3470. };
  3471.  
  3472. private _damage = damage _unit;
  3473. if (_damage > 0.3) then {
  3474. private _lastSmokeTime = _unit getVariable ["lastSmokeTime", 0];
  3475. if (time > _lastSmokeTime + (90 * PERF_COOLDOWN_MULTIPLIER) && getSuppression _unit > 0.5) then {
  3476. [_unit, "cover"] call fnc_botUseSmoke;
  3477. _unit setVariable ["lastSmokeTime", time];
  3478. };
  3479.  
  3480. if (_damage > 0.5) then {
  3481. private _isRetreating = _unit getVariable ["isRetreating", false];
  3482. if (!_isRetreating) then {
  3483. private _retreatPos = _unitPos getPos [30, (direction _unit) + 180];
  3484. _unit doMove _retreatPos;
  3485. _unit setBehaviour "AWARE";
  3486. _unit setVariable ["isRetreating", true];
  3487. _unit setVariable ["retreatPos", _retreatPos];
  3488. } else {
  3489. private _retreatPos = _unit getVariable ["retreatPos", [0,0,0]];
  3490. if (_unit distance _retreatPos < 10) then {
  3491. if ("FirstAidKit" in items _unit) then {
  3492. _unit action ["HealSoldierSelf", _unit];
  3493. _unit removeItem "FirstAidKit";
  3494. };
  3495. _unit setVariable ["isRetreating", false];
  3496. };
  3497. };
  3498. };
  3499. };
  3500.  
  3501. if (currentWeapon _unit != "") then {
  3502. private _currentAmmo = _unit ammo (currentWeapon _unit);
  3503. private _lastKnownAmmo = _unit getVariable ["lastKnownAmmo", _currentAmmo];
  3504. if (_currentAmmo < _lastKnownAmmo) then {
  3505. _unit setVariable ["lastFiredTime", time];
  3506. };
  3507. _unit setVariable ["lastKnownAmmo", _currentAmmo];
  3508. };
  3509. } else {
  3510. // VEHICLE CREW LOGIC
  3511. private _lastVehicleCheck = _vehicle getVariable ["lastVehicleCheck", 0];
  3512. if (time > _lastVehicleCheck + 6) then {
  3513. _vehicle setVariable ["lastVehicleCheck", time];
  3514.  
  3515. private _nearTargets = _unit nearTargets (250 * PERF_SEARCH_RADIUS_MULTIPLIER);
  3516. private _targetCount = (count _nearTargets) min 5;
  3517.  
  3518. for "_t" from 0 to (_targetCount - 1) do {
  3519. private _targetData = _nearTargets select _t;
  3520. private _target = _targetData select 4;
  3521.  
  3522. if (alive _target && side _target == _enemySide && (_target isKindOf "CAManBase" || _target isKindOf "LandVehicle")) then {
  3523. private _distance = _unit distance _target;
  3524. if (_distance < (250 * PERF_SEARCH_RADIUS_MULTIPLIER) && _unit knowsAbout _target > 0) then {
  3525. private _obstructionLevel = [_unit, _target] call fnc_checkFoliageObstruction;
  3526.  
  3527. if (_obstructionLevel > 0.6) then {
  3528. (group _unit) reveal [_target, 1.5];
  3529. } else {
  3530. (group _unit) reveal [_target, 3];
  3531. };
  3532. };
  3533. };
  3534. };
  3535. };
  3536.  
  3537. // HELICOPTER LOGIC
  3538. if (_vehicle isKindOf "Air" && driver _vehicle == _unit) then {
  3539. private _lastHeliCheck = _vehicle getVariable ["lastHeliCheck", 0];
  3540. if (time > _lastHeliCheck + 4) then {
  3541. _vehicle setVariable ["lastHeliCheck", time];
  3542.  
  3543. private _nearMissiles = _vehicle nearObjects ["MissileBase", 800];
  3544. private _lastFlareTime = _vehicle getVariable ["lastFlareTime", 0];
  3545. if (count _nearMissiles > 0 && time > _lastFlareTime + 4) then {
  3546. _vehicle action ["useWeapon", _vehicle, driver _vehicle, 0];
  3547. _vehicle setVariable ["lastFlareTime", time];
  3548. private _evadeDir = random 360;
  3549. _vehicle doMove (_vehicle getPos [300, _evadeDir]);
  3550. _vehicle flyInHeight (50 + random 150);
  3551. };
  3552.  
  3553. if (isNull (assignedTarget _unit)) then {
  3554. private _potentialTargets = _vehicle nearEntities [["CAManBase", "LandVehicle"], 1000];
  3555. private _enemies = _potentialTargets select {
  3556. side _x == _enemySide && alive _x && _vehicle knowsAbout _x > 0.5
  3557. };
  3558.  
  3559. if (count _enemies > 0) then {
  3560. (gunner _vehicle) doTarget (_enemies select 0);
  3561. (gunner _vehicle) doFire (_enemies select 0);
  3562. };
  3563. };
  3564. };
  3565. };
  3566. };
  3567.  
  3568. private _lastIntelTime = _unit getVariable ["BOTAI_lastIntelCheck", 0];
  3569. if (time > _lastIntelTime + (15 * PERF_COOLDOWN_MULTIPLIER)) then {
  3570. _unit setVariable ["BOTAI_lastIntelCheck", time];
  3571. [_unit, side _unit] call HC_fnc_gatherIntelligence;
  3572. [_unit, side _unit] call HC_fnc_investigateNearbyIntel;
  3573. };
  3574. };
  3575. };
  3576.  
  3577. // Move to next batch
  3578. _unitIndex = _unitIndex + _batchSize;
  3579. if (_unitIndex >= count _allAiUnits) then {
  3580. _unitIndex = 0;
  3581. };
  3582.  
  3583. // Measure cycle execution time for the performance monitor
  3584. private _cycleEndTime = diag_tickTime;
  3585. private _cycleExecTime = _cycleEndTime - _cycleStartTime;
  3586. missionNamespace setVariable ["BOTAI_lastExecTime", _cycleExecTime];
  3587.  
  3588. // Log if a single cycle takes too long
  3589. if (_cycleExecTime > 0.050) then {
  3590. diag_log format ["[BOTAI] SLOW CYCLE: %1ms (batch: %2, state: %3)",
  3591. round (_cycleExecTime * 1000), _batchSize, PERF_STATE];
  3592. };
  3593.  
  3594. // Adaptive sleep time based on server performance
  3595. sleep (1.0 * PERF_MAIN_LOOP_MULTIPLIER);
  3596. };
  3597. };
  3598. };
  3599.  
  3600. ==================== END OF: botai.sqf ====================
  3601.  
  3602.  
  3603.  
  3604. ==================== START OF: cleanup.sqf ====================
  3605.  
  3606. // cleanup.sqf
  3607.  
  3608. if (isNil "CLEANUP_BODY_LIMIT") then { CLEANUP_BODY_LIMIT = 20; };
  3609. if (isNil "CLEANUP_DELAY") then { CLEANUP_DELAY = 30; };
  3610. if (isNil "CLEANUP_BODY_TIMER") then { CLEANUP_BODY_TIMER = 120; };
  3611. if (isNil "CLEANUP_WEAPON_TIMER") then { CLEANUP_WEAPON_TIMER = 300; };
  3612. if (isNil "CLEANUP_VEHICLE_TIMER") then { CLEANUP_VEHICLE_TIMER = 120; };
  3613. if (isNil "CLEANUP_MIN_DISTANCE") then { CLEANUP_MIN_DISTANCE = 300; };
  3614.  
  3615. if (isNil "CLEANUP_BODY_ARRAY") then { CLEANUP_BODY_ARRAY = []; };
  3616.  
  3617. fnc_performCleanup = {
  3618. private _startTime = diag_tickTime;
  3619. private _deleted = 0;
  3620. private _currentTime = time;
  3621.  
  3622. // Apply dynamic multiplier from performance settings
  3623. // PERF_CLEANUP_MULTIPLIER is defined in performance.sqf (default 1.0)
  3624. // Lower multiplier means faster cleanup (shorter timers)
  3625. private _timerMult = missionNamespace getVariable ["PERF_CLEANUP_MULTIPLIER", 1.0];
  3626.  
  3627. private _bodyTimer = CLEANUP_BODY_TIMER * _timerMult;
  3628. private _weaponTimer = CLEANUP_WEAPON_TIMER * _timerMult;
  3629. private _vehicleTimer = CLEANUP_VEHICLE_TIMER * _timerMult;
  3630.  
  3631. // --- Add new dead bodies to the tracking array ---
  3632. private _deadUnits = allDead select {_x isKindOf "CAManBase"};
  3633. {
  3634. private _body = _x;
  3635. if ((CLEANUP_BODY_ARRAY findIf {(_x select 0) == _body}) == -1) then {
  3636. CLEANUP_BODY_ARRAY pushBack [_body, _currentTime];
  3637. };
  3638. } forEach _deadUnits;
  3639.  
  3640. // --- Process and delete old bodies ---
  3641. for "_i" from (count CLEANUP_BODY_ARRAY - 1) to 0 step -1 do {
  3642. private _entry = CLEANUP_BODY_ARRAY select _i;
  3643. private _body = _entry select 0;
  3644. private _deathTime = _entry select 1;
  3645.  
  3646. if (isNull _body || (_currentTime - _deathTime > _bodyTimer)) then {
  3647. if (!isNull _body) then {
  3648. deleteVehicle _body;
  3649. _deleted = _deleted + 1;
  3650. };
  3651. CLEANUP_BODY_ARRAY deleteAt _i;
  3652. };
  3653. };
  3654.  
  3655. // --- Process Ground Weapon Holders ---
  3656. {
  3657. private _holder = _x;
  3658. if ((count (weaponCargo _holder) == 0) && (count (magazineCargo _holder) == 0) && (count (itemCargo _holder) == 0)) then {
  3659. deleteVehicle _holder;
  3660. _deleted = _deleted + 1;
  3661. } else {
  3662. private _age = _currentTime - (_holder getVariable ["cleanup_time", _currentTime]);
  3663. if (isNil {_holder getVariable "cleanup_time"}) then {
  3664. _holder setVariable ["cleanup_time", _currentTime];
  3665. };
  3666. if (_age > _weaponTimer) then {
  3667. deleteVehicle _holder;
  3668. _deleted = _deleted + 1;
  3669. };
  3670. };
  3671. } forEach (allMissionObjects "GroundWeaponHolder");
  3672.  
  3673. // ==================== CHANGE START: HIGHLY OPTIMIZED VEHICLE CLEANUP ====================
  3674. private _cleanupTimer = _vehicleTimer; // Use dynamic timer
  3675.  
  3676. // Helper function to process a vehicle for cleanup
  3677. private _fnc_processVehicle = {
  3678. params ["_vehicle", "_timerValue"]; // Accept timer as argument
  3679. if (isNull _vehicle || {_vehicle isKindOf "CAManBase"}) exitWith {};
  3680.  
  3681. // Aggressive cleanup for unoccupied quad bikes
  3682. if (_vehicle isKindOf "Quadbike_01_base_F") then {
  3683. if (count crew _vehicle == 0) then {
  3684. private _startTime = _vehicle getVariable ["cleanup_unoccupied_startTime", -1];
  3685. if (_startTime == -1) then {
  3686. _vehicle setVariable ["cleanup_unoccupied_startTime", time, true];
  3687. } else {
  3688. if (time - _startTime > 300) then { // 5 minute timer for abandoned quads
  3689. deleteVehicle _vehicle;
  3690. _deleted = _deleted + 1;
  3691. continue;
  3692. };
  3693. };
  3694. } else {
  3695. if !(isNil {_vehicle getVariable "cleanup_unoccupied_startTime"}) then {
  3696. _vehicle setVariable ["cleanup_unoccupied_startTime", nil, true];
  3697. };
  3698. };
  3699. };
  3700.  
  3701. private _isDerelict = false;
  3702. if (!alive _vehicle) then {
  3703. _isDerelict = true; // Is destroyed
  3704. } else {
  3705. if (damage _vehicle > 0.9) then {
  3706. _isDerelict = true; // Is heavily damaged
  3707. } else {
  3708. if (count crew _vehicle == 0) then {
  3709. // Is abandoned (only check if it was an AI vehicle)
  3710. if ((_vehicle getVariable ["isAIVehicle", false]) || (_vehicle getVariable ["unitValue", 0] > 0)) then {
  3711. _isDerelict = true;
  3712. };
  3713. };
  3714. };
  3715. };
  3716.  
  3717. // Don't clean protected vehicles unless they are derelict
  3718. if ((_vehicle getVariable ["gcImportant", false]) && !_isDerelict) then {
  3719. if !(isNil {_vehicle getVariable "cleanup_startTime"}) then {
  3720. _vehicle setVariable ["cleanup_startTime", nil, true];
  3721. };
  3722. continue;
  3723. };
  3724.  
  3725. if (_isDerelict) then {
  3726. private _startTime = _vehicle getVariable ["cleanup_startTime", -1];
  3727. if (_startTime == -1) then {
  3728. _vehicle setVariable ["cleanup_startTime", time, true];
  3729. } else {
  3730. if (time - _startTime > _timerValue) then {
  3731. deleteVehicle _vehicle;
  3732. _deleted = _deleted + 1;
  3733. };
  3734. };
  3735. } else {
  3736. if !(isNil {_vehicle getVariable "cleanup_startTime"}) then {
  3737. _vehicle setVariable ["cleanup_startTime", nil, true];
  3738. };
  3739. };
  3740. };
  3741.  
  3742. // 1. Process all recently destroyed vehicles (fast)
  3743. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach (allDead select {!(_x isKindOf "CAManBase")});
  3744.  
  3745. // 2. Process tracked AI vehicles that might be abandoned (fast)
  3746. BLUFOR_ACTIVE_LIGHT_VEHICLES = BLUFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x};
  3747. OPFOR_ACTIVE_LIGHT_VEHICLES = OPFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x};
  3748. BLUFOR_ACTIVE_TANKS = BLUFOR_ACTIVE_TANKS select {!isNull _x};
  3749. OPFOR_ACTIVE_TANKS = OPFOR_ACTIVE_TANKS select {!isNull _x};
  3750. BLUFOR_ACTIVE_ATTACK_HELIS = BLUFOR_ACTIVE_ATTACK_HELIS select {!isNull _x};
  3751. OPFOR_ACTIVE_ATTACK_HELIS = OPFOR_ACTIVE_ATTACK_HELIS select {!isNull _x};
  3752.  
  3753. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach BLUFOR_ACTIVE_LIGHT_VEHICLES;
  3754. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach OPFOR_ACTIVE_LIGHT_VEHICLES;
  3755. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach BLUFOR_ACTIVE_TANKS;
  3756. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach OPFOR_ACTIVE_TANKS;
  3757. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach BLUFOR_ACTIVE_ATTACK_HELIS;
  3758. { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach OPFOR_ACTIVE_ATTACK_HELIS;
  3759. // ===================== CHANGE END =====================
  3760.  
  3761. // --- Empty Group Cleanup ---
  3762. {
  3763. if (count (units _x) == 0) then {
  3764. deleteGroup _x;
  3765. };
  3766. } forEach allGroups;
  3767.  
  3768. // --- Crater and Smoke Cleanup ---
  3769. { deleteVehicle _x; _deleted = _deleted + 1; } forEach (allMissionObjects "CraterLong");
  3770. { deleteVehicle _x; _deleted = _deleted + 1; } forEach (allMissionObjects "CraterLong_small");
  3771.  
  3772. {
  3773. private _obj = _x;
  3774. private _age = time - (_obj getVariable ["cleanup_time", time]);
  3775.  
  3776. if (_age > 300) then {
  3777. deleteVehicle _obj;
  3778. _deleted = _deleted + 1;
  3779. } else {
  3780. if (isNil {_obj getVariable "cleanup_time"}) then {
  3781. _obj setVariable ["cleanup_time", time];
  3782. };
  3783. };
  3784. } forEach (allMissionObjects "SmokeShell");
  3785.  
  3786. private _endTime = diag_tickTime;
  3787. private _executionTime = (_endTime - _startTime) * 1000;
  3788. };
  3789.  
  3790. if (isServer) then {
  3791. [] spawn {
  3792. waitUntil {time > 10};
  3793.  
  3794. while {true} do {
  3795. // Adaptive cleanup delay
  3796. private _baseDelay = CLEANUP_DELAY;
  3797. private _adaptiveDelay = [_baseDelay] call fnc_getAdaptiveSleep;
  3798.  
  3799. sleep _adaptiveDelay;
  3800.  
  3801. if (count allPlayers > 0) then {
  3802. [] call fnc_performCleanup;
  3803. };
  3804. };
  3805. };
  3806.  
  3807. addMissionEventHandler ["EntityKilled", {
  3808. params ["_victim"];
  3809.  
  3810. if (_victim isKindOf "AllVehicles" && !(_victim isKindOf "CAManBase")) then {
  3811. _victim setVariable ["cleanup_destroyed_time", time];
  3812. };
  3813. }];
  3814. };
  3815.  
  3816. ==================== END OF: cleanup.sqf ====================
  3817.  
  3818.  
  3819.  
  3820. ==================== START OF: description.ext ====================
  3821.  
  3822. class Header
  3823. {
  3824. gameType = CTI;
  3825. minPlayers = 1;
  3826. maxPlayers = 50;
  3827. };
  3828. respawn = "BASE"; // Enables respawn at the defined respawn positions (from BIS_fnc_addRespawnPosition in init.sqf)
  3829. respawnDelay = 5; // Default respawn delay in seconds. Will be dynamically adjusted by playerinit.sqf
  3830. respawnDialog = 0; // Hides the respawn screen, making respawn automatic after the delay
  3831.  
  3832. // ======================= BASE UI CLASSES =======================
  3833. class RscText_Base {
  3834. type = 0; style = 0;
  3835. font = "RobotoCondensed"; sizeEx = 0.035;
  3836. colorText[] = {1,1,1,1};
  3837. colorBackground[] = {0,0,0,0}; // Transparent background
  3838. text = "";
  3839. };
  3840.  
  3841. class RscButton_Base {
  3842. type = 1; style = 2;
  3843. font = "RobotoCondensed"; sizeEx = 0.035;
  3844. colorText[] = {1,1,1,1};
  3845. colorBackground[] = {0,0,0,0.8};
  3846. colorBackgroundActive[] = {1,1,1,0.3};
  3847. colorFocused[] = {0,0,0,1};
  3848. colorDisabled[] = {1,1,1,0.3};
  3849. colorBackgroundDisabled[] = {0,0,0,0.3};
  3850. colorBorder[] = {0,0,0,1};
  3851. colorShadow[] = {0,0,0,0.5};
  3852. soundEnter[] = {"\A3\ui_f\data\sound\RscButton\soundEnter", 0.09, 1};
  3853. soundPush[] = {"\A3\ui_f\data\sound\RscButton\soundPush", 0.09, 1};
  3854. soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick", 0.09, 1};
  3855. soundEscape[] = {"\A3\ui_f\data\sound\RscButton\soundEscape", 0.09, 1};
  3856. borderSize = 0; offsetX = 0; offsetY = 0; offsetPressedX = 0; offsetPressedY = 0;
  3857. };
  3858.  
  3859. // ======================= MISSION BRIEFING DIALOG =======================
  3860. class MissionBriefingDialog {
  3861. idd = 9300;
  3862. movingEnable = false;
  3863. onLoad = "uiNamespace setVariable ['MissionBriefingDisplay', _this select 0];";
  3864. onUnload = "uiNamespace setVariable ['MissionBriefingDisplay', displayNull];";
  3865.  
  3866. class Controls {
  3867. // --- BACKGROUND ---
  3868. class Background {
  3869. type = 0; style = 0; idc = 93000;
  3870. x = 0.1; y = 0.05; w = 0.8; h = 0.9; // Increased height, moved up
  3871. font = "RobotoCondensed"; sizeEx = 0.04;
  3872. colorBackground[] = {0,0,0,0.8};
  3873. colorText[] = {1,1,1,1};
  3874. text = "";
  3875. };
  3876. // --- TITLE ---
  3877. class Title {
  3878. type = 0; style = 2; idc = 93001;
  3879. text = "Mission Briefing";
  3880. x = 0.1; y = 0.07; w = 0.8; h = 0.04; // Moved up
  3881. font = "RobotoCondensed"; sizeEx = 0.04;
  3882. colorBackground[] = {0,0,0,0};
  3883. colorText[] = {1,1,1,1};
  3884. };
  3885. // --- TEXT AREA (SCROLLABLE) ---
  3886. class BriefingText {
  3887. type = 0; style = 16; // ST_MULTI for scrollable text
  3888. idc = 93002;
  3889. text = "QUICK START GUIDE\n\n--- OBJECTIVE ---\nYour primary goal is to capture the enemy's main Command Tower. You can also win if the enemy team's forces collapse.\n\n--- AI COMMANDER & TEAM POINTS ---\nEach team is led by an AI Commander who automatically purchases AI squads and vehicles. The Commander uses Team Points, which your side earns for every enemy unit destroyed.\n\n--- YOUR ROLE & PLAYER POINTS ---\nAs a player, you earn Personal Points for kills. These points are yours to spend. To purchase vehicles, go to the Arsenal Box at your base and use your scroll wheel menu to open the Purchase Menu.\n\n--- WINNING THE GAME ---\n1. Base Capture: Get within 5 meters of the enemy Command Tower and use your scroll wheel to select 'Capture Base'. You must survive for 60 seconds to win.\n\n2. Enemy Collapse: The round will also end if an enemy team's points fall below 50 AND their army is less than half the size of yours.";
  3890. x = 0.12; y = 0.13; w = 0.76; h = 0.74; // Increased height, moved up
  3891. font = "RobotoCondensed"; sizeEx = 0.032; // Reduced font size
  3892. colorBackground[] = {0,0,0,0};
  3893. colorText[] = {1,1,1,1};
  3894. lineSpacing = 1;
  3895. };
  3896. // --- CLOSE BUTTON ---
  3897. class CloseButton: RscButton_Base {
  3898. idc = 93003;
  3899. text = "Close";
  3900. x = 0.475; y = 0.88; w = 0.15; h = 0.04; // Adjusted position for larger dialog
  3901. action = "closeDialog 0;";
  3902. };
  3903. };
  3904. };
  3905.  
  3906. // ======================= MAIN PURCHASE MENU =======================
  3907. class PlayerPurchaseMenu {
  3908. idd = 9000;
  3909. movingEnable = false;
  3910. onLoad = "((_this select 0) displayCtrl 9002) ctrlSetText format['Points: %1', player getVariable ['playerPoints', 0]];";
  3911.  
  3912. class Controls {
  3913. // --- BACKGROUND AND TITLES ---
  3914. class Background {
  3915. type = 0; style = 0; idc = 90000;
  3916. x = 0.3; y = 0.28; w = 0.4; h = 0.48;
  3917. font = "RobotoCondensed"; sizeEx = 0.04;
  3918. colorBackground[] = {0,0,0,0.7};
  3919. colorText[] = {1,1,1,1};
  3920. text = "";
  3921. };
  3922. class Title {
  3923. type = 0; style = 2; idc = 9001;
  3924. text = "Purchase Menu";
  3925. x = 0.3; y = 0.3; w = 0.4; h = 0.04;
  3926. font = "RobotoCondensed"; sizeEx = 0.04;
  3927. colorBackground[] = {0,0,0,0};
  3928. colorText[] = {1,1,1,1};
  3929. };
  3930. class PointsDisplay {
  3931. type = 0; style = 2; idc = 9002;
  3932. text = "Points: 100";
  3933. x = 0.3; y = 0.34; w = 0.4; h = 0.04;
  3934. font = "RobotoCondensed"; sizeEx = 0.04;
  3935. colorBackground[] = {0,0,0,0};
  3936. colorText[] = {1,1,0,1};
  3937. };
  3938.  
  3939. // --- BUTTONS ---
  3940. class QuadButton: RscButton_Base {
  3941. idc = 9003;
  3942. text = "Quad Bike (1 pt)";
  3943. x = 0.32; y = 0.40; w = 0.36; h = 0.05;
  3944. action = "private _veh = if (side player == west) then {'B_Quadbike_01_F'} else {'O_Quadbike_01_F'}; [player, _veh] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  3945. };
  3946.  
  3947. class LightVicButton: RscButton_Base {
  3948. idc = 9004;
  3949. text = "Light Vehicles (10 pts)";
  3950. x = 0.32; y = 0.46; w = 0.36; h = 0.05;
  3951. action = "closeDialog 0; createDialog 'LightVehicleMenu';";
  3952. };
  3953.  
  3954. class HeavyVicButton: RscButton_Base {
  3955. idc = 9006;
  3956. text = "Heavy Vehicles (20-30 pts)";
  3957. x = 0.32; y = 0.52; w = 0.36; h = 0.05;
  3958. action = "closeDialog 0; createDialog 'HeavyVehicleMenu';";
  3959. };
  3960.  
  3961. class ParadropButton: RscButton_Base {
  3962. idc = 9007;
  3963. text = "Paradrop Near Stronghold (2 pts)";
  3964. x = 0.32; y = 0.58; w = 0.36; h = 0.05;
  3965. action = "[player] remoteExecCall ['fnc_server_paradropPlayer', 2]; closeDialog 0;";
  3966. };
  3967.  
  3968. // ==================== CHANGE START ====================
  3969. class AISquadButton: RscButton_Base {
  3970. idc = 9008;
  3971. text = "Buy 3 Man AI Squad (15 pts)";
  3972. x = 0.32; y = 0.64; w = 0.36; h = 0.05;
  3973. action = "[player] remoteExecCall ['fnc_server_purchaseAISquad', 2]; closeDialog 0;";
  3974. };
  3975. // ===================== CHANGE END =====================
  3976.  
  3977. class CloseButton: RscButton_Base {
  3978. idc = 9005;
  3979. text = "Close";
  3980. x = 0.425; y = 0.70; w = 0.15; h = 0.04;
  3981. action = "closeDialog 0;";
  3982. };
  3983. };
  3984. };
  3985.  
  3986. // ======================= LIGHT VEHICLE SUB-MENU (Self-Contained) =======================
  3987. class LightVehicleMenu {
  3988. idd = 9100;
  3989. movingEnable = false;
  3990. onLoad = " private _display = _this select 0; private _isBlufor = side player == west; ((_display displayCtrl 9100) ctrlSetText format ['Points: %1', player getVariable ['playerPoints', 0]]); ((_display displayCtrl 9101) ctrlShow _isBlufor); ((_display displayCtrl 9102) ctrlShow _isBlufor); ((_display displayCtrl 9103) ctrlShow !_isBlufor); ((_display displayCtrl 9104) ctrlShow !_isBlufor); ";
  3991.  
  3992. class Controls {
  3993. class Background {
  3994. type = 0; style = 0; idc = 91000;
  3995. x = 0.3; y = 0.28; w = 0.4; h = 0.35;
  3996. font = "RobotoCondensed"; sizeEx = 0.04;
  3997. colorBackground[] = {0,0,0,0.7};
  3998. colorText[] = {1,1,1,1}; text = "";
  3999. };
  4000. class Title {
  4001. type = 0; style = 2; idc = 91001;
  4002. text = "Light Vehicles (10 pts)";
  4003. x = 0.3; y = 0.3; w = 0.4; h = 0.04;
  4004. font = "RobotoCondensed"; sizeEx = 0.04;
  4005. colorBackground[] = {0,0,0,0}; colorText[] = {1,1,1,1};
  4006. };
  4007. class PointsDisplay {
  4008. type = 0; style = 2; idc = 9100;
  4009. text = "Points: 100";
  4010. x = 0.3; y = 0.34; w = 0.4; h = 0.04;
  4011. font = "RobotoCondensed"; sizeEx = 0.04;
  4012. colorBackground[] = {0,0,0,0}; colorText[] = {1,1,0,1};
  4013. };
  4014. // --- BLUFOR BUTTONS ---
  4015. class HunterButton: RscButton_Base {
  4016. idc = 9101;
  4017. text = "Hunter HMG";
  4018. x = 0.32; y = 0.40; w = 0.17; h = 0.05;
  4019. action = "[player, 'B_MRAP_01_hmg_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4020. };
  4021. class PawneeButton: RscButton_Base {
  4022. idc = 9102;
  4023. text = "Pawnee";
  4024. x = 0.51; y = 0.40; w = 0.17; h = 0.05;
  4025. action = "[player, 'B_Heli_Light_01_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4026. };
  4027. // --- OPFOR BUTTONS ---
  4028. class IfritButton: RscButton_Base {
  4029. idc = 9103;
  4030. text = "Ifrit HMG";
  4031. x = 0.32; y = 0.40; w = 0.17; h = 0.05;
  4032. action = "[player, 'O_MRAP_02_hmg_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4033. };
  4034. class OrcaButton: RscButton_Base {
  4035. idc = 9104;
  4036. text = "Orca";
  4037. x = 0.51; y = 0.40; w = 0.17; h = 0.05;
  4038. action = "[player, 'O_Heli_Light_02_unarmed_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4039. };
  4040. // --- UTILITY BUTTONS ---
  4041. class BackButton: RscButton_Base {
  4042. idc = 9198;
  4043. text = "Back";
  4044. x = 0.32; y = 0.58; w = 0.15; h = 0.04;
  4045. action = "closeDialog 0; createDialog 'PlayerPurchaseMenu';";
  4046. };
  4047. class CloseButton: RscButton_Base {
  4048. idc = 9199;
  4049. text = "Close";
  4050. x = 0.53; y = 0.58; w = 0.15; h = 0.04;
  4051. action = "closeDialog 0;";
  4052. };
  4053. };
  4054. };
  4055.  
  4056. // ======================= HEAVY VEHICLE SUB-MENU (Self-Contained) =======================
  4057. class HeavyVehicleMenu {
  4058. idd = 9200;
  4059. movingEnable = false;
  4060. onLoad = " private _display = _this select 0; private _isBlufor = side player == west; ((_display displayCtrl 9200) ctrlSetText format ['Points: %1', player getVariable ['playerPoints', 0]]); ((_display displayCtrl 9201) ctrlShow _isBlufor); ((_display displayCtrl 9202) ctrlShow _isBlufor); ((_display displayCtrl 9203) ctrlShow !_isBlufor); ((_display displayCtrl 9204) ctrlShow !_isBlufor); ";
  4061.  
  4062. class Controls {
  4063. class Background {
  4064. type = 0; style = 0; idc = 92000;
  4065. x = 0.3; y = 0.28; w = 0.4; h = 0.35;
  4066. font = "RobotoCondensed"; sizeEx = 0.04;
  4067. colorBackground[] = {0,0,0,0.7};
  4068. colorText[] = {1,1,1,1}; text = "";
  4069. };
  4070. class Title {
  4071. type = 0; style = 2; idc = 92001;
  4072. text = "Heavy Vehicles";
  4073. x = 0.3; y = 0.3; w = 0.4; h = 0.04;
  4074. font = "RobotoCondensed"; sizeEx = 0.04;
  4075. colorBackground[] = {0,0,0,0}; colorText[] = {1,1,1,1};
  4076. };
  4077. class PointsDisplay {
  4078. type = 0; style = 2; idc = 9200;
  4079. text = "Points: 100";
  4080. x = 0.3; y = 0.34; w = 0.4; h = 0.04;
  4081. font = "RobotoCondensed"; sizeEx = 0.04;
  4082. colorBackground[] = {0,0,0,0}; colorText[] = {1,1,0,1};
  4083. };
  4084. // --- BLUFOR BUTTONS ---
  4085. class MarshallButton: RscButton_Base {
  4086. idc = 9201;
  4087. text = "Marshall APC (20 pts)";
  4088. x = 0.32; y = 0.40; w = 0.17; h = 0.05;
  4089. action = "[player, 'B_APC_Wheeled_01_cannon_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4090. };
  4091. class SlammerButton: RscButton_Base {
  4092. idc = 9202;
  4093. text = "Slammer MBT (30 pts)";
  4094. x = 0.51; y = 0.40; w = 0.17; h = 0.05;
  4095. action = "[player, 'B_MBT_01_cannon_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4096. };
  4097. // --- OPFOR BUTTONS ---
  4098. class MaridButton: RscButton_Base {
  4099. idc = 9203;
  4100. text = "Marid APC (20 pts)";
  4101. x = 0.32; y = 0.40; w = 0.17; h = 0.05;
  4102. action = "[player, 'O_APC_Wheeled_02_rcws_v2_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4103. };
  4104. class VarsukButton: RscButton_Base {
  4105. idc = 9204;
  4106. text = "Varsuk MBT (30 pts)";
  4107. x = 0.51; y = 0.40; w = 0.17; h = 0.05;
  4108. action = "[player, 'O_MBT_02_cannon_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
  4109. };
  4110. // --- UTILITY BUTTONS ---
  4111. class BackButton: RscButton_Base {
  4112. idc = 9298;
  4113. text = "Back";
  4114. x = 0.32; y = 0.58; w = 0.15; h = 0.04;
  4115. action = "closeDialog 0; createDialog 'PlayerPurchaseMenu';";
  4116. };
  4117. class CloseButton: RscButton_Base {
  4118. idc = 9299;
  4119. text = "Close";
  4120. x = 0.53; y = 0.58; w = 0.15; h = 0.04;
  4121. action = "closeDialog 0;";
  4122. };
  4123. };
  4124. };
  4125.  
  4126. // ======================= ADMIN/SP SETTINGS MENU =======================
  4127. class AdminSettingsMenu {
  4128. idd = 9400;
  4129. movingEnable = false;
  4130. onLoad = " \
  4131. private _display = _this select 0; \
  4132. private _isSP = !isMultiplayer; \
  4133. _display displayCtrl 9411 ctrlShow _isSP; \
  4134. _display displayCtrl 9412 ctrlShow _isSP; \
  4135. _display displayCtrl 9413 ctrlShow _isSP; \
  4136. _display displayCtrl 9414 ctrlShow _isSP; \
  4137. _display displayCtrl 9415 ctrlShow _isSP; \
  4138. if (!_isSP) then { \
  4139. (_display displayCtrl 9410) ctrlSetText 'Team Points (SP Only - Disabled)'; \
  4140. (_display displayCtrl 9416) ctrlSetText 'Personal Points (SP Only - Disabled)'; \
  4141. }; \
  4142. _display displayCtrl 9420 ctrlSetText (if (_isSP) then {'Singleplayer Settings'} else {'Host Settings'}); \
  4143. private _currentMaxAI = missionNamespace getVariable ['maxAI', 120]; \
  4144. (_display displayCtrl 9421) ctrlSetText format ['Current: %1', _currentMaxAI]; \
  4145. ";
  4146.  
  4147. class Controls {
  4148. // --- BACKGROUND AND TITLES ---
  4149. class Background: RscText_Base {
  4150. idc = 94000;
  4151. x = 0.25; y = 0.1; w = 0.5; h = 0.8;
  4152. colorBackground[] = {0,0,0,0.7};
  4153. };
  4154. class Title: RscText_Base {
  4155. idc = 9420; style = 2; // Centered
  4156. text = "Settings";
  4157. x = 0.25; y = 0.12; w = 0.5; h = 0.04;
  4158. sizeEx = 0.04;
  4159. };
  4160.  
  4161. // --- MAX AI SETTINGS ---
  4162. class MaxAITitle: RscText_Base {
  4163. idc = -1;
  4164. text = "Max AI Count:";
  4165. x = 0.26; y = 0.18; w = 0.2; h = 0.04;
  4166. sizeEx = 0.03;
  4167. };
  4168. class MaxAIDisplay: RscText_Base {
  4169. idc = 9421;
  4170. text = "Current: 120";
  4171. x = 0.55; y = 0.18; w = 0.2; h = 0.04;
  4172. style = 1; // Right aligned
  4173. sizeEx = 0.03;
  4174. colorText[] = {1,1,0,1};
  4175. };
  4176. // --- ROW 1 ---
  4177. class MaxAIButton80: RscButton_Base {
  4178. idc = -1; text = "80";
  4179. x = 0.26; y = 0.22; w = 0.08; h = 0.04;
  4180. action = "[80] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 80';";
  4181. };
  4182. class MaxAIButton90: RscButton_Base {
  4183. idc = -1; text = "90";
  4184. x = 0.36; y = 0.22; w = 0.08; h = 0.04;
  4185. action = "[90] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 90';";
  4186. };
  4187. class MaxAIButton100: RscButton_Base {
  4188. idc = -1; text = "100";
  4189. x = 0.46; y = 0.22; w = 0.08; h = 0.04;
  4190. action = "[100] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 100';";
  4191. };
  4192. class MaxAIButton110: RscButton_Base {
  4193. idc = -1; text = "110";
  4194. x = 0.56; y = 0.22; w = 0.08; h = 0.04;
  4195. action = "[110] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 110';";
  4196. };
  4197. class MaxAIButton120: RscButton_Base {
  4198. idc = -1; text = "120";
  4199. x = 0.66; y = 0.22; w = 0.08; h = 0.04;
  4200. action = "[120] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 120';";
  4201. };
  4202. // --- ROW 2 ---
  4203. class MaxAIButton130: RscButton_Base {
  4204. idc = -1; text = "130";
  4205. x = 0.26; y = 0.27; w = 0.08; h = 0.04;
  4206. action = "[130] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 130';";
  4207. };
  4208. class MaxAIButton140: RscButton_Base {
  4209. idc = -1; text = "140";
  4210. x = 0.36; y = 0.27; w = 0.08; h = 0.04;
  4211. action = "[140] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 140';";
  4212. };
  4213. class MaxAIButton150: RscButton_Base {
  4214. idc = -1; text = "150";
  4215. x = 0.46; y = 0.27; w = 0.08; h = 0.04;
  4216. action = "[150] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 150';";
  4217. };
  4218. class MaxAIButton160: RscButton_Base {
  4219. idc = -1; text = "160";
  4220. x = 0.56; y = 0.27; w = 0.08; h = 0.04;
  4221. action = "[160] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 160';";
  4222. };
  4223. class MaxAIButton170: RscButton_Base {
  4224. idc = -1; text = "170";
  4225. x = 0.66; y = 0.27; w = 0.08; h = 0.04;
  4226. action = "[170] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 170';";
  4227. };
  4228. // --- ROW 3 ---
  4229. class MaxAIButton180: RscButton_Base {
  4230. idc = -1; text = "180";
  4231. x = 0.26; y = 0.32; w = 0.08; h = 0.04;
  4232. action = "[180] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 180';";
  4233. };
  4234. class MaxAIButton190: RscButton_Base {
  4235. idc = -1; text = "190";
  4236. x = 0.36; y = 0.32; w = 0.08; h = 0.04;
  4237. action = "[190] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 190';";
  4238. };
  4239. class MaxAIButton200: RscButton_Base {
  4240. idc = -1; text = "200";
  4241. x = 0.46; y = 0.32; w = 0.08; h = 0.04;
  4242. action = "[200] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 200';";
  4243. };
  4244. class MaxAIButton210: RscButton_Base {
  4245. idc = -1; text = "210";
  4246. x = 0.56; y = 0.32; w = 0.08; h = 0.04;
  4247. action = "[210] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 210';";
  4248. };
  4249. class MaxAIButton220: RscButton_Base {
  4250. idc = -1; text = "220";
  4251. x = 0.66; y = 0.32; w = 0.08; h = 0.04;
  4252. action = "[220] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 220';";
  4253. };
  4254. // --- ROW 4 ---
  4255. class MaxAIButton230: RscButton_Base {
  4256. idc = -1; text = "230";
  4257. x = 0.26; y = 0.37; w = 0.08; h = 0.04;
  4258. action = "[230] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 230';";
  4259. };
  4260. class MaxAIButton240: RscButton_Base {
  4261. idc = -1; text = "240";
  4262. x = 0.36; y = 0.37; w = 0.08; h = 0.04;
  4263. action = "[240] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 240';";
  4264. };
  4265. class MaxAIButton250: RscButton_Base {
  4266. idc = -1; text = "250";
  4267. x = 0.46; y = 0.37; w = 0.08; h = 0.04;
  4268. action = "[250] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 250';";
  4269. };
  4270. class MaxAIButton260: RscButton_Base {
  4271. idc = -1; text = "260";
  4272. x = 0.56; y = 0.37; w = 0.08; h = 0.04;
  4273. action = "[260] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 260';";
  4274. };
  4275. class MaxAIButton270: RscButton_Base {
  4276. idc = -1; text = "270";
  4277. x = 0.66; y = 0.37; w = 0.08; h = 0.04;
  4278. action = "[270] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 270';";
  4279. };
  4280. // --- ROW 5 ---
  4281. class MaxAIButton280: RscButton_Base {
  4282. idc = -1; text = "280";
  4283. x = 0.26; y = 0.42; w = 0.08; h = 0.04;
  4284. action = "[280] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 280';";
  4285. };
  4286. class MaxAIButton290: RscButton_Base {
  4287. idc = -1; text = "290";
  4288. x = 0.36; y = 0.42; w = 0.08; h = 0.04;
  4289. action = "[290] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 290';";
  4290. };
  4291. class MaxAIButton300: RscButton_Base {
  4292. idc = -1; text = "300";
  4293. x = 0.46; y = 0.42; w = 0.08; h = 0.04;
  4294. action = "[300] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 300';";
  4295. };
  4296.  
  4297. // --- TIMESCALE SETTINGS ---
  4298. class TimescaleTitle: RscText_Base {
  4299. idc = -1;
  4300. text = "Timescale:";
  4301. x = 0.26; y = 0.48; w = 0.2; h = 0.04;
  4302. sizeEx = 0.03;
  4303. };
  4304. class TimescaleButton1: RscButton_Base {
  4305. idc = -1; text = "Realtime";
  4306. x = 0.26; y = 0.52; w = 0.15; h = 0.04;
  4307. action = "[1] remoteExecCall ['fnc_server_setTimescale', 2];";
  4308. };
  4309. class TimescaleButton2: RscButton_Base {
  4310. idc = -1; text = "2h = 1 Day";
  4311. x = 0.42; y = 0.52; w = 0.15; h = 0.04;
  4312. action = "[12] remoteExecCall ['fnc_server_setTimescale', 2];";
  4313. };
  4314. class TimescaleButton3: RscButton_Base {
  4315. idc = -1; text = "1h = 1 Day";
  4316. x = 0.58; y = 0.52; w = 0.15; h = 0.04;
  4317. action = "[24] remoteExecCall ['fnc_server_setTimescale', 2];";
  4318. };
  4319.  
  4320. // --- TEAM POINTS (SP ONLY) ---
  4321. class TeamPointsTitle: RscText_Base {
  4322. idc = 9410;
  4323. text = "Team Points (SP Only):";
  4324. x = 0.26; y = 0.58; w = 0.3; h = 0.04;
  4325. sizeEx = 0.03;
  4326. };
  4327. class AddBluforPoints: RscButton_Base {
  4328. idc = 9411; text = "+100 BLUFOR";
  4329. x = 0.26; y = 0.62; w = 0.23; h = 0.04;
  4330. action = "['BLUFOR', 100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
  4331. };
  4332. class RemBluforPoints: RscButton_Base {
  4333. idc = 9412; text = "-100 BLUFOR";
  4334. x = 0.26; y = 0.67; w = 0.23; h = 0.04;
  4335. action = "['BLUFOR', -100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
  4336. };
  4337. class AddOpforPoints: RscButton_Base {
  4338. idc = 9413; text = "+100 OPFOR";
  4339. x = 0.51; y = 0.62; w = 0.23; h = 0.04;
  4340. action = "['OPFOR', 100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
  4341. };
  4342. class RemOpforPoints: RscButton_Base {
  4343. idc = 9414; text = "-100 OPFOR";
  4344. x = 0.51; y = 0.67; w = 0.23; h = 0.04;
  4345. action = "['OPFOR', -100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
  4346. };
  4347.  
  4348. // --- PERSONAL POINTS (SP ONLY) ---
  4349. class PersonalPointsTitle: RscText_Base {
  4350. idc = 9416;
  4351. text = "Personal Points (SP Only):";
  4352. x = 0.26; y = 0.73; w = 0.3; h = 0.04;
  4353. sizeEx = 0.03;
  4354. };
  4355. class AddPersonalPoints: RscButton_Base {
  4356. idc = 9415;
  4357. text = "+100 Personal Points";
  4358. x = 0.26; y = 0.77; w = 0.48; h = 0.04;
  4359. action = "[player, 100] remoteExecCall ['fnc_server_addPersonalPoints', 2];";
  4360. };
  4361.  
  4362. // --- CLOSE BUTTON ---
  4363. class CloseButton: RscButton_Base {
  4364. idc = -1;
  4365. text = "Close";
  4366. x = 0.4; y = 0.83; w = 0.2; h = 0.04;
  4367. action = "closeDialog 0;";
  4368. };
  4369. };
  4370. };
  4371.  
  4372. ==================== END OF: description.ext ====================
  4373.  
  4374.  
  4375.  
  4376. ==================== START OF: highcommand.sqf ====================
  4377.  
  4378. // highcommand.sqf
  4379. // High Command AI logic for Arma 3 mission
  4380.  
  4381. // Initialize global variables
  4382. if (isNil "BLUFOR_STRATEGY") then { BLUFOR_STRATEGY = "SEARCH"; };
  4383. if (isNil "OPFOR_STRATEGY") then { OPFOR_STRATEGY = "SEARCH"; };
  4384. if (isNil "BLUFOR_LAST_DECISION") then { BLUFOR_LAST_DECISION = time; };
  4385. if (isNil "OPFOR_LAST_DECISION") then { OPFOR_LAST_DECISION = time; };
  4386.  
  4387. if (isNil "BLUFOR_LAST_FLARE") then { BLUFOR_LAST_FLARE = time - 3600; };
  4388. if (isNil "OPFOR_LAST_FLARE") then { OPFOR_LAST_FLARE = time - 3600; };
  4389. if (isNil "BLUFOR_LAST_SMOKE") then { BLUFOR_LAST_SMOKE = time - 3600; };
  4390. if (isNil "OPFOR_LAST_SMOKE") then { OPFOR_LAST_SMOKE = time - 3600; };
  4391. if (isNil "BLUFOR_LAST_DRONE") then { BLUFOR_LAST_DRONE = time - 3600; };
  4392. if (isNil "OPFOR_LAST_DRONE") then { OPFOR_LAST_DRONE = time - 3600; };
  4393. if (isNil "BLUFOR_LAST_ARTILLERY") then { BLUFOR_LAST_ARTILLERY = time - 3600; };
  4394. if (isNil "OPFOR_LAST_ARTILLERY") then { OPFOR_LAST_ARTILLERY = time - 3600; };
  4395.  
  4396. HC_SUPPORT_COST = 0;
  4397.  
  4398. if (isNil "BLUFOR_COMMANDER_PERSONALITY") then {
  4399. BLUFOR_COMMANDER_PERSONALITY = selectRandom HC_COMMANDER_PERSONALITIES;
  4400. };
  4401. if (isNil "OPFOR_COMMANDER_PERSONALITY") then {
  4402. OPFOR_COMMANDER_PERSONALITY = selectRandom HC_COMMANDER_PERSONALITIES;
  4403. };
  4404.  
  4405. if (isNil "BLUFOR_INTEL") then { BLUFOR_INTEL = createHashMap; };
  4406. if (isNil "OPFOR_INTEL") then { OPFOR_INTEL = createHashMap; };
  4407. if (isNil "BLUFOR_SEARCH_AREAS") then { BLUFOR_SEARCH_AREAS = []; };
  4408. if (isNil "OPFOR_SEARCH_AREAS") then { OPFOR_SEARCH_AREAS = []; };
  4409.  
  4410. if (isNil "BLUFOR_TACTICAL_STATE") then { BLUFOR_TACTICAL_STATE = createHashMap; };
  4411. if (isNil "OPFOR_TACTICAL_STATE") then { OPFOR_TACTICAL_STATE = createHashMap; };
  4412. if (isNil "BLUFOR_LAST_SUPPORT_REQUEST") then { BLUFOR_LAST_SUPPORT_REQUEST = 0; };
  4413. if (isNil "OPFOR_LAST_SUPPORT_REQUEST") then { OPFOR_LAST_SUPPORT_REQUEST = 0; };
  4414.  
  4415. if (isNil "HC_Active_Groups") then { HC_Active_Groups = createHashMap; };
  4416.  
  4417. // ==================== INTEL OPTIMIZATION START ====================
  4418. // NEW: Lightweight queues for recent tactical reports to avoid scanning the entire intel database.
  4419. if (isNil "BLUFOR_TACTICAL_REPORTS") then { BLUFOR_TACTICAL_REPORTS = []; };
  4420. if (isNil "OPFOR_TACTICAL_REPORTS") then { OPFOR_TACTICAL_REPORTS = []; };
  4421. HC_REPORTS_MAX_SIZE = 100; // Max size for the tactical report queue.
  4422. // ===================== INTEL OPTIMIZATION END =====================
  4423.  
  4424. // REVISED FUNCTION: Creates a temporary map marker that fades out over 60 seconds.
  4425. fnc_HC_createSupportMarker = {
  4426. params ["_side", "_pos", "_type", "_text"];
  4427. if (!isServer) exitWith {};
  4428.  
  4429. private _markerName = format ["support_marker_%1_%2", round(random 99999), time];
  4430. private _markerColor = if (_side == west) then { "ColorBlue" } else { "ColorRed" };
  4431.  
  4432. // Create marker only for the appropriate side's players
  4433. [_markerName, _pos, _type, _markerColor, _text] remoteExec ["fnc_createLocalSupportMarker", _side, true];
  4434.  
  4435. // Server-side cleanup spawn
  4436. [_markerName, _side] spawn {
  4437. params ["_marker", "_side"];
  4438. sleep 60;
  4439. [_marker] remoteExec ["deleteMarkerLocal", _side, true];
  4440. };
  4441. };
  4442.  
  4443. fnc_HC_assignGroupWithSpacing = {
  4444. params ["_groups", "_role", "_centerPos", "_ownBase", "_enemyBase"];
  4445.  
  4446. {
  4447. private _group = _x;
  4448. private _targetPos = _centerPos;
  4449.  
  4450. // Apply aggressive spacing for the first 15 minutes to ensure varied deployment
  4451. // After 15 minutes, gradually reduce spacing over the next 10 minutes
  4452. if (time < 900) then {
  4453. private _spacingMultiplier = 1.0;
  4454.  
  4455. // Full spacing for first 15 minutes
  4456. if (time < 900) then {
  4457. _spacingMultiplier = 1.0;
  4458. };
  4459.  
  4460. // Gradually reduce spacing between 15-25 minutes
  4461. if (time >= 900 && time < 1500) then {
  4462. _spacingMultiplier = 1.0 - ((time - 900) / 600); // Gradually reduce from 1.0 to 0
  4463. };
  4464.  
  4465. // Apply the spacing with current multiplier
  4466. if (_spacingMultiplier > 0.1) then {
  4467. // Much larger spread - 100-500m radius with full randomization
  4468. private _spreadDistance = (100 + (random 400)) * _spacingMultiplier;
  4469. private _spreadAngle = random 360;
  4470. _targetPos = _centerPos getPos [_spreadDistance, _spreadAngle];
  4471. };
  4472. };
  4473.  
  4474. // Execute the role with the (potentially offset) target position.
  4475. [_group, _role, _targetPos, _ownBase, _enemyBase] call fnc_executeGroupRole;
  4476.  
  4477. } forEach _groups;
  4478. };
  4479.  
  4480. fnc_createLocalSupportMarker = {
  4481. params ["_markerName", "_pos", "_type", "_markerColor", "_text"];
  4482. if (!hasInterface) exitWith {};
  4483.  
  4484. createMarkerLocal [_markerName, _pos];
  4485. _markerName setMarkerShapeLocal "ICON";
  4486. _markerName setMarkerTypeLocal _type;
  4487. _markerName setMarkerColorLocal _markerColor;
  4488. _markerName setMarkerTextLocal _text;
  4489. _markerName setMarkerSizeLocal [0.8, 0.8];
  4490.  
  4491. // Local fade effect
  4492. [_markerName] spawn {
  4493. params ["_marker"];
  4494. private _lifetime = 60;
  4495. for "_i" from 0 to (_lifetime - 1) do {
  4496. private _alpha = 1 - (_i / _lifetime);
  4497. _marker setMarkerAlphaLocal _alpha;
  4498. sleep 1;
  4499. };
  4500. deleteMarkerLocal _marker;
  4501. };
  4502. };
  4503.  
  4504. // Check if support is available
  4505. fnc_isSupportAvailable = {
  4506. params ["_side", "_supportType"];
  4507. private _lastSupport = 0;
  4508. private _cooldown = 9999;
  4509.  
  4510. switch (_supportType) do {
  4511. case "FLARE": {
  4512. _lastSupport = if (_side == west) then {BLUFOR_LAST_FLARE} else {OPFOR_LAST_FLARE};
  4513. _cooldown = HC_FLARE_COOLDOWN;
  4514. };
  4515. case "SMOKE": {
  4516. _lastSupport = if (_side == west) then {BLUFOR_LAST_SMOKE} else {OPFOR_LAST_SMOKE};
  4517. _cooldown = HC_SMOKE_COOLDOWN;
  4518. };
  4519. case "DRONE": {
  4520. _lastSupport = if (_side == west) then {BLUFOR_LAST_DRONE} else {OPFOR_LAST_DRONE};
  4521. _cooldown = HC_DRONE_COOLDOWN;
  4522. };
  4523. case "ARTILLERY": {
  4524. _lastSupport = if (_side == west) then {BLUFOR_LAST_ARTILLERY} else {OPFOR_LAST_ARTILLERY};
  4525. _cooldown = HC_ARTILLERY_COOLDOWN;
  4526. };
  4527. };
  4528.  
  4529. (time - _lastSupport >= _cooldown)
  4530. };
  4531.  
  4532. // Check if support can be afforded
  4533. fnc_canAffordSupport = {
  4534. params ["_side"];
  4535. private _points = if (_side == west) then {missionNamespace getVariable ["BLUFOR_POINTS", 0]} else {missionNamespace getVariable ["OPFOR_POINTS", 0]};
  4536. (_points >= HC_SUPPORT_COST)
  4537. };
  4538.  
  4539. // Deduct support cost
  4540. fnc_deductSupportCost = {
  4541. params ["_side"];
  4542. if (_side == west) then {
  4543. BLUFOR_POINTS = (missionNamespace getVariable ["BLUFOR_POINTS", 0]) - HC_SUPPORT_COST;
  4544. publicVariable "BLUFOR_POINTS";
  4545. } else {
  4546. OPFOR_POINTS = (missionNamespace getVariable ["OPFOR_POINTS", 0]) - HC_SUPPORT_COST;
  4547. publicVariable "OPFOR_POINTS";
  4548. };
  4549. };
  4550.  
  4551. // Call flare support
  4552. fnc_callFlareSupport = {
  4553. params ["_side", "_targetPos"];
  4554. if (!([_side, "FLARE"] call fnc_isSupportAvailable)) exitWith { false };
  4555. if (!([_side] call fnc_canAffordSupport)) exitWith { false };
  4556.  
  4557. [_side] call fnc_deductSupportCost;
  4558. private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
  4559. systemChat format ["GLOBAL ALERT: %1 has called for Illumination support.", _sideName];
  4560. [
  4561. _side,
  4562. "Support Command",
  4563. format["Illumination mission requested over grid %1.", mapGridPosition _targetPos]
  4564. ] remoteExecCall ["sideChat", _side];
  4565.  
  4566. if (_side == west) then {
  4567. BLUFOR_LAST_FLARE = time;
  4568. } else {
  4569. OPFOR_LAST_FLARE = time;
  4570. };
  4571.  
  4572. [_side, _targetPos, "mil_dot", "Flare Illumination"] call fnc_HC_createSupportMarker;
  4573.  
  4574. [_targetPos] spawn {
  4575. params ["_pos"];
  4576. sleep 3; // Small delay for realism
  4577. private _flarePos = [(_pos select 0), (_pos select 1), 250];
  4578. private _flareShell = "F_40mm_White" createVehicle _flarePos;
  4579. _flareShell setPosASL _flarePos;
  4580. _flareShell setVelocity [0, 0, -5];
  4581. };
  4582.  
  4583. true
  4584. };
  4585.  
  4586. // Call smoke support
  4587. fnc_callSmokeSupport = {
  4588. params ["_side", "_targetPos"];
  4589. if (!([_side, "SMOKE"] call fnc_isSupportAvailable)) exitWith { false };
  4590. if (!([_side] call fnc_canAffordSupport)) exitWith { false };
  4591.  
  4592. [_side] call fnc_deductSupportCost;
  4593. private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
  4594. systemChat format ["GLOBAL ALERT: %1 has called for Smoke Screen support.", _sideName];
  4595. [
  4596. _side,
  4597. "Support Command",
  4598. format["Smoke cover requested for friendlies at grid %1.", mapGridPosition _targetPos]
  4599. ] remoteExecCall ["sideChat", _side];
  4600.  
  4601. if (_side == west) then {
  4602. BLUFOR_LAST_SMOKE = time;
  4603. } else {
  4604. OPFOR_LAST_SMOKE = time;
  4605. };
  4606.  
  4607. [_side, _targetPos, "mil_dot", "Smoke Cover"] call fnc_HC_createSupportMarker;
  4608.  
  4609. [_targetPos, _side] spawn {
  4610. params ["_pos", "_side"];
  4611. private _smokeRadius = 40;
  4612.  
  4613. [
  4614. _side,
  4615. "Support Command",
  4616. "Smoke cover inbound. ETA 10 seconds."
  4617. ] remoteExecCall ["sideChat", _side];
  4618.  
  4619. sleep 10;
  4620.  
  4621. [
  4622. _side,
  4623. "Support Command",
  4624. "SPLASH! Smoke rounds impacting."
  4625. ] remoteExecCall ["sideChat", _side];
  4626.  
  4627. for "_i" from 1 to HC_SMOKE_ROUNDS do {
  4628. private _roundPos = _pos vectorAdd [
  4629. (random _smokeRadius) - (_smokeRadius / 2),
  4630. (random _smokeRadius) - (_smokeRadius / 2),
  4631. 0
  4632. ];
  4633. private _smoke = createVehicle ["SmokeShellArty", _roundPos, [], 0, "CAN_COLLIDE"];
  4634. _smoke setPos _roundPos;
  4635. sleep (random 2);
  4636. };
  4637.  
  4638. [
  4639. _side,
  4640. "Support Command",
  4641. "Smoke screen deployed for friendly cover."
  4642. ] remoteExecCall ["sideChat", _side];
  4643. };
  4644.  
  4645. true
  4646. };
  4647.  
  4648. // Call recon drone support
  4649. fnc_callReconDroneSupport = {
  4650. params ["_side", "_targetPos"];
  4651. if (time < 600) exitWith { false };
  4652. if (!([_side, "DRONE"] call fnc_isSupportAvailable)) exitWith { false };
  4653. if (!([_side] call fnc_canAffordSupport)) exitWith { false };
  4654.  
  4655. // Check if target position is inside the play area
  4656. private _playAreaCenter = missionNamespace getVariable ["PLAY_AREA_CENTER", [0,0,0]];
  4657. private _playAreaRadius = missionNamespace getVariable ["PLAY_AREA_RADIUS", 0];
  4658. if (_playAreaRadius > 0 && (_targetPos distance2D _playAreaCenter > _playAreaRadius)) exitWith { false }; // Don't call drones outside the battle zone
  4659.  
  4660. [_side] call fnc_deductSupportCost;
  4661. private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
  4662. systemChat format ["GLOBAL ALERT: %1 has deployed Recon Drone support.", _sideName];
  4663. [
  4664. _side,
  4665. "Support Command",
  4666. format["Recon Drone en route to grid %1.", mapGridPosition _targetPos]
  4667. ] remoteExecCall ["sideChat", _side];
  4668.  
  4669. if (_side == west) then {
  4670. BLUFOR_LAST_DRONE = time;
  4671. } else {
  4672. OPFOR_LAST_DRONE = time;
  4673. };
  4674.  
  4675. private _basePos = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  4676. private _droneClass = if (_side == west) then {"B_UAV_01_F"} else {"O_UAV_01_F"};
  4677. private _enemySide = if (_side == west) then {east} else {west};
  4678.  
  4679. private _droneSpawnPos = _basePos vectorAdd [0, 0, 500];
  4680. private _drone = createVehicle [_droneClass, _droneSpawnPos, [], 0, "FLY"];
  4681. createVehicleCrew _drone;
  4682. _drone flyInHeight 300;
  4683.  
  4684. private _droneGroup = group _drone;
  4685. private _wpToTarget = _droneGroup addWaypoint [_targetPos, 0];
  4686. _wpToTarget setWaypointType "MOVE";
  4687. _wpToTarget setWaypointBehaviour "SAFE";
  4688. _wpToTarget setWaypointSpeed "NORMAL";
  4689.  
  4690. private _loiterWp = _droneGroup addWaypoint [_targetPos, 0];
  4691. _loiterWp setWaypointType "LOITER";
  4692. _loiterWp setWaypointLoiterType "CIRCLE_L";
  4693. _loiterWp setWaypointLoiterRadius 500;
  4694. _loiterWp setWaypointBehaviour "SAFE";
  4695. _loiterWp setWaypointSpeed "NORMAL";
  4696.  
  4697. // MODIFIED: The drone will now loiter and spot indefinitely until destroyed.
  4698. [_drone, _targetPos, _enemySide] spawn {
  4699. params ["_drone", "_targetPos", "_enemySide"];
  4700. while {!isNull _drone && alive _drone} do {
  4701. private _nearEnemies = _targetPos nearEntities [["CAManBase", "LandVehicle", "Air"], 1000] select {side _x == _enemySide && alive _x};
  4702. { _drone reveal [_x, 4]; } forEach _nearEnemies;
  4703. sleep 5; // Check for enemies every 5 seconds
  4704. };
  4705. };
  4706.  
  4707. true
  4708. };
  4709.  
  4710. // Call artillery support
  4711. fnc_callArtillerySupport = {
  4712. params ["_side", "_targetPos"];
  4713. if (!([_side, "ARTILLERY"] call fnc_isSupportAvailable)) exitWith { false };
  4714. if (!([_side] call fnc_canAffordSupport)) exitWith { false };
  4715.  
  4716. // Check if target is inside enemy base area
  4717. private _enemyBaseMarker = if (_side == west) then {"opfor_base_area"} else {"blufor_base_area"};
  4718. private _enemyBasePos = markerPos _enemyBaseMarker;
  4719. private _enemyBaseSize = (markerSize _enemyBaseMarker) select 0;
  4720.  
  4721. if (_targetPos distance2D _enemyBasePos < _enemyBaseSize) exitWith {
  4722. false // Don't allow mortars inside enemy base area
  4723. };
  4724.  
  4725. // Check for friendlies in target area
  4726. private _nearbyFriendlies = _targetPos nearEntities [["CAManBase", "LandVehicle"], 50];
  4727. private _friendliesInArea = _nearbyFriendlies select {side _x == _side && alive _x};
  4728.  
  4729. if (count _friendliesInArea > 0) exitWith {
  4730. false // Don't allow mortars on friendlies
  4731. };
  4732.  
  4733. [_side] call fnc_deductSupportCost;
  4734. private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
  4735. systemChat format ["GLOBAL ALERT: %1 has called for Mortar support.", _sideName];
  4736. [
  4737. _side,
  4738. "Support Command",
  4739. format["Mortar fire mission requested on grid %1.", mapGridPosition _targetPos]
  4740. ] remoteExecCall ["sideChat", _side];
  4741.  
  4742. if (_side == west) then {
  4743. BLUFOR_LAST_ARTILLERY = time;
  4744. } else {
  4745. OPFOR_LAST_ARTILLERY = time;
  4746. };
  4747.  
  4748. [_side, _targetPos, "mil_destroy", "Mortar Strike"] call fnc_HC_createSupportMarker;
  4749.  
  4750. [_targetPos, _side] spawn {
  4751. params ["_pos", "_side"];
  4752.  
  4753. // Spawn red smoke first
  4754. [
  4755. _side,
  4756. "Support Command",
  4757. "Marking target with red smoke. Mortars inbound."
  4758. ] remoteExecCall ["sideChat", _side];
  4759.  
  4760. private _smoke = createVehicle ["SmokeShellRed", _pos, [], 0, "CAN_COLLIDE"];
  4761. _smoke setPos _pos;
  4762.  
  4763. // Wait 10 seconds after smoke
  4764. sleep 10;
  4765.  
  4766. [
  4767. _side,
  4768. "Support Command",
  4769. "SPLASH! Mortar rounds impacting."
  4770. ] remoteExecCall ["sideChat", _side];
  4771.  
  4772. // Fire 10 mortar rounds over 30 seconds in a 20-40 meter area
  4773. for "_i" from 1 to 10 do {
  4774. private _impactRadius = 20 + (random 20); // 20-40 meter radius
  4775. private _roundPos = _pos vectorAdd [
  4776. (random (_impactRadius * 2)) - _impactRadius,
  4777. (random (_impactRadius * 2)) - _impactRadius,
  4778. 0
  4779. ];
  4780.  
  4781. // Create mortar shell
  4782. private _shell = "Sh_82mm_AMOS" createVehicle [_roundPos select 0, _roundPos select 1, 200];
  4783. _shell setVectorDir [0, 0, -1];
  4784. _shell setVelocity [0, 0, -100];
  4785.  
  4786. sleep 3; // 30 seconds / 10 rounds = 3 seconds between rounds
  4787. };
  4788.  
  4789. [
  4790. _side,
  4791. "Support Command",
  4792. "Mortar mission complete. BDA to follow."
  4793. ] remoteExecCall ["sideChat", _side];
  4794. };
  4795.  
  4796. true
  4797. };
  4798.  
  4799. // ==================== INTEL OPTIMIZATION START ====================
  4800. // MODIFIED: This function now reads from the new, lightweight tactical report queue.
  4801. fnc_evaluateSupportNeed = {
  4802. params ["_side"];
  4803. private _supportToCall = "";
  4804. if (!([_side] call fnc_canAffordSupport)) exitWith {_supportToCall};
  4805.  
  4806. // OPTIMIZATION: Read from the fast report queue instead of the state machine.
  4807. private _recentReports = if (_side == west) then {BLUFOR_TACTICAL_REPORTS} else {OPFOR_TACTICAL_REPORTS};
  4808.  
  4809. // Clean out stale reports from the queue (older than 5 minutes)
  4810. for "_i" from (count _recentReports - 1) to 0 step -1 do {
  4811. private _report = _recentReports select _i;
  4812. if (time - (_report select 0) > 300) then {
  4813. _recentReports deleteAt _i;
  4814. };
  4815. };
  4816.  
  4817. private _heavyContactReports = count _recentReports;
  4818. private _recentCombatAreas = [];
  4819. { _recentCombatAreas pushBackUnique (_x select 2); } forEach _recentReports;
  4820.  
  4821. // Check for friendly casualties to call smoke
  4822. private _friendlyGroups = allGroups select {side _x == _side && count (units _x) > 0};
  4823. private _casualtyAreas = [];
  4824. {
  4825. private _group = _x;
  4826. private _casualties = {damage _x > 0.3} count (units _group);
  4827. if (_casualties >= 2 && behaviour leader _group == "COMBAT") then {
  4828. _casualtyAreas pushBack (getPos leader _group);
  4829. };
  4830. } forEach _friendlyGroups;
  4831.  
  4832. // Priority 1: Drone for reconnaissance
  4833. if ([_side, "DRONE"] call fnc_isSupportAvailable) then {
  4834. _supportToCall = "DRONE";
  4835. };
  4836.  
  4837. // Priority 2: Artillery on any enemy concentration
  4838. if (_supportToCall == "" && _heavyContactReports > 0) then {
  4839. if ([_side, "ARTILLERY"] call fnc_isSupportAvailable) then {
  4840. _supportToCall = "ARTILLERY";
  4841. };
  4842. };
  4843.  
  4844. // Priority 3: Smoke for friendly casualties
  4845. if (_supportToCall == "") then {
  4846. if (count _casualtyAreas > 0 && ([_side, "SMOKE"] call fnc_isSupportAvailable)) then {
  4847. _supportToCall = "SMOKE";
  4848. };
  4849. };
  4850.  
  4851. // Priority 4: Flares for night operations
  4852. if (_supportToCall == "") then {
  4853. if (sunOrMoon < 0.1 && ([_side, "FLARE"] call fnc_isSupportAvailable)) then {
  4854. if (count _recentCombatAreas > 0) then {
  4855. _supportToCall = "FLARE";
  4856. };
  4857. };
  4858. };
  4859.  
  4860. _supportToCall
  4861. };
  4862. // ===================== INTEL OPTIMIZATION END =====================
  4863.  
  4864. // Cluster positions for analysis
  4865. fnc_clusterPositions = {
  4866. params ["_positions", "_clusterRadius"];
  4867. private _clusters = [];
  4868. private _unclustered = +_positions;
  4869.  
  4870. while {count _unclustered > 0} do {
  4871. private _seed = _unclustered select 0;
  4872. private _cluster = [_seed];
  4873. _unclustered = _unclustered - [_seed];
  4874.  
  4875. private _toCheck = +_cluster;
  4876. while {count _toCheck > 0} do {
  4877. private _current = _toCheck select 0;
  4878. _toCheck = _toCheck - [_current];
  4879. private _nearby = _unclustered select {_x distance _current < _clusterRadius};
  4880. _cluster append _nearby;
  4881. _unclustered = _unclustered - _nearby;
  4882. _toCheck append _nearby;
  4883. };
  4884. _clusters pushBack _cluster;
  4885. };
  4886.  
  4887. _clusters
  4888. };
  4889.  
  4890. // Evaluate terrain cover quality at a position
  4891. fnc_evaluateTerrainCover = {
  4892. params ["_pos"];
  4893.  
  4894. private _coverScore = 0;
  4895. private _coverType = "NONE";
  4896.  
  4897. // Check for buildings (best hard cover)
  4898. private _nearBuildings = nearestObjects [_pos, ["House", "Building"], 30];
  4899. if (count _nearBuildings > 0) then {
  4900. _coverScore = _coverScore + 50;
  4901. _coverType = "BUILDING";
  4902. };
  4903.  
  4904. // Check for walls and fortifications
  4905. private _nearWalls = nearestTerrainObjects [_pos, ["WALL", "FENCE"], 20];
  4906. if (count _nearWalls > 0) then {
  4907. _coverScore = _coverScore + 30;
  4908. if (_coverType == "NONE") then { _coverType = "WALL"; };
  4909. };
  4910.  
  4911. // Check for natural cover (trees, rocks, bushes)
  4912. private _nearTrees = nearestTerrainObjects [_pos, ["TREE", "SMALL TREE"], 15];
  4913. private _nearRocks = nearestTerrainObjects [_pos, ["ROCK", "ROCKS"], 15];
  4914. private _nearBushes = nearestTerrainObjects [_pos, ["BUSH"], 10];
  4915.  
  4916. private _naturalCoverCount = (count _nearTrees) + (count _nearRocks) + (count _nearBushes);
  4917. if (_naturalCoverCount > 0) then {
  4918. _coverScore = _coverScore + (_naturalCoverCount * 5);
  4919. if (_coverType == "NONE") then { _coverType = "FOREST"; };
  4920. };
  4921.  
  4922. // Check for defilade (terrain masking)
  4923. private _terrainHeight = getTerrainHeightASL _pos;
  4924. private _surroundingHeights = [];
  4925.  
  4926. for "_angle" from 0 to 315 step 45 do {
  4927. private _checkPos = _pos getPos [30, _angle];
  4928. _surroundingHeights pushBack (getTerrainHeightASL _checkPos);
  4929. };
  4930.  
  4931. private _avgSurroundingHeight = 0;
  4932. {_avgSurroundingHeight = _avgSurroundingHeight + _x;} forEach _surroundingHeights;
  4933. _avgSurroundingHeight = _avgSurroundingHeight / count _surroundingHeights;
  4934.  
  4935. // If position is lower than surroundings (in defilade)
  4936. if (_terrainHeight < (_avgSurroundingHeight - 2)) then {
  4937. _coverScore = _coverScore + 25;
  4938. if (_coverType == "NONE") then { _coverType = "DEFILADE"; };
  4939. };
  4940.  
  4941. // Check for forest canopy using selectBestPlaces
  4942. private _forestDensity = selectBestPlaces [_pos, 50, "forest", 1, 1];
  4943. if (count _forestDensity > 0) then {
  4944. private _density = (_forestDensity select 0) select 1;
  4945. _coverScore = _coverScore + (_density * 20);
  4946. if (_coverType == "NONE") then { _coverType = "FOREST"; };
  4947. };
  4948.  
  4949. // Return cover assessment
  4950. private _assessment = createHashMap;
  4951. _assessment set ["score", _coverScore];
  4952. _assessment set ["type", _coverType];
  4953. _assessment set ["hasCover", _coverScore > 20];
  4954.  
  4955. _assessment
  4956. };
  4957.  
  4958. // Assess threat environment between two positions
  4959. fnc_assessThreatEnvironment = {
  4960. params ["_startPos", "_endPos", "_side"];
  4961.  
  4962. private _enemySide = if (_side == west) then {east} else {west};
  4963. private _midPoint = [
  4964. ((_startPos select 0) + (_endPos select 0)) / 2,
  4965. ((_startPos select 1) + (_endPos select 1)) / 2,
  4966. 0
  4967. ];
  4968.  
  4969. // Check for enemy presence along route
  4970. private _routeLength = _startPos distance2D _endPos;
  4971. private _searchRadius = (_routeLength / 2) max 300;
  4972.  
  4973. private _nearbyEnemies = _midPoint nearEntities [["CAManBase", "LandVehicle", "Air"], _searchRadius];
  4974. private _enemyCount = {side _x == _enemySide && alive _x} count _nearbyEnemies;
  4975.  
  4976. // Check intel for known enemy positions
  4977. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  4978. private _recentEnemyReports = 0;
  4979.  
  4980. {
  4981. private _report = _y;
  4982. private _age = time - (_report get "time");
  4983. if (_age < 60) then {
  4984. private _reportPos = _report get "position";
  4985. if ((_reportPos distance2D _midPoint) < _searchRadius) then {
  4986. _recentEnemyReports = _recentEnemyReports + 1;
  4987. };
  4988. };
  4989. } forEach _intel;
  4990.  
  4991. // Calculate exposure score
  4992. private _exposureScore = 0;
  4993.  
  4994. // Sample points along route to check cover
  4995. private _routeSamples = 5;
  4996. private _totalCover = 0;
  4997.  
  4998. for "_i" from 0 to (_routeSamples - 1) do {
  4999. private _fraction = _i / _routeSamples;
  5000. private _samplePos = [
  5001. (_startPos select 0) + ((_endPos select 0) - (_startPos select 0)) * _fraction,
  5002. (_startPos select 1) + ((_endPos select 1) - (_startPos select 1)) * _fraction,
  5003. 0
  5004. ];
  5005.  
  5006. private _coverData = [_samplePos] call fnc_evaluateTerrainCover;
  5007. _totalCover = _totalCover + (_coverData get "score");
  5008. };
  5009.  
  5010. private _avgCover = _totalCover / _routeSamples;
  5011.  
  5012. // High exposure if little cover
  5013. if (_avgCover < 20) then {
  5014. _exposureScore = _exposureScore + 40;
  5015. } else {
  5016. if (_avgCover < 50) then {
  5017. _exposureScore = _exposureScore + 20;
  5018. };
  5019. };
  5020.  
  5021. // Threat level calculation
  5022. private _threatLevel = "LOW";
  5023. private _threatScore = _enemyCount + _recentEnemyReports + (_exposureScore / 10);
  5024.  
  5025. if (_threatScore > 15) then {
  5026. _threatLevel = "EXTREME";
  5027. } else {
  5028. if (_threatScore > 10) then {
  5029. _threatLevel = "HIGH";
  5030. } else {
  5031. if (_threatScore > 5) then {
  5032. _threatLevel = "MEDIUM";
  5033. };
  5034. };
  5035. };
  5036.  
  5037. // Return threat assessment
  5038. private _assessment = createHashMap;
  5039. _assessment set ["threatLevel", _threatLevel];
  5040. _assessment set ["threatScore", _threatScore];
  5041. _assessment set ["enemyCount", _enemyCount];
  5042. _assessment set ["exposureScore", _exposureScore];
  5043. _assessment set ["avgCover", _avgCover];
  5044.  
  5045. _assessment
  5046. };
  5047.  
  5048. // Select waypoints that maximize cover along a route
  5049. fnc_selectCoveredWaypoints = {
  5050. params ["_startPos", "_targetPos", "_groupType"];
  5051.  
  5052. private _waypoints = [];
  5053. private _distance = _startPos distance2D _targetPos;
  5054.  
  5055. // For short distances, go direct
  5056. if (_distance < 300) exitWith {
  5057. [_targetPos]
  5058. };
  5059.  
  5060. // Calculate number of waypoints based on distance
  5061. private _waypointCount = (floor (_distance / 400)) max 2;
  5062. _waypointCount = _waypointCount min 6;
  5063.  
  5064. // Generate candidate waypoints along corridor
  5065. private _dirToTarget = _startPos getDir _targetPos;
  5066. private _segmentLength = _distance / (_waypointCount + 1);
  5067.  
  5068. for "_i" from 1 to _waypointCount do {
  5069. private _baseDistance = _i * _segmentLength;
  5070. private _basePos = _startPos getPos [_baseDistance, _dirToTarget];
  5071.  
  5072. // Search for covered positions in area around base position
  5073. private _searchRadius = 200;
  5074. private _bestPos = _basePos;
  5075. private _bestCoverScore = 0;
  5076.  
  5077. // Sample 8 positions in a circle
  5078. for "_angle" from 0 to 315 step 45 do {
  5079. private _testPos = _basePos getPos [random _searchRadius, _angle];
  5080.  
  5081. // Validate position
  5082. if (!surfaceIsWater _testPos) then {
  5083. // Check cover at this position
  5084. private _coverData = [_testPos] call fnc_evaluateTerrainCover;
  5085. private _coverScore = _coverData get "score";
  5086.  
  5087. // Prefer positions with good cover
  5088. if (_coverScore > _bestCoverScore) then {
  5089. _bestCoverScore = _coverScore;
  5090. _bestPos = _testPos;
  5091. };
  5092. };
  5093. };
  5094.  
  5095. // Use BIS_fnc_findSafePos to validate final position
  5096. private _safePos = [_bestPos, 0, 50, 5, 0, 0.3, 0] call BIS_fnc_findSafePos;
  5097. if (count _safePos > 0 && !surfaceIsWater _safePos) then {
  5098. _waypoints pushBack _safePos;
  5099. } else {
  5100. _waypoints pushBack _bestPos;
  5101. };
  5102. };
  5103.  
  5104. _waypoints
  5105. };
  5106.  
  5107. // Determine appropriate movement behavior based on terrain and threat
  5108. fnc_determineMovementBehavior = {
  5109. params ["_group", "_currentPos", "_targetPos", "_side"];
  5110.  
  5111. // Assess threat environment
  5112. private _threatData = [_currentPos, _targetPos, _side] call fnc_assessThreatEnvironment;
  5113. private _threatLevel = _threatData get "threatLevel";
  5114. private _avgCover = _threatData get "avgCover";
  5115.  
  5116. // Assess cover at current position
  5117. private _currentCover = [_currentPos] call fnc_evaluateTerrainCover;
  5118. private _hasCover = _currentCover get "hasCover";
  5119.  
  5120. // Determine behavior
  5121. private _behavior = "AWARE";
  5122. private _speed = "NORMAL";
  5123. private _formation = "LINE";
  5124. private _combatMode = "YELLOW";
  5125.  
  5126. // Classify group type
  5127. private _groupType = [_group] call fnc_classifyGroup;
  5128.  
  5129. switch (_threatLevel) do {
  5130. case "EXTREME": {
  5131. if (_avgCover > 40) then {
  5132. // Good cover available - use stealth
  5133. _behavior = "STEALTH";
  5134. _speed = "LIMITED";
  5135. _formation = "FILE";
  5136. _combatMode = "GREEN";
  5137. } else {
  5138. // Poor cover - aggressive movement
  5139. _behavior = "COMBAT";
  5140. _speed = "FULL";
  5141. _formation = "LINE";
  5142. _combatMode = "RED";
  5143. };
  5144. };
  5145.  
  5146. case "HIGH": {
  5147. if (_avgCover > 30) then {
  5148. _behavior = "AWARE";
  5149. _speed = "LIMITED";
  5150. _formation = "STAG COLUMN";
  5151. _combatMode = "YELLOW";
  5152. } else {
  5153. _behavior = "COMBAT";
  5154. _speed = "NORMAL";
  5155. _formation = "LINE";
  5156. _combatMode = "RED";
  5157. };
  5158. };
  5159.  
  5160. case "MEDIUM": {
  5161. _behavior = "AWARE";
  5162. _speed = "NORMAL";
  5163. _formation = "COLUMN";
  5164. _combatMode = "YELLOW";
  5165. };
  5166.  
  5167. case "LOW": {
  5168. _behavior = "SAFE";
  5169. _speed = "NORMAL";
  5170. _formation = "COLUMN";
  5171. _combatMode = "YELLOW";
  5172. };
  5173. };
  5174.  
  5175. // Special considerations for group types
  5176. if (_groupType in ["SNIPER", "SPECOPS", "ELITE"]) then {
  5177. // Elite units prefer stealth when cover is available
  5178. if (_avgCover > 25) then {
  5179. _behavior = "STEALTH";
  5180. _speed = "LIMITED";
  5181. _formation = "FILE";
  5182. };
  5183. };
  5184.  
  5185. if (_groupType in ["ARMOR", "MECHANIZED"]) then {
  5186. // Vehicles are less affected by cover, more aggressive
  5187. _speed = "NORMAL";
  5188. if (_threatLevel in ["HIGH", "EXTREME"]) then {
  5189. _behavior = "COMBAT";
  5190. _combatMode = "RED";
  5191. };
  5192. };
  5193.  
  5194. // Return movement parameters
  5195. private _params = createHashMap;
  5196. _params set ["behavior", _behavior];
  5197. _params set ["speed", _speed];
  5198. _params set ["formation", _formation];
  5199. _params set ["combatMode", _combatMode];
  5200.  
  5201. _params
  5202. };
  5203.  
  5204. // Find tactical positions on the battlefield (high ground, flanking routes, defensive positions)
  5205. fnc_findTacticalPosition = {
  5206. params ["_centerPos", "_role", "_friendlyBase", "_enemyBase"];
  5207.  
  5208. private _position = [];
  5209. private _rawPosition = [];
  5210. private _playAreaCenter = missionNamespace getVariable ["PLAY_AREA_CENTER", [worldSize/2, worldSize/2, 0]];
  5211. private _playAreaRadius = missionNamespace getVariable ["PLAY_AREA_RADIUS", 5000];
  5212.  
  5213. switch (_role) do {
  5214. case "HIGH_GROUND": {
  5215. // Find elevated positions near the battle
  5216. private _bestHeight = -999;
  5217. for "_i" from 0 to 8 do {
  5218. private _testPos = _centerPos getPos [random 800, random 360];
  5219. private _height = getTerrainHeightASL _testPos;
  5220. // Check if position is within play area
  5221. if (_height > _bestHeight && !surfaceIsWater _testPos && (_testPos distance2D _playAreaCenter) < _playAreaRadius) then {
  5222. _bestHeight = _height;
  5223. _position = _testPos;
  5224. };
  5225. };
  5226. };
  5227. case "FLANK_LEFT": {
  5228. // Position to the left of the line between friendly and enemy base
  5229. private _dirToEnemy = _friendlyBase getDir _enemyBase;
  5230. private _flankDir = _dirToEnemy - 90;
  5231. private _distance = ((_friendlyBase distance _enemyBase) * 0.6) min (_playAreaRadius - 100);
  5232. _rawPosition = _centerPos getPos [_distance, _flankDir];
  5233. };
  5234. case "FLANK_RIGHT": {
  5235. // Position to the right of the line between friendly and enemy base
  5236. private _dirToEnemy = _friendlyBase getDir _enemyBase;
  5237. private _flankDir = _dirToEnemy + 90;
  5238. private _distance = ((_friendlyBase distance _enemyBase) * 0.6) min (_playAreaRadius - 100);
  5239. _rawPosition = _centerPos getPos [_distance, _flankDir];
  5240. };
  5241. case "DEFENSIVE": {
  5242. // Position between friendly base and battle center
  5243. private _dirToBase = _centerPos getDir _friendlyBase;
  5244. private _distance = ((_centerPos distance _friendlyBase) * 0.4) min (_playAreaRadius - 100);
  5245. _rawPosition = _centerPos getPos [_distance, _dirToBase];
  5246. };
  5247. case "FORWARD": {
  5248. // Position toward enemy base
  5249. private _dirToEnemy = _centerPos getDir _enemyBase;
  5250. private _distance = (400 + random 200) min (_playAreaRadius - 100);
  5251. _rawPosition = _centerPos getPos [_distance, _dirToEnemy];
  5252. };
  5253. case "SUPPORT": {
  5254. // Position behind the main battle line
  5255. private _dirToBase = _centerPos getDir _friendlyBase;
  5256. private _distance = (300 + random 200) min (_playAreaRadius - 100);
  5257. _rawPosition = _centerPos getPos [_distance, _dirToBase];
  5258. };
  5259. };
  5260.  
  5261. // MODIFIED: Centralized validation for all roles except HIGH_GROUND
  5262. if (count _rawPosition > 0) then {
  5263. _position = [_rawPosition, 50, 200, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
  5264. };
  5265.  
  5266. // Ensure position is valid and within play area
  5267. if (count _position == 0 || (_position distance2D _playAreaCenter) > _playAreaRadius) then {
  5268. _position = _centerPos;
  5269. };
  5270.  
  5271. _position
  5272. };
  5273.  
  5274. // Create a comprehensive battle plan based on available forces and intel
  5275. fnc_createBattlePlan = {
  5276. params ["_side", "_availableGroups", "_enemyBase", "_ownBase"];
  5277.  
  5278. private _battlePlan = createHashMap;
  5279.  
  5280. // Get intel to determine battle center
  5281. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  5282. private _enemyPositions = [];
  5283. {
  5284. private _report = _y;
  5285. private _age = time - (_report get "time");
  5286. if (_age < 120) then {
  5287. _enemyPositions pushBack (_report get "position");
  5288. };
  5289. } forEach _intel;
  5290.  
  5291. // Determine battle center
  5292. private _battleCenter = _enemyBase;
  5293. if (count _enemyPositions > 0) then {
  5294. private _avgX = 0; private _avgY = 0;
  5295. {_avgX = _avgX + (_x select 0); _avgY = _avgY + (_x select 1);} forEach _enemyPositions;
  5296. _battleCenter = [_avgX / count _enemyPositions, _avgY / count _enemyPositions, 0];
  5297. };
  5298.  
  5299. // OPTIMIZATION: Pre-allocate arrays for group classification
  5300. private _infantryGroups = [];
  5301. private _armorGroups = [];
  5302. private _mechanizedGroups = [];
  5303. private _airGroups = [];
  5304. private _specialGroups = [];
  5305.  
  5306. // OPTIMIZATION: Classify groups with caching
  5307. {
  5308. private _group = _x;
  5309.  
  5310. // Check if classification is cached and still valid
  5311. private _cachedType = _group getVariable ["classifiedType", ""];
  5312. private _cacheTime = _group getVariable ["classificationTime", 0];
  5313.  
  5314. // Cache is valid for 60 seconds
  5315. if (_cachedType == "" || (time - _cacheTime) > 60) then {
  5316. _cachedType = [_group] call fnc_classifyGroup;
  5317. _group setVariable ["classifiedType", _cachedType];
  5318. _group setVariable ["classificationTime", time];
  5319. };
  5320.  
  5321. // Use cached type for categorization
  5322. switch (_cachedType) do {
  5323. case "INFANTRY": {_infantryGroups pushBack _group;};
  5324. case "ARMOR": {_armorGroups pushBack _group;};
  5325. case "MECHANIZED": {_mechanizedGroups pushBack _group;};
  5326. case "AIR": {_airGroups pushBack _group;};
  5327. case "SNIPER";
  5328. case "SPECOPS";
  5329. case "ELITE": {_specialGroups pushBack _group;};
  5330. };
  5331. } forEach _availableGroups;
  5332.  
  5333. // Calculate force strength
  5334. private _totalInfantry = count _infantryGroups;
  5335. private _totalArmor = count _armorGroups;
  5336. private _totalMech = count _mechanizedGroups;
  5337.  
  5338. // Initialize battle plan structure
  5339. _battlePlan set ["attackers", []];
  5340. _battlePlan set ["defenders", []];
  5341. _battlePlan set ["leftFlank", []];
  5342. _battlePlan set ["rightFlank", []];
  5343. _battlePlan set ["reserve", []];
  5344. _battlePlan set ["support", []];
  5345. _battlePlan set ["battleCenter", _battleCenter];
  5346.  
  5347. // Defense: Keep some forces back if under pressure
  5348. private _enemiesNearBase = [_side, 800] call fnc_enemiesNearBase;
  5349. if (_enemiesNearBase) then {
  5350. private _defenderCount = (count _availableGroups * 0.3) max 2;
  5351. for "_i" from 0 to (_defenderCount min (count _infantryGroups) - 1) do {
  5352. (_battlePlan get "defenders") pushBack (_infantryGroups select _i);
  5353. };
  5354. _infantryGroups = _infantryGroups - (_battlePlan get "defenders");
  5355. };
  5356.  
  5357. // Special forces: Flanking and harassment
  5358. {
  5359. if ((count (_battlePlan get "leftFlank")) < 2) then {
  5360. (_battlePlan get "leftFlank") pushBack _x;
  5361. } else {
  5362. (_battlePlan get "rightFlank") pushBack _x;
  5363. };
  5364. } forEach _specialGroups;
  5365.  
  5366. // Armor: Main assault force
  5367. {
  5368. (_battlePlan get "attackers") pushBack _x;
  5369. } forEach _armorGroups;
  5370.  
  5371. // Mechanized: Support assault or flanking
  5372. private _mechCount = count _mechanizedGroups;
  5373. for "_i" from 0 to (_mechCount - 1) do {
  5374. private _group = _mechanizedGroups select _i;
  5375. if (_i % 2 == 0) then {
  5376. (_battlePlan get "attackers") pushBack _group;
  5377. } else {
  5378. if ((count (_battlePlan get "leftFlank")) <= (count (_battlePlan get "rightFlank"))) then {
  5379. (_battlePlan get "leftFlank") pushBack _group;
  5380. } else {
  5381. (_battlePlan get "rightFlank") pushBack _group;
  5382. };
  5383. };
  5384. };
  5385.  
  5386. // OPTIMIZATION: Simplified infantry distribution using direct calculations
  5387. private _infCount = count _infantryGroups;
  5388. if (_infCount > 0) then {
  5389. private _attackerCount = ceil (_infCount * 0.4);
  5390. private _flankCount = ceil (_infCount * 0.3);
  5391.  
  5392. // Attackers
  5393. for "_i" from 0 to (_attackerCount - 1) do {
  5394. if (_i < _infCount) then {
  5395. (_battlePlan get "attackers") pushBack (_infantryGroups select _i);
  5396. };
  5397. };
  5398.  
  5399. // Flankers
  5400. for "_i" from _attackerCount to (_attackerCount + _flankCount - 1) do {
  5401. if (_i < _infCount) then {
  5402. if ((_i - _attackerCount) % 2 == 0) then {
  5403. (_battlePlan get "leftFlank") pushBack (_infantryGroups select _i);
  5404. } else {
  5405. (_battlePlan get "rightFlank") pushBack (_infantryGroups select _i);
  5406. };
  5407. };
  5408. };
  5409.  
  5410. // Reserve
  5411. for "_i" from (_attackerCount + _flankCount) to (_infCount - 1) do {
  5412. (_battlePlan get "reserve") pushBack (_infantryGroups select _i);
  5413. };
  5414. };
  5415.  
  5416. // Air units: Support role
  5417. {
  5418. (_battlePlan get "support") pushBack _x;
  5419. } forEach _airGroups;
  5420.  
  5421. _battlePlan
  5422. };
  5423.  
  5424. // Execute a specific role for a group with appropriate waypoints
  5425. fnc_executeGroupRole = {
  5426. params ["_group", "_role", "_battleCenter", "_ownBase", "_enemyBase"];
  5427.  
  5428. private _groupType = [_group] call fnc_classifyGroup;
  5429. private _targetPos = [0,0,0];
  5430. private _finalOrder = _role; // Default to role name if no specific override
  5431.  
  5432. switch (_role) do {
  5433. case "ATTACK": {
  5434. _targetPos = [_battleCenter, "FORWARD", _ownBase, _enemyBase] call fnc_findTacticalPosition;
  5435. // Attack logic remains standard
  5436. _finalOrder = "ATTACK";
  5437. _group setVariable ["HC_ROLE", "ATTACKER", true];
  5438. };
  5439. case "DEFEND": {
  5440. _targetPos = [_battleCenter, "DEFENSIVE", _ownBase, _enemyBase] call fnc_findTacticalPosition;
  5441.  
  5442. // Intelligent Defense: Check if enemies are actually near the position
  5443. private _enemiesNear = (_targetPos nearEntities [["CAManBase", "LandVehicle"], 600]) findIf {side _x != side _group && alive _x} != -1;
  5444.  
  5445. if (_enemiesNear) then {
  5446. // Active combat nearby: Use standard DEFEND (SAD cycle)
  5447. _finalOrder = "DEFEND";
  5448. _group setVariable ["HC_ROLE", "DEFENDER", true];
  5449. } else {
  5450. // No immediate threat: Use passive GUARD or SENTRY waypoints
  5451. // 50/50 chance to Guard (Patrol/Engage) or Sentry (Hold until spotted)
  5452. if (random 1 > 0.5) then {
  5453. _finalOrder = "GUARD";
  5454. _group setVariable ["HC_ROLE", "GUARD", true];
  5455. } else {
  5456. _finalOrder = "SENTRY";
  5457. _group setVariable ["HC_ROLE", "SENTRY", true];
  5458. };
  5459. };
  5460. };
  5461. case "FLANK_LEFT": {
  5462. _targetPos = [_battleCenter, "FLANK_LEFT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
  5463. _finalOrder = "FLANK_LEFT";
  5464. _group setVariable ["HC_ROLE", "FLANKER", true];
  5465. };
  5466. case "FLANK_RIGHT": {
  5467. _targetPos = [_battleCenter, "FLANK_RIGHT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
  5468. _finalOrder = "FLANK_RIGHT";
  5469. _group setVariable ["HC_ROLE", "FLANKER", true];
  5470. };
  5471. case "RESERVE": {
  5472. _targetPos = [_battleCenter, "SUPPORT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
  5473. // Reserves should HOLD position until called upon
  5474. _finalOrder = "HOLD";
  5475. _group setVariable ["HC_ROLE", "RESERVE", true];
  5476. };
  5477. case "SUPPORT": {
  5478. if (_groupType == "AIR") then {
  5479. // Air units patrol/loiter
  5480. _targetPos = _battleCenter;
  5481. _finalOrder = "SUPPORT_AIR";
  5482. } else {
  5483. _targetPos = [_battleCenter, "HIGH_GROUND", _ownBase, _enemyBase] call fnc_findTacticalPosition;
  5484. // Ground support (like mortars/HMG teams) should HOLD high ground or SENTRY
  5485. _finalOrder = "SENTRY";
  5486. };
  5487. _group setVariable ["HC_ROLE", "SUPPORT", true];
  5488. };
  5489. };
  5490.  
  5491. // Pass the calculated specific order and target to the assignment function
  5492. [_group, _targetPos, _finalOrder, _groupType] call fnc_assignGroupObjective;
  5493. };
  5494.  
  5495. // Get strategic groups
  5496. fnc_getStrategicGroups = {
  5497. params ["_side"];
  5498. allGroups select {
  5499. (side _x == _side) &&
  5500. (count (units _x) > 0) &&
  5501. ({isPlayer _x} count (units _x) == 0)
  5502. };
  5503. };
  5504.  
  5505. // Check for enemies near base
  5506. fnc_enemiesNearBase = {
  5507. params ["_side", ["_radius", 500]];
  5508. private _ownBasePos = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  5509. private _enemySide = if (_side == west) then {east} else {west};
  5510. private _nearbyEnemyUnits = _ownBasePos nearEntities [["CAManBase", "LandVehicle", "Air"], _radius];
  5511. private _firstEnemy = _nearbyEnemyUnits findIf {alive _x && side _x == _enemySide};
  5512. (_firstEnemy != -1)
  5513. };
  5514.  
  5515. // Check if group is locked
  5516. fnc_isGroupLocked = {
  5517. params ["_group"];
  5518. if (isNull _group || {count (units _group) == 0}) exitWith {false};
  5519. if (_group getVariable ["HC_FORCED_LOCK", false]) exitWith {true};
  5520.  
  5521. private _currentTime = time;
  5522. private _assignmentTime = _group getVariable ["HC_ASSIGNMENT_TIME", 0];
  5523. if (_assignmentTime == 0) exitWith {false};
  5524. if (_currentTime - _assignmentTime < HC_LOCK_DURATION) exitWith {true};
  5525. if (_currentTime - (_group getVariable ["HC_LAST_COMBAT", 0]) < HC_COMBAT_LOCK_TIME) exitWith {true};
  5526. false
  5527. };
  5528.  
  5529. // Get available groups
  5530. fnc_getAvailableGroups = {
  5531. params ["_side"];
  5532. private _allGroups = [_side] call fnc_getStrategicGroups;
  5533. private _availableGroups = [];
  5534. {
  5535. if !([_x] call fnc_isGroupLocked) then {
  5536. _availableGroups pushBack _x;
  5537. _x setVariable ["HC_ASSIGNMENT_TIME", nil];
  5538. _x setVariable ["HC_TARGET", nil];
  5539. _x setVariable ["HC_ORDER", nil];
  5540. HC_Active_Groups deleteAt (groupId _x);
  5541. };
  5542. } forEach _allGroups;
  5543. _availableGroups
  5544. };
  5545.  
  5546. // Assign group objective
  5547. fnc_assignGroupObjective = {
  5548. params ["_group", "_targetPos", "_orderType", ["_groupType", "INFANTRY"]];
  5549.  
  5550. if (typeName _targetPos != "ARRAY" || count _targetPos < 2) exitWith {
  5551. diag_log format ["ERROR: Invalid target position for group %1: %2", groupId _group, _targetPos];
  5552. };
  5553.  
  5554. _group setVariable ["HC_ASSIGNMENT_TIME", time];
  5555. _group setVariable ["HC_TARGET", _targetPos];
  5556. _group setVariable ["HC_ORDER", _orderType];
  5557. _group setVariable ["HC_FORCED_LOCK", false];
  5558. HC_Active_Groups set [groupId _group, _group];
  5559.  
  5560. // Clear existing waypoints before assigning new vanilla tasks
  5561. // (Note: ATTACK/FLANK/etc. handle their own cleanup inside called functions)
  5562.  
  5563. switch (_orderType) do {
  5564. case "ATTACK": {
  5565. [_group, _targetPos, _groupType] call fnc_createTacticalPath;
  5566. };
  5567. case "DEFEND": {
  5568. [_group, _targetPos] call fnc_createDefenseWaypoints;
  5569. };
  5570. case "FLANK_LEFT": {
  5571. [_group, "FLANK_LEFT", _targetPos, _groupType] call fnc_createAdvancedWaypoints;
  5572. };
  5573. case "FLANK_RIGHT": {
  5574. [_group, "FLANK_RIGHT", _targetPos, _groupType] call fnc_createAdvancedWaypoints;
  5575. };
  5576. case "SUPPORT_AIR": {
  5577. // Assuming center of map or target area is handled by the specialized heli function
  5578. private _centerPos = missionNamespace getVariable ["mission_centralPoint", _targetPos];
  5579. [_group, _targetPos, _centerPos] call fnc_createHelicopterAttackWaypoints;
  5580. };
  5581. case "PATROL": {
  5582. [_group, _targetPos] call fnc_createPatrolWaypoints;
  5583. };
  5584. case "RETREAT": {
  5585. while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
  5586. private _wp = _group addWaypoint [_targetPos, 50];
  5587. _wp setWaypointType "MOVE";
  5588. _wp setWaypointBehaviour "AWARE";
  5589. _wp setWaypointSpeed "FULL";
  5590. };
  5591. // --- NEW VANILLA WAYPOINT TYPES ---
  5592. case "GUARD": {
  5593. while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
  5594. private _wp = _group addWaypoint [_targetPos, 0];
  5595. _wp setWaypointType "GUARD"; // AI will engage any detected enemies within range, then return
  5596. _wp setWaypointBehaviour "COMBAT";
  5597. _wp setWaypointSpeed "NORMAL";
  5598. _wp setWaypointCombatMode "RED";
  5599. _wp setWaypointCompletionRadius 50;
  5600. };
  5601. case "SENTRY": {
  5602. while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
  5603. private _wp = _group addWaypoint [_targetPos, 0];
  5604. _wp setWaypointType "SENTRY"; // AI holds until enemy seen, then proceeds (effectively wakes up)
  5605. _wp setWaypointBehaviour "STEALTH";
  5606. _wp setWaypointSpeed "LIMITED";
  5607. _wp setWaypointCombatMode "YELLOW"; // Fire at will
  5608. _wp setWaypointCompletionRadius 20;
  5609. };
  5610. case "HOLD": {
  5611. while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
  5612. private _wp = _group addWaypoint [_targetPos, 0];
  5613. _wp setWaypointType "HOLD"; // AI holds position strictly
  5614. _wp setWaypointBehaviour "AWARE";
  5615. _wp setWaypointCombatMode "YELLOW";
  5616. _wp setWaypointFormation "DIAMOND"; // Good for all-around defense
  5617. };
  5618. default {
  5619. [_group, _targetPos] call fnc_createPatrolWaypoints;
  5620. };
  5621. };
  5622. };
  5623.  
  5624. // Classify group type
  5625. fnc_classifyGroup = {
  5626. params ["_group"];
  5627. private _units = units _group select {alive _x};
  5628. if (count _units == 0) exitWith { "EMPTY" };
  5629.  
  5630. // OPTIMIZATION: Check vehicles first (fastest check)
  5631. private _leader = leader _group;
  5632. if (!isNull _leader) then {
  5633. private _vehicle = vehicle _leader;
  5634. if (_vehicle != _leader) exitWith {
  5635. // Unit is in a vehicle - classify by vehicle type
  5636. if (_vehicle isKindOf "Air") exitWith { "AIR" };
  5637. if (_vehicle isKindOf "Tank") exitWith { "ARMOR" };
  5638. if ("APC" in typeOf _vehicle) exitWith { "MECHANIZED" };
  5639. "MOTORIZED"
  5640. };
  5641. };
  5642.  
  5643. // OPTIMIZATION: Check special unit flags first (most units won't have these)
  5644. private _firstUnit = _units select 0;
  5645. if (_firstUnit getVariable ["isSniper", false]) exitWith { "SNIPER" };
  5646. if (_firstUnit getVariable ["isSpecOps", false]) exitWith { "SPECOPS" };
  5647. if (_firstUnit getVariable ["isElite", false]) exitWith { "ELITE" };
  5648.  
  5649. // OPTIMIZATION: Only do detailed composition check if no special flags found
  5650. // Count special unit types
  5651. private _sniperCount = 0;
  5652. private _specOpsCount = 0;
  5653. private _eliteCount = 0;
  5654.  
  5655. {
  5656. if (_x getVariable ["isSniper", false]) then { _sniperCount = _sniperCount + 1; };
  5657. if (_x getVariable ["isSpecOps", false]) then { _specOpsCount = _specOpsCount + 1; };
  5658. if (_x getVariable ["isElite", false]) then { _eliteCount = _eliteCount + 1; };
  5659.  
  5660. // Early exit if we found enough special units
  5661. if (_sniperCount >= 2) exitWith {};
  5662. if (_specOpsCount >= 3) exitWith {};
  5663. if (_eliteCount >= 3) exitWith {};
  5664. } forEach _units;
  5665.  
  5666. if (_sniperCount >= 2) exitWith { "SNIPER" };
  5667. if (_specOpsCount >= 3) exitWith { "SPECOPS" };
  5668. if (_eliteCount >= 3) exitWith { "ELITE" };
  5669.  
  5670. // OPTIMIZATION: Only check unit types if needed for specialized roles
  5671. private _atCount = 0;
  5672. {
  5673. private _typeStr = toLower (typeOf _x);
  5674. if ("_at_" in _typeStr || "_lat_" in _typeStr) then {
  5675. _atCount = _atCount + 1;
  5676. if (_atCount >= 2) exitWith {}; // Early exit
  5677. };
  5678. } forEach _units;
  5679.  
  5680. if (_atCount >= 2) exitWith { "ANTI_ARMOR" };
  5681.  
  5682. // Default to infantry
  5683. "INFANTRY"
  5684. };
  5685.  
  5686. // Calculate how detectable a target is based on movement and stance
  5687. fnc_calculateTargetDetectability = {
  5688. params ["_target"];
  5689.  
  5690. private _velocity = velocity _target;
  5691. private _speed = sqrt((_velocity select 0)^2 + (_velocity select 1)^2); // 2D speed in m/s
  5692. private _stance = stance _target; // "STAND", "CROUCH", "PRONE", "UNDEFINED"
  5693.  
  5694. // Base detectability by stance
  5695. private _stanceMultiplier = 1.0;
  5696. switch (_stance) do {
  5697. case "STAND": { _stanceMultiplier = 1.0; };
  5698. case "CROUCH": { _stanceMultiplier = 0.6; };
  5699. case "PRONE": { _stanceMultiplier = 0.3; };
  5700. default { _stanceMultiplier = 1.0; }; // Undefined or in vehicle
  5701. };
  5702.  
  5703. // Movement speed modifiers
  5704. private _movementMultiplier = 1.0;
  5705.  
  5706. if (_speed < 0.5) then {
  5707. // Stationary or very slow
  5708. _movementMultiplier = 0.5;
  5709. } else {
  5710. if (_speed < 2.5) then {
  5711. // Slow walk/crouch walk
  5712. _movementMultiplier = 0.7;
  5713. } else {
  5714. if (_speed < 4.5) then {
  5715. // Normal walk
  5716. _movementMultiplier = 0.85;
  5717. } else {
  5718. if (_speed < 6.5) then {
  5719. // Fast walk / jog
  5720. _movementMultiplier = 1.0;
  5721. } else {
  5722. // Sprint
  5723. _movementMultiplier = 1.3;
  5724. };
  5725. };
  5726. };
  5727. };
  5728.  
  5729. // NEW: Foliage concealment check
  5730. private _foliageMultiplier = 1.0;
  5731. private _targetPos = getPosATL _target;
  5732.  
  5733. // Check for nearby foliage that could conceal the target
  5734. private _nearFoliage = nearestTerrainObjects [_targetPos, ["BUSH", "TREE", "SMALL TREE"], 3, false, true];
  5735.  
  5736. if (count _nearFoliage > 0) then {
  5737. // Target is in/near foliage
  5738. private _foliageCount = count _nearFoliage;
  5739.  
  5740. if (_foliageCount >= 3) then {
  5741. // Heavy foliage - significant concealment
  5742. _foliageMultiplier = 0.3;
  5743. } else {
  5744. if (_foliageCount >= 2) then {
  5745. // Moderate foliage
  5746. _foliageMultiplier = 0.5;
  5747. } else {
  5748. // Light foliage
  5749. _foliageMultiplier = 0.7;
  5750. };
  5751. };
  5752.  
  5753. // Extra concealment bonus if prone or crouched in foliage
  5754. if (_stance in ["PRONE", "CROUCH"]) then {
  5755. _foliageMultiplier = _foliageMultiplier * 0.7;
  5756. };
  5757.  
  5758. // Penalty if moving fast through foliage (rustling bushes)
  5759. if (_speed > 2.5) then {
  5760. _foliageMultiplier = _foliageMultiplier * 1.4;
  5761. };
  5762. };
  5763.  
  5764. // Combine all factors
  5765. private _finalDetectability = _stanceMultiplier * _movementMultiplier * _foliageMultiplier;
  5766.  
  5767. // Clamp between 0.05 and 1.3
  5768. _finalDetectability = (_finalDetectability max 0.05) min 1.3;
  5769.  
  5770. _finalDetectability
  5771. };
  5772.  
  5773. // Calculate spotter's detection skill
  5774. fnc_calculateSpotterSkill = {
  5775. params ["_spotter"];
  5776.  
  5777. private _skillMultiplier = 1.0;
  5778.  
  5779. // Elite troops are the best spotters
  5780. if (_spotter getVariable ["isElite", false]) exitWith { 1.4 };
  5781.  
  5782. // SpecOps and Snipers are excellent spotters
  5783. if (_spotter getVariable ["isSpecOps", false]) exitWith { 1.3 };
  5784. if (_spotter getVariable ["isSniper", false]) exitWith { 1.3 };
  5785.  
  5786. // Upgraded troops are above average
  5787. if (_spotter getVariable ["isUpgraded", false]) exitWith { 1.15 };
  5788.  
  5789. // Standard infantry (default)
  5790. 1.0
  5791. };
  5792.  
  5793. // ==================== INTEL OPTIMIZATION START ====================
  5794. // MODIFIED: This function now also populates the new lightweight tactical report queue.
  5795. HC_fnc_gatherIntelligence = {
  5796. params ["_unit", "_side"];
  5797. if (!alive _unit) exitWith {};
  5798.  
  5799. // NEW: Prevent intel gathering while parachuting or in freefall
  5800. private _vehicle = vehicle _unit;
  5801. if (_vehicle != _unit && {_vehicle isKindOf "ParachuteBase"}) exitWith {};
  5802. if ((getPosATL _unit select 2) > 5 && (velocity _unit select 2) < -5 && _vehicle == _unit) exitWith {};
  5803.  
  5804. // Performance throttling - skip some spotting checks when server is struggling
  5805. private _skipSpotting = missionNamespace getVariable ["PERF_SKIP_SPOTTING", false];
  5806. if (_skipSpotting) then {
  5807. private _spottingChance = missionNamespace getVariable ["PERF_SPOTTING_CHANCE", 1.0];
  5808. if (random 1 > _spottingChance) exitWith {}; // Skip this unit's spotting check
  5809. };
  5810.  
  5811. private _enemySide = if (_side == west) then {east} else {west};
  5812. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  5813. private _viewDistance = 500;
  5814.  
  5815. if (_unit getVariable ["isElite", false]) then { _viewDistance = 1200; };
  5816. if (_unit getVariable ["isSpecOps", false]) then { _viewDistance = 1200; };
  5817. if (_unit getVariable ["isSniper", false]) then { _viewDistance = 1500; };
  5818. if (vehicle _unit != _unit) then { _viewDistance = 1000; };
  5819.  
  5820. // OPTIMIZATION: Use nearEntities with type filter to reduce initial search
  5821. private _potentialEnemies = _unit nearEntities [["CAManBase", "LandVehicle", "Air"], _viewDistance];
  5822.  
  5823. // OPTIMIZATION: Early exit if no potential enemies found
  5824. if (count _potentialEnemies == 0) exitWith {};
  5825.  
  5826. // OPTIMIZATION: Filter enemies with combined checks to reduce iterations
  5827. private _nearEnemies = _potentialEnemies select {
  5828. (side _x == _enemySide || (_x getVariable ["isAAF", false])) &&
  5829. alive _x
  5830. };
  5831.  
  5832. // OPTIMIZATION: Early exit if no visible enemies
  5833. if (count _nearEnemies == 0) exitWith {};
  5834.  
  5835. if (behaviour _unit in ["COMBAT", "STEALTH"]) then {
  5836. (group _unit) setVariable ["HC_LAST_COMBAT", time];
  5837. };
  5838.  
  5839. // OPTIMIZATION: Get tactical reports array once
  5840. private _tacticalReports = if (_side == west) then {BLUFOR_TACTICAL_REPORTS} else {OPFOR_TACTICAL_REPORTS};
  5841.  
  5842. {
  5843. private _enemy = _x;
  5844.  
  5845. // REPLACED: Threshold lowered from 1.5 to 0.1.
  5846. // Any perception (> 0) counts as intel. If they are shooting, this is definitely > 0.1.
  5847. if (_unit knowsAbout _enemy > 0.1) then {
  5848.  
  5849. // Successfully spotted the enemy - gather intel
  5850. private _enemyID = netId _enemy;
  5851.  
  5852. // OPTIMIZATION: Determine entity type with early exit checks
  5853. private _entityType = "INFANTRY";
  5854. private _isTank = false;
  5855.  
  5856. if !(_enemy isKindOf "CAManBase") then {
  5857. if (_enemy isKindOf "Air") then {
  5858. _entityType = "AIR_VEHICLE";
  5859. } else {
  5860. _entityType = "GROUND_VEHICLE";
  5861. if (_enemy isKindOf "Tank") then {
  5862. _isTank = true;
  5863. };
  5864. };
  5865. };
  5866.  
  5867. // OPTIMIZATION: Create report with minimal overhead
  5868. private _intelReport = createHashMap;
  5869. _intelReport set ["position", getPos _enemy];
  5870. _intelReport set ["time", time];
  5871. _intelReport set ["velocity", velocity _enemy];
  5872. _intelReport set ["lastUpdate", time];
  5873. _intelReport set ["type", typeOf _enemy];
  5874. _intelReport set ["isMounted", vehicle _enemy != _enemy];
  5875. _intelReport set ["entityType", _entityType];
  5876. _intelReport set ["isTank", _isTank];
  5877. _intelReport set ["isAAF", _enemy getVariable ["isAAF", false]];
  5878. _intel set [_enemyID, _intelReport];
  5879.  
  5880. // Add to tactical report queue
  5881. _tacticalReports pushBack [time, _entityType, getPos _enemy, _isTank];
  5882.  
  5883. // OPTIMIZATION: Cap array size inline (no separate check needed)
  5884. if (count _tacticalReports > HC_REPORTS_MAX_SIZE) then {
  5885. _tacticalReports deleteAt 0;
  5886. };
  5887. };
  5888. } forEach _nearEnemies;
  5889. };
  5890.  
  5891. // Make AI aware of enemy presence in nearby intel areas without direct revelation
  5892. HC_fnc_investigateNearbyIntel = {
  5893. params ["_unit", "_side"];
  5894.  
  5895. // Only process for group leaders to avoid redundant checks
  5896. if (_unit != leader (group _unit)) exitWith {};
  5897.  
  5898. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  5899. private _unitPos = getPosATL _unit;
  5900. private _unitGroup = group _unit;
  5901.  
  5902. // Don't interfere with locked groups or groups already in combat
  5903. if ([_unitGroup] call fnc_isGroupLocked) exitWith {};
  5904. if (behaviour _unit == "COMBAT") exitWith {};
  5905.  
  5906. private _closestIntelPos = [];
  5907. private _closestDistance = 9999;
  5908.  
  5909. // Find closest recent intel within 500m
  5910. {
  5911. private _intelReport = _y;
  5912. private _intelPos = _intelReport get "position";
  5913. private _intelAge = time - (_intelReport get "time");
  5914.  
  5915. // Only process recent intel (less than 90 seconds old)
  5916. if (_intelAge < 90) then {
  5917. private _distance = _unitPos distance2D _intelPos;
  5918.  
  5919. if (_distance <= 500 && _distance < _closestDistance) then {
  5920. _closestDistance = _distance;
  5921. _closestIntelPos = _intelPos;
  5922. };
  5923. };
  5924. } forEach _intel;
  5925.  
  5926. // If we found nearby intel, make the group investigate
  5927. if (count _closestIntelPos > 0) then {
  5928. private _lastIntelReaction = _unitGroup getVariable ["lastIntelReaction", 0];
  5929.  
  5930. // Only react every 60 seconds to avoid constant waypoint changes
  5931. if (time - _lastIntelReaction > 60) then {
  5932. _unitGroup setVariable ["lastIntelReaction", time];
  5933.  
  5934. // Set group to combat-aware behavior
  5935. _unitGroup setBehaviour "AWARE";
  5936. _unitGroup setCombatMode "YELLOW";
  5937. _unitGroup setSpeedMode "NORMAL";
  5938.  
  5939. // Only add investigation waypoint if group doesn't have many waypoints already
  5940. if (count (waypoints _unitGroup) < 2) then {
  5941. // Add waypoint to investigate the intel area
  5942. private _wp = _unitGroup addWaypoint [_closestIntelPos, 100];
  5943. _wp setWaypointType "SAD"; // Search and Destroy - will engage if they find enemies
  5944. _wp setWaypointBehaviour "AWARE";
  5945. _wp setWaypointSpeed "NORMAL";
  5946. _wp setWaypointCombatMode "YELLOW";
  5947. _wp setWaypointCompletionRadius 150;
  5948. };
  5949. };
  5950. };
  5951. };
  5952.  
  5953. // Plan coordinated attack
  5954. fnc_planCoordinatedAttack = {
  5955. params ["_attackGroups", "_targetPos", "_side"];
  5956. if (count _attackGroups == 0) exitWith {};
  5957.  
  5958. if (count _attackGroups < 2) exitWith {
  5959. private _group = _attackGroups select 0;
  5960. [_group, "ASSAULT", _targetPos, [_group] call fnc_classifyGroup] call fnc_createAdvancedWaypoints;
  5961. };
  5962.  
  5963. private _assaultGroups = [];
  5964. private _supportGroups = [];
  5965. private _flankingGroups = [];
  5966.  
  5967. {
  5968. private _group = _x;
  5969. private _type = [_group] call fnc_classifyGroup;
  5970. switch (_type) do {
  5971. case "ARMOR";
  5972. case "MECHANIZED";
  5973. case "SNIPER";
  5974. case "SUPPORT": { _supportGroups pushBack _group; };
  5975. case "SPECOPS";
  5976. case "ELITE": { _flankingGroups pushBack _group; };
  5977. default {
  5978. if (count _assaultGroups <= count _flankingGroups) then {
  5979. _assaultGroups pushBack _group;
  5980. } else {
  5981. _flankingGroups pushBack _group;
  5982. };
  5983. };
  5984. };
  5985. } forEach _attackGroups;
  5986.  
  5987. { [_x, "ASSAULT", _targetPos, [_x] call fnc_classifyGroup] call fnc_createAdvancedWaypoints; } forEach _assaultGroups;
  5988. {
  5989. private _grpType = [_x] call fnc_classifyGroup;
  5990. private _order = if (_grpType == "SNIPER") then {"DEEP_FLANK"} else {"OVERWATCH"};
  5991. [_x, _order, _targetPos, _grpType] call fnc_createAdvancedWaypoints;
  5992. } forEach _supportGroups;
  5993.  
  5994. private _flankCount = count _flankingGroups;
  5995. {
  5996. private _group = _x;
  5997. private _grpType = [_group] call fnc_classifyGroup;
  5998. private _flankSide = if (_forEachIndex < (_flankCount / 2)) then {"FLANK_LEFT"} else {"FLANK_RIGHT"};
  5999. [_group, _flankSide, _targetPos, _grpType] call fnc_createAdvancedWaypoints;
  6000. } forEach _flankingGroups;
  6001. };
  6002.  
  6003. fnc_processIntelligence = {
  6004. params ["_side"];
  6005. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  6006.  
  6007. // OPTIMIZATION: Early exit if no intel to process
  6008. if (count _intel == 0) exitWith {};
  6009.  
  6010. private _currentTime = time;
  6011. private _keysToRemove = [];
  6012.  
  6013. // OPTIMIZATION: Removed complex threat categorization - it's rarely used effectively
  6014. // Simply clean old intel and let other systems query what they need directly
  6015. {
  6016. private _report = _y;
  6017. private _age = _currentTime - (_report get "time");
  6018. if (_age > HC_INTEL_DECAY_TIME) then {
  6019. _keysToRemove pushBack _x;
  6020. };
  6021. } forEach _intel;
  6022.  
  6023. // Clean up old intel
  6024. { _intel deleteAt _x; } forEach _keysToRemove;
  6025.  
  6026. // OPTIMIZATION: No need to store empty threat categorization
  6027. // Other functions can query intel directly when needed
  6028. };
  6029.  
  6030. // Create tactical path
  6031. fnc_createTacticalPath = {
  6032. params ["_group", "_targetPos", "_groupType"];
  6033. while {count (waypoints _group) > 0} do {
  6034. deleteWaypoint ((waypoints _group) select 0);
  6035. };
  6036.  
  6037. private _leader = leader _group;
  6038. if (isNull _leader) exitWith {};
  6039.  
  6040. private _startPos = position _leader;
  6041. private _distance = _startPos distance _targetPos;
  6042. private _side = side _group;
  6043.  
  6044. // Short distance - direct approach with terrain-based behavior
  6045. if (_distance < 500) then {
  6046. private _movementParams = [_group, _startPos, _targetPos, _side] call fnc_determineMovementBehavior;
  6047.  
  6048. private _wp = _group addWaypoint [_targetPos, 20];
  6049. _wp setWaypointType "SAD";
  6050. _wp setWaypointBehaviour (_movementParams get "behavior");
  6051. _wp setWaypointSpeed (_movementParams get "speed");
  6052. _wp setWaypointFormation (_movementParams get "formation");
  6053. _wp setWaypointCombatMode (_movementParams get "combatMode");
  6054. _wp setWaypointCompletionRadius 100;
  6055. } else {
  6056. // Long distance - use covered waypoints
  6057. private _coveredWaypoints = [_startPos, _targetPos, _groupType] call fnc_selectCoveredWaypoints;
  6058.  
  6059. // Create waypoints with terrain-appropriate behaviors
  6060. {
  6061. private _wpPos = _x;
  6062. private _movementParams = [_group, _startPos, _wpPos, _side] call fnc_determineMovementBehavior;
  6063.  
  6064. private _wp = _group addWaypoint [_wpPos, 50];
  6065. _wp setWaypointType "MOVE";
  6066. _wp setWaypointBehaviour (_movementParams get "behavior");
  6067. _wp setWaypointSpeed (_movementParams get "speed");
  6068. _wp setWaypointFormation (_movementParams get "formation");
  6069. _wp setWaypointCombatMode (_movementParams get "combatMode");
  6070. _wp setWaypointCompletionRadius 75;
  6071.  
  6072. _startPos = _wpPos; // Update for next segment assessment
  6073. } forEach _coveredWaypoints;
  6074.  
  6075. // Final waypoint at target with aggressive settings
  6076. private _finalWp = _group addWaypoint [_targetPos, 20];
  6077. _finalWp setWaypointType "SAD";
  6078. _finalWp setWaypointBehaviour "COMBAT";
  6079. _finalWp setWaypointSpeed "NORMAL";
  6080. _finalWp setWaypointCombatMode "RED";
  6081. _finalWp setWaypointCompletionRadius 100;
  6082. };
  6083. };
  6084.  
  6085. // Create advanced waypoints
  6086. fnc_createAdvancedWaypoints = {
  6087. params ["_group", "_orderType", "_targetPos", "_groupType"];
  6088. while {count (waypoints _group) > 0} do {
  6089. deleteWaypoint ((waypoints _group) select 0);
  6090. };
  6091.  
  6092. private _side = side _group;
  6093. private _startPos = position leader _group;
  6094.  
  6095. switch (_orderType) do {
  6096. case "ASSAULT": {
  6097. [_group, _targetPos, _groupType] call fnc_createTacticalPath;
  6098. };
  6099. case "FLANK_LEFT": {
  6100. private _angle = [_startPos, _targetPos] call BIS_fnc_dirTo;
  6101. private _flankAngle = _angle - 90 + (random 40 - 20);
  6102.  
  6103. // Use covered waypoints for flanking
  6104. private _flankDist1 = 400 + (random 400);
  6105. private _flankPos1 = _targetPos getPos [_flankDist1, _flankAngle];
  6106.  
  6107. private _flankWaypoints = [_startPos, _flankPos1, _groupType] call fnc_selectCoveredWaypoints;
  6108.  
  6109. {
  6110. private _movementParams = [_group, _startPos, _x, _side] call fnc_determineMovementBehavior;
  6111.  
  6112. private _wp = _group addWaypoint [_x, 50];
  6113. _wp setWaypointType "MOVE";
  6114. _wp setWaypointBehaviour (_movementParams get "behavior");
  6115. _wp setWaypointSpeed (_movementParams get "speed");
  6116. _wp setWaypointFormation (_movementParams get "formation");
  6117. _wp setWaypointCombatMode (_movementParams get "combatMode");
  6118.  
  6119. _startPos = _x;
  6120. } forEach _flankWaypoints;
  6121.  
  6122. // Final approach position
  6123. private _flankDist2 = 200 + (random 300);
  6124. private _flankPos2 = _targetPos getPos [_flankDist2, _flankAngle - 45];
  6125.  
  6126. private _wp2 = _group addWaypoint [_flankPos2, 30];
  6127. _wp2 setWaypointType "MOVE";
  6128. _wp2 setWaypointBehaviour "COMBAT";
  6129. _wp2 setWaypointSpeed "NORMAL";
  6130. _wp2 setWaypointFormation "LINE";
  6131.  
  6132. private _wp3 = _group addWaypoint [_targetPos, 50];
  6133. _wp3 setWaypointType "SAD";
  6134. _wp3 setWaypointBehaviour "COMBAT";
  6135. _wp3 setWaypointCombatMode "RED";
  6136. };
  6137. case "FLANK_RIGHT": {
  6138. private _angle = [_startPos, _targetPos] call BIS_fnc_dirTo;
  6139. private _flankAngle = _angle + 90 + (random 40 - 20);
  6140.  
  6141. // Use covered waypoints for flanking
  6142. private _flankDist1 = 400 + (random 400);
  6143. private _flankPos1 = _targetPos getPos [_flankDist1, _flankAngle];
  6144.  
  6145. private _flankWaypoints = [_startPos, _flankPos1, _groupType] call fnc_selectCoveredWaypoints;
  6146.  
  6147. {
  6148. private _movementParams = [_group, _startPos, _x, _side] call fnc_determineMovementBehavior;
  6149.  
  6150. private _wp = _group addWaypoint [_x, 50];
  6151. _wp setWaypointType "MOVE";
  6152. _wp setWaypointBehaviour (_movementParams get "behavior");
  6153. _wp setWaypointSpeed (_movementParams get "speed");
  6154. _wp setWaypointFormation (_movementParams get "formation");
  6155. _wp setWaypointCombatMode (_movementParams get "combatMode");
  6156.  
  6157. _startPos = _x;
  6158. } forEach _flankWaypoints;
  6159.  
  6160. // Final approach position
  6161. private _flankDist2 = 200 + (random 300);
  6162. private _flankPos2 = _targetPos getPos [_flankDist2, _flankAngle + 45];
  6163.  
  6164. private _wp2 = _group addWaypoint [_flankPos2, 30];
  6165. _wp2 setWaypointType "MOVE";
  6166. _wp2 setWaypointBehaviour "COMBAT";
  6167. _wp2 setWaypointSpeed "NORMAL";
  6168. _wp2 setWaypointFormation "LINE";
  6169.  
  6170. private _wp3 = _group addWaypoint [_targetPos, 50];
  6171. _wp3 setWaypointType "SAD";
  6172. _wp3 setWaypointBehaviour "COMBAT";
  6173. _wp3 setWaypointCombatMode "RED";
  6174. };
  6175. case "OVERWATCH": {
  6176. // Find covered overwatch position
  6177. private _overwatchPos = [_targetPos, 300, 600, 10, 0, 2, 0] call BIS_fnc_findSafePos;
  6178. if (count _overwatchPos == 0) then {
  6179. _overwatchPos = _targetPos getPos [400, random 360];
  6180. };
  6181.  
  6182. // Assess cover at overwatch position
  6183. private _coverData = [_overwatchPos] call fnc_evaluateTerrainCover;
  6184.  
  6185. // Use covered approach if available
  6186. if (_startPos distance2D _overwatchPos > 400) then {
  6187. private _approachWaypoints = [_startPos, _overwatchPos, _groupType] call fnc_selectCoveredWaypoints;
  6188.  
  6189. {
  6190. private _movementParams = [_group, _startPos, _x, _side] call fnc_determineMovementBehavior;
  6191.  
  6192. private _wp = _group addWaypoint [_x, 30];
  6193. _wp setWaypointType "MOVE";
  6194. _wp setWaypointBehaviour (_movementParams get "behavior");
  6195. _wp setWaypointSpeed (_movementParams get "speed");
  6196.  
  6197. _startPos = _x;
  6198. } forEach _approachWaypoints;
  6199. };
  6200.  
  6201. private _wp = _group addWaypoint [_overwatchPos, 10];
  6202. _wp setWaypointType "MOVE";
  6203. _wp setWaypointBehaviour "AWARE";
  6204. _wp setWaypointSpeed "LIMITED";
  6205.  
  6206. private _patrolWp = _group addWaypoint [_overwatchPos, 30];
  6207. _patrolWp setWaypointType "SAD";
  6208. _patrolWp setWaypointBehaviour "AWARE";
  6209. _patrolWp setWaypointSpeed "LIMITED";
  6210. _patrolWp setWaypointCompletionRadius 150;
  6211. };
  6212. case "DEEP_FLANK": {
  6213. private _angleBack = [_targetPos, _startPos] call BIS_fnc_dirTo;
  6214. private _flankPos = _targetPos getPos [600, _angleBack + (random 60 - 30)];
  6215.  
  6216. // Use maximum stealth for deep flanking
  6217. private _deepWaypoints = [_startPos, _flankPos, _groupType] call fnc_selectCoveredWaypoints;
  6218.  
  6219. {
  6220. private _wp = _group addWaypoint [_x, 60];
  6221. _wp setWaypointType "MOVE";
  6222. _wp setWaypointBehaviour "STEALTH";
  6223. _wp setWaypointSpeed "LIMITED";
  6224. _wp setWaypointFormation "FILE";
  6225. _wp setWaypointCombatMode "GREEN";
  6226. } forEach _deepWaypoints;
  6227.  
  6228. private _wpFinal = _group addWaypoint [_flankPos, 5];
  6229. _wpFinal setWaypointType "SAD";
  6230. _wpFinal setWaypointBehaviour "STEALTH";
  6231. _wpFinal setWaypointCompletionRadius 200;
  6232. };
  6233. case "RECON": {
  6234. for "_i" from 0 to 2 do {
  6235. private _reconPos = _targetPos getPos [300 + (_i * 100), random 360];
  6236.  
  6237. // Evaluate cover at recon position
  6238. private _coverData = [_reconPos] call fnc_evaluateTerrainCover;
  6239. private _hasCover = _coverData get "hasCover";
  6240.  
  6241. private _wp = _group addWaypoint [_reconPos, 30];
  6242. _wp setWaypointType "MOVE";
  6243. _wp setWaypointBehaviour "STEALTH";
  6244. _wp setWaypointSpeed "LIMITED";
  6245. _wp setWaypointFormation "FILE";
  6246. };
  6247. if (count (waypoints _group) > 0) then {
  6248. (_group addWaypoint [waypointPosition [_group, 0], 0]) setWaypointType "CYCLE";
  6249. };
  6250. };
  6251. };
  6252.  
  6253. _group setVariable ["HC_ORDER", _orderType, true];
  6254. _group setVariable ["HC_ORDER_TIME", time, true];
  6255. _group setVariable ["HC_TARGET", _targetPos, true];
  6256. };
  6257.  
  6258. // Create patrol waypoints
  6259. fnc_createPatrolWaypoints = {
  6260. params ["_group", "_targetPos", ["_behavior", "AWARE"], ["_formation", "LINE"]];
  6261. while {count (waypoints _group) > 0} do {
  6262. deleteWaypoint ((waypoints _group) select 0);
  6263. };
  6264.  
  6265. private _patrolPos = [_targetPos, 500, 1000, 10, 2, 0, 10] call BIS_fnc_findSafePos;
  6266. if (count _patrolPos == 0) then { _patrolPos = _targetPos; };
  6267.  
  6268. (_group addWaypoint [_patrolPos, 50]) setWaypointType "MOVE";
  6269.  
  6270. for "_i" from 1 to 3 do {
  6271. _patrolPos = [_targetPos, 300, 1000, 10, 2, 0, 10] call BIS_fnc_findSafePos;
  6272. if (count _patrolPos == 0) then { _patrolPos = _targetPos; };
  6273. private _wp = _group addWaypoint [_patrolPos, 75];
  6274. _wp setWaypointType "SAD";
  6275. _wp setWaypointCompletionRadius 100;
  6276. };
  6277.  
  6278. if (count (waypoints _group) > 1) then {
  6279. (_group addWaypoint [waypointPosition [_group, 1], 0]) setWaypointType "CYCLE";
  6280. };
  6281.  
  6282. _group setBehaviour _behavior;
  6283. _group setCombatMode "RED";
  6284. };
  6285.  
  6286. // Create defense waypoints
  6287. fnc_createDefenseWaypoints = {
  6288. params ["_group", "_targetPos"];
  6289. while {count (waypoints _group) > 0} do {
  6290. deleteWaypoint ((waypoints _group) select 0);
  6291. };
  6292.  
  6293. private _patrolPoints = [];
  6294. for "_i" from 0 to 4 do {
  6295. private _point = [_targetPos, 0, 250, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
  6296. if (count _point > 0) then { _patrolPoints pushBack _point; };
  6297. };
  6298. if (count _patrolPoints == 0) then { _patrolPoints pushBack _targetPos; };
  6299.  
  6300. {
  6301. private _wp = _group addWaypoint [_x, 10];
  6302. _wp setWaypointType "SAD";
  6303. _wp setWaypointCompletionRadius 100;
  6304. _wp setWaypointBehaviour "AWARE";
  6305. _wp setWaypointCombatMode "RED";
  6306. } forEach _patrolPoints;
  6307.  
  6308. if (count (waypoints _group) > 1) then {
  6309. (_group addWaypoint [waypointPosition [_group, 0], 0]) setWaypointType "CYCLE";
  6310. };
  6311. _group setBehaviour "COMBAT";
  6312. };
  6313.  
  6314. // Get side state
  6315. HC_fnc_getSideState = {
  6316. params ["_side"];
  6317. private _availableGroups = [];
  6318. private _busyGroups = [];
  6319.  
  6320. {
  6321. if (side _x == _side && count (units _x) > 0 && {isPlayer _x} count (units _x) == 0) then {
  6322. private _group = _x;
  6323. if ([_group] call fnc_isGroupLocked) then {
  6324. _busyGroups pushBack _group;
  6325. } else {
  6326. _group setVariable ["HC_ASSIGNMENT_TIME", nil];
  6327. _group setVariable ["HC_TARGET", nil];
  6328. _group setVariable ["HC_ORDER", nil];
  6329. HC_Active_Groups deleteAt (groupId _group);
  6330. _availableGroups pushBack _group;
  6331. };
  6332. };
  6333. } forEach allGroups;
  6334.  
  6335. private _state = createHashMap;
  6336. _state set ["groups", _availableGroups];
  6337. _state set ["busyGroups", _busyGroups];
  6338. _state
  6339. };
  6340.  
  6341. fnc_getBattleHotspot = {
  6342. params ["_side"];
  6343. private _enemySide = if (_side == west) then {east} else {west};
  6344. private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
  6345. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  6346.  
  6347. // Find the midpoint between bases as default
  6348. private _defaultPos = [
  6349. ((_enemyBase select 0) + (_ownBase select 0)) / 2,
  6350. ((_enemyBase select 1) + (_ownBase select 1)) / 2,
  6351. 0
  6352. ];
  6353.  
  6354. // Try to find actual combat areas
  6355. private _combatPositions = [];
  6356. {
  6357. if (side _x == _side && behaviour leader _x == "COMBAT") then {
  6358. _combatPositions pushBack (getPos leader _x);
  6359. };
  6360. } forEach allGroups;
  6361.  
  6362. if (count _combatPositions > 0) exitWith {
  6363. selectRandom _combatPositions
  6364. };
  6365.  
  6366. _defaultPos
  6367. };
  6368.  
  6369. // NEW: Analyzes terrain between bases to find tactical intermediate objectives (Towns, Hills, etc.)
  6370. fnc_HC_assessTerrainObjectives = {
  6371. params ["_side"];
  6372.  
  6373. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  6374. private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
  6375.  
  6376. // Default to enemy base if no terrain features found
  6377. private _bestObjective = _enemyBase;
  6378.  
  6379. // 1. Find potential tactical locations (Cities, Villages, Hills)
  6380. private _midPoint = [((_ownBase select 0) + (_enemyBase select 0)) / 2, ((_ownBase select 1) + (_enemyBase select 1)) / 2, 0];
  6381. private _searchRadius = (_ownBase distance _enemyBase) / 1.2;
  6382.  
  6383. private _locations = nearestLocations [_midPoint, ["NameCityCapital", "NameCity", "NameVillage", "Hill", "Strategic"], _searchRadius];
  6384.  
  6385. // 2. Filter locations to create a "Corridor of Advance"
  6386. // Only keep locations that are roughly between the two bases (within 800m of the direct line)
  6387. private _corridorWidth = 1000;
  6388. private _validLocations = [];
  6389.  
  6390. {
  6391. private _locPos = locationPosition _x;
  6392. // Calculate distance of point to the line segment between bases
  6393. private _distToLine = abs ((_enemyBase select 1) - (_ownBase select 1)) * (_locPos select 0) - ((_enemyBase select 0) - (_ownBase select 0)) * (_locPos select 1) + (_enemyBase select 0) * (_ownBase select 1) - (_enemyBase select 1) * (_ownBase select 0);
  6394. _distToLine = _distToLine / (_ownBase distance _enemyBase);
  6395.  
  6396. if (_distToLine < _corridorWidth) then {
  6397. _validLocations pushBack _locPos;
  6398. };
  6399. } forEach _locations;
  6400.  
  6401. // 3. Sort locations by distance from OWN base (Sequential capture)
  6402. _validLocations = [_validLocations, [], {_x distance _ownBase}, "ASCEND"] call BIS_fnc_sortBy;
  6403.  
  6404. // 4. Determine the "Front Line" Objective
  6405. // Iterate through sorted locations. The first one that isn't fully secured is the target.
  6406. private _foundIntermediate = false;
  6407.  
  6408. {
  6409. private _locPos = _x;
  6410. // Check control status
  6411. private _friendlyCount = {side _x == _side && alive _x} count (_locPos nearEntities [["CAManBase", "LandVehicle"], 200]);
  6412. private _enemySide = if (_side == west) then {east} else {west};
  6413. private _enemyCount = {side _x == _enemySide && alive _x} count (_locPos nearEntities [["CAManBase", "LandVehicle"], 200]);
  6414.  
  6415. // Conditions to consider a location "Secured"
  6416. // 1. We have significant force there AND enemies are cleared OR
  6417. // 2. We are closer to the NEXT objective than this one (we moved past it)
  6418. private _isSecured = (_friendlyCount > 2 && _enemyCount == 0);
  6419.  
  6420. // If not secured, this is our next Tactical Objective
  6421. if (!_isSecured) exitWith {
  6422. _bestObjective = _locPos;
  6423. _foundIntermediate = true;
  6424. };
  6425. } forEach _validLocations;
  6426.  
  6427. // 5. If all intermediate terrain is secured, push for the Enemy Base,
  6428. // but try to find a covered assault position 300m out rather than center-mass.
  6429. if (!_foundIntermediate) then {
  6430. private _dirToEnemy = _ownBase getDir _enemyBase;
  6431. // Offset slightly to utilize terrain if possible
  6432. private _assaultPos = _enemyBase getPos [300, _dirToEnemy];
  6433. private _coverPos = [_assaultPos, 0, 150, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
  6434. if (count _coverPos > 0) then {
  6435. _bestObjective = _coverPos;
  6436. } else {
  6437. _bestObjective = _enemyBase;
  6438. };
  6439. };
  6440.  
  6441. _bestObjective
  6442. };
  6443.  
  6444. // Main commander decision-making
  6445. fnc_commanderMakeDecision = {
  6446. params ["_side"];
  6447. if (isNil "bluforSpawnObj" || isNil "opforSpawnObj") exitWith {};
  6448.  
  6449. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  6450. private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
  6451.  
  6452. // Get Play Area constraints
  6453. private _playAreaCenter = missionNamespace getVariable ["PLAY_AREA_CENTER", [0,0,0]];
  6454. private _playAreaRadius = missionNamespace getVariable ["PLAY_AREA_RADIUS", 2000];
  6455.  
  6456. private _friendlyState = [_side] call HC_fnc_getSideState;
  6457. private _availableGroups = _friendlyState get "groups";
  6458. if (count _availableGroups == 0) exitWith {};
  6459.  
  6460. // PRIORITY 0: Emergency base defense (Always overrides everything)
  6461. if ([_side, 600] call fnc_enemiesNearBase) then {
  6462. _availableGroups = [_availableGroups, [], {leader _x distance _ownBase}, "ASCEND"] call BIS_fnc_sortBy;
  6463. private _groupsToDefendBase = _availableGroups select [0, floor(count _availableGroups / 3) max 1];
  6464. { [_x, _ownBase, "DEFEND"] call fnc_assignGroupObjective; } forEach _groupsToDefendBase;
  6465. _availableGroups = _availableGroups - _groupsToDefendBase;
  6466. };
  6467.  
  6468. if (count _availableGroups == 0) exitWith {};
  6469.  
  6470. // --- STEP 1: SEGREGATE SCOUTING FORCES ---
  6471. // Identify units best suited for wide flank security (Light Vics, SpecOps, Snipers, Air)
  6472. private _scoutGroups = [];
  6473. private _mainBattleGroups = [];
  6474.  
  6475. {
  6476. private _grp = _x;
  6477. private _type = [_grp] call fnc_classifyGroup;
  6478. // Check if group is light/fast/stealthy
  6479. // Note: Standard Infantry can also be scouts if we have too many, but let's prioritize these first
  6480. if (_type in ["SNIPER", "SPECOPS", "ELITE", "AIR"] ||
  6481. (_type == "MOTORIZED") || // Assuming Motorized are light vehicles like MRAPs
  6482. (_type == "MECHANIZED" && random 1 > 0.7)) then { // 30% chance for APCs to help flanks
  6483. _scoutGroups pushBack _grp;
  6484. } else {
  6485. _mainBattleGroups pushBack _grp;
  6486. };
  6487. } forEach _availableGroups;
  6488.  
  6489. // --- STEP 2: ASSIGN WIDE FLANK PATROLS ---
  6490. // These units ignore the "Tactical Objective" and focus on the edges of the Red Circle
  6491. if (count _scoutGroups > 0) then {
  6492. private _attackDir = _ownBase getDir _enemyBase;
  6493. // Use 65% of the radius to stay safely inside the circle but wide enough to spot players
  6494. private _flankDist = _playAreaRadius * 0.65;
  6495.  
  6496. // Calculate dynamic flank positions relative to the PLAY AREA CENTER, not the units
  6497. private _leftFlankPos = _playAreaCenter getPos [_flankDist, _attackDir - 90];
  6498. private _rightFlankPos = _playAreaCenter getPos [_flankDist, _attackDir + 90];
  6499.  
  6500. // Find safe spots for these coordinates
  6501. private _safeLeft = [_leftFlankPos, 0, 100, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
  6502. if (count _safeLeft == 0) then { _safeLeft = _leftFlankPos; };
  6503.  
  6504. private _safeRight = [_rightFlankPos, 0, 100, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
  6505. if (count _safeRight == 0) then { _safeRight = _rightFlankPos; };
  6506.  
  6507. {
  6508. private _grp = _x;
  6509. // Alternate assignment: Even index -> Left, Odd index -> Right
  6510. private _target = if (_forEachIndex % 2 == 0) then { _safeLeft } else { _safeRight };
  6511.  
  6512. // Assign a Patrol/SAD task to the flank
  6513. // We use "PATROL" so they move around that area looking for enemies
  6514. [_grp, _target, "PATROL"] call fnc_assignGroupObjective;
  6515.  
  6516. // Mark them as Flankers for debugging/tracking
  6517. _grp setVariable ["HC_ROLE", "WIDE_FLANK_SECURITY", true];
  6518.  
  6519. } forEach _scoutGroups;
  6520. };
  6521.  
  6522. // If no main force left (e.g. only had snipers), exit
  6523. if (count _mainBattleGroups == 0) exitWith {};
  6524.  
  6525. // --- STEP 3: MAIN FORCE LOGIC (Center/Towns) ---
  6526. // The remaining heavy troops (Tanks, main Infantry) proceed with the existing terrain logic
  6527.  
  6528. // Calculate the best tactical objective based on terrain analysis
  6529. private _tacticalObjective = [_side] call fnc_HC_assessTerrainObjectives;
  6530.  
  6531. private _currentStrategyName = "ADVANCE";
  6532. if (_tacticalObjective distance2D _enemyBase < 300) then {
  6533. _currentStrategyName = "BASE_ASSAULT";
  6534. } else {
  6535. _currentStrategyName = "SECTOR_CONTROL";
  6536. };
  6537.  
  6538. if (STRATEGY_ENABLED) then {
  6539. private _strategicRecommendation = [_side, _mainBattleGroups] call fnc_getStrategicRecommendation;
  6540.  
  6541. if (count _strategicRecommendation > 0) then {
  6542. private _objective = _strategicRecommendation get "objective";
  6543. private _recommendedGroupCount = _strategicRecommendation get "groupCount";
  6544. private _prediction = _strategicRecommendation get "prediction";
  6545. private _score = _strategicRecommendation get "score";
  6546. private _expectedCasualties = _prediction get "expectedCasualties";
  6547.  
  6548. if (_score >= STRATEGY_MIN_SCORE && _expectedCasualties <= STRATEGY_CASUALTY_TOLERANCE) then {
  6549. // Strategy System Override (e.g. Taking a Hill)
  6550. private _objectivePos = _objective get "position";
  6551. private _objectiveType = _objective get "type";
  6552.  
  6553. private _sortedGroups = [_mainBattleGroups, [], {(leader _x) distance _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  6554. private _groupsToAssign = _sortedGroups select [0, _recommendedGroupCount min count _sortedGroups];
  6555.  
  6556. private _battlePlan = [_side, _groupsToAssign, _objectivePos, _ownBase] call fnc_createBattlePlan;
  6557. private _battleCenter = _battlePlan get "battleCenter";
  6558.  
  6559. private _attackers = _battlePlan get "attackers";
  6560. private _defenders = _battlePlan get "defenders";
  6561. private _leftFlank = _battlePlan get "leftFlank";
  6562. private _rightFlank = _battlePlan get "rightFlank";
  6563. private _reserve = _battlePlan get "reserve";
  6564. private _support = _battlePlan get "support";
  6565.  
  6566. [_attackers, "ATTACK", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
  6567. [_defenders, "DEFEND", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
  6568. [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
  6569. [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
  6570. [_reserve, "RESERVE", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
  6571. [_support, "SUPPORT", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
  6572.  
  6573. if (_side == west) then { BLUFOR_STRATEGY = _objectiveType; } else { OPFOR_STRATEGY = _objectiveType; };
  6574.  
  6575. } else {
  6576. // Fallback to Terrain Logic
  6577. private _battlePlan = [_side, _mainBattleGroups, _tacticalObjective, _ownBase] call fnc_createBattlePlan;
  6578. private _battleCenter = _battlePlan get "battleCenter";
  6579.  
  6580. private _attackers = _battlePlan get "attackers";
  6581. private _defenders = _battlePlan get "defenders";
  6582. private _leftFlank = _battlePlan get "leftFlank";
  6583. private _rightFlank = _battlePlan get "rightFlank";
  6584. private _reserve = _battlePlan get "reserve";
  6585. private _support = _battlePlan get "support";
  6586.  
  6587. [_attackers, "ATTACK", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6588. [_defenders, "DEFEND", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6589. [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6590. [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6591. [_reserve, "RESERVE", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6592. [_support, "SUPPORT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6593.  
  6594. if (_side == west) then { BLUFOR_STRATEGY = _currentStrategyName; } else { OPFOR_STRATEGY = _currentStrategyName; };
  6595. };
  6596. } else {
  6597. // No strategy available -> Terrain Logic
  6598. private _battlePlan = [_side, _mainBattleGroups, _tacticalObjective, _ownBase] call fnc_createBattlePlan;
  6599. private _battleCenter = _battlePlan get "battleCenter";
  6600.  
  6601. private _attackers = _battlePlan get "attackers";
  6602. private _defenders = _battlePlan get "defenders";
  6603. private _leftFlank = _battlePlan get "leftFlank";
  6604. private _rightFlank = _battlePlan get "rightFlank";
  6605. private _reserve = _battlePlan get "reserve";
  6606. private _support = _battlePlan get "support";
  6607.  
  6608. [_attackers, "ATTACK", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6609. [_defenders, "DEFEND", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6610. [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6611. [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6612. [_reserve, "RESERVE", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6613. [_support, "SUPPORT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6614.  
  6615. if (_side == west) then { BLUFOR_STRATEGY = _currentStrategyName; } else { OPFOR_STRATEGY = _currentStrategyName; };
  6616. };
  6617. } else {
  6618. // Strategy Disabled -> Terrain Logic
  6619. private _battlePlan = [_side, _mainBattleGroups, _tacticalObjective, _ownBase] call fnc_createBattlePlan;
  6620. private _battleCenter = _battlePlan get "battleCenter";
  6621.  
  6622. private _attackers = _battlePlan get "attackers";
  6623. private _defenders = _battlePlan get "defenders";
  6624. private _leftFlank = _battlePlan get "leftFlank";
  6625. private _rightFlank = _battlePlan get "rightFlank";
  6626. private _reserve = _battlePlan get "reserve";
  6627. private _support = _battlePlan get "support";
  6628.  
  6629. [_attackers, "ATTACK", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6630. [_defenders, "DEFEND", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6631. [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6632. [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6633. [_reserve, "RESERVE", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6634. [_support, "SUPPORT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
  6635.  
  6636. if (_side == west) then { BLUFOR_STRATEGY = _currentStrategyName; } else { OPFOR_STRATEGY = _currentStrategyName; };
  6637. };
  6638. };
  6639.  
  6640. // Create attack helicopter waypoints with improved combat patterns
  6641. fnc_createHelicopterAttackWaypoints = {
  6642. params ["_group", "_targetArea", "_centerPos"];
  6643.  
  6644. // Clear existing waypoints
  6645. while {count (waypoints _group) > 0} do {
  6646. deleteWaypoint ((waypoints _group) select 0);
  6647. };
  6648.  
  6649. private _heli = vehicle (leader _group);
  6650. if (isNull _heli || {!(_heli isKindOf "Air")}) exitWith {};
  6651.  
  6652. // Set optimal flight height for attack helicopters
  6653. _heli flyInHeight 150;
  6654.  
  6655. // Create attack run pattern
  6656. private _attackDistance = 2000; // Stand-off distance for initial approach
  6657. private _orbitRadius = 1500; // Orbit radius around target area
  6658.  
  6659. // Waypoint 1: Move to attack position (approach from distance)
  6660. private _approachAngle = random 360;
  6661. private _approachPos = _targetArea getPos [_attackDistance, _approachAngle];
  6662. private _wp1 = _group addWaypoint [_approachPos, 50];
  6663. _wp1 setWaypointType "MOVE";
  6664. _wp1 setWaypointBehaviour "AWARE";
  6665. _wp1 setWaypointSpeed "NORMAL";
  6666. _wp1 setWaypointStatements ["true", "(vehicle this) flyInHeight 100;"];
  6667.  
  6668. // Waypoint 2: SAD at target area
  6669. private _wp2 = _group addWaypoint [_targetArea, 200];
  6670. _wp2 setWaypointType "SAD";
  6671. _wp2 setWaypointBehaviour "COMBAT";
  6672. _wp2 setWaypointSpeed "LIMITED";
  6673. _wp2 setWaypointTimeout [30, 45, 60];
  6674. _wp2 setWaypointStatements ["true", "(vehicle this) flyInHeight 80;"];
  6675.  
  6676. // Waypoint 3-5: Orbit pattern around target
  6677. for "_i" from 0 to 2 do {
  6678. private _orbitAngle = _approachAngle + (120 * (_i + 1));
  6679. private _orbitPos = _targetArea getPos [_orbitRadius, _orbitAngle];
  6680. private _wpOrbit = _group addWaypoint [_orbitPos, 100];
  6681. _wpOrbit setWaypointType "SAD";
  6682. _wpOrbit setWaypointBehaviour "COMBAT";
  6683. _wpOrbit setWaypointSpeed "NORMAL";
  6684. _wpOrbit setWaypointTimeout [10, 15, 20];
  6685. };
  6686.  
  6687. // Waypoint 6: Return to patrol the center area
  6688. private _wpCenter = _group addWaypoint [_centerPos, 500];
  6689. _wpCenter setWaypointType "SAD";
  6690. _wpCenter setWaypointBehaviour "AWARE";
  6691. _wpCenter setWaypointSpeed "NORMAL";
  6692. _wpCenter setWaypointStatements ["true", "(vehicle this) flyInHeight 150;"];
  6693.  
  6694. // Cycle back to waypoint 1
  6695. private _wpCycle = _group addWaypoint [_approachPos, 0];
  6696. _wpCycle setWaypointType "CYCLE";
  6697.  
  6698. // Force the group to start moving
  6699. _group setCurrentWaypoint [_group, 1];
  6700. };
  6701.  
  6702. // Server-side loops
  6703. if (isServer) then {
  6704. // Main decision-making loop
  6705. [] spawn {
  6706. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  6707. sleep 10;
  6708.  
  6709. while {true} do {
  6710. // Adaptive decision interval based on performance
  6711. private _baseInterval = HC_DECISION_INTERVAL;
  6712. private _adaptiveInterval = [_baseInterval] call fnc_getAdaptiveSleep;
  6713.  
  6714. if (time - BLUFOR_LAST_DECISION > _adaptiveInterval) then {
  6715. [west] call fnc_commanderMakeDecision;
  6716. BLUFOR_LAST_DECISION = time;
  6717. };
  6718.  
  6719. sleep 3;
  6720.  
  6721. if (time - OPFOR_LAST_DECISION > _adaptiveInterval) then {
  6722. [east] call fnc_commanderMakeDecision;
  6723. OPFOR_LAST_DECISION = time;
  6724. };
  6725.  
  6726. sleep (_adaptiveInterval - 3);
  6727. };
  6728. };
  6729.  
  6730. // Check active groups for objective completion
  6731. [] spawn {
  6732. while {true} do {
  6733. sleep 10;
  6734. private _activeGroupIds = keys HC_Active_Groups;
  6735. {
  6736. private _group = HC_Active_Groups getOrDefault [_x, grpNull];
  6737. if (isNull _group || count (units _group) == 0) then {
  6738. HC_Active_Groups deleteAt _x;
  6739. } else {
  6740. private _targetPos = _group getVariable ["HC_TARGET", [0,0,0]];
  6741. if !(_targetPos isEqualTo [0,0,0]) then {
  6742. private _leader = leader _group;
  6743. if (!isNull _leader && alive _leader) then {
  6744. if ((_leader distance _targetPos) < HC_SUCCESS_UNLOCK_RADIUS) then {
  6745. _group setVariable ["HC_ASSIGNMENT_TIME", 0];
  6746. };
  6747. };
  6748. };
  6749. };
  6750. } forEach _activeGroupIds;
  6751. };
  6752. };
  6753.  
  6754. // Intel processing and sharing
  6755. [] spawn {
  6756. waitUntil { time > 5 };
  6757. while {true} do {
  6758. [west] call fnc_processIntelligence;
  6759. [east] call fnc_processIntelligence;
  6760. publicVariable "BLUFOR_INTEL";
  6761. publicVariable "OPFOR_INTEL";
  6762. sleep 5;
  6763. };
  6764. };
  6765.  
  6766. // Support evaluation and calling loop
  6767. [] spawn {
  6768. waitUntil { time > 5 };
  6769.  
  6770. // Function to handle support calls for a side
  6771. private _fnc_handleSideSupport = {
  6772. params ["_side"];
  6773.  
  6774. private _support = [_side] call fnc_evaluateSupportNeed;
  6775. if (_support == "") exitWith {};
  6776.  
  6777. private _recentReports = if (_side == west) then {BLUFOR_TACTICAL_REPORTS} else {OPFOR_TACTICAL_REPORTS};
  6778. private _enemySide = if (_side == west) then {east} else {west};
  6779. private _target = [0,0,0];
  6780.  
  6781. switch (_support) do {
  6782. case "ARTILLERY": {
  6783. private _bestTarget = [0,0,0];
  6784. private _mostRecentTime = 0;
  6785. private _maxIntelAge = 30; // CHANGED: Artillery only uses intel from last 30 seconds
  6786.  
  6787. private _enemyBaseMarker = if (_side == west) then {"opfor_base_area"} else {"blufor_base_area"};
  6788. private _enemyBasePos = markerPos _enemyBaseMarker;
  6789. private _enemyBaseSize = (markerSize _enemyBaseMarker) select 0;
  6790.  
  6791. // Loop through all reports to find the newest FRESH one
  6792. {
  6793. private _report = _x;
  6794. private _reportTime = _report select 0;
  6795. private _reportAge = time - _reportTime;
  6796.  
  6797. // CHANGED: Only consider reports from the last 30 seconds
  6798. if (_reportAge <= _maxIntelAge && _reportTime > _mostRecentTime) then {
  6799. private _pos = _report select 2;
  6800.  
  6801. // Check if it's a valid target
  6802. if (_pos distance2D _enemyBasePos >= _enemyBaseSize) then {
  6803. private _nearbyFriendlies = _pos nearEntities [["CAManBase", "LandVehicle"], 50];
  6804. private _friendliesInArea = {side _x == _side && alive _x} count _nearbyFriendlies;
  6805.  
  6806. if (_friendliesInArea == 0) then {
  6807. // It's valid and it's the newest fresh report so far
  6808. _mostRecentTime = _reportTime;
  6809. _bestTarget = _pos;
  6810. };
  6811. };
  6812. };
  6813. } forEach _recentReports;
  6814.  
  6815. // CHANGED: Only set target if we found a fresh report
  6816. if (_mostRecentTime > 0) then {
  6817. _target = _bestTarget;
  6818. };
  6819. };
  6820.  
  6821. case "SMOKE": {
  6822. private _casualtyAreas = [];
  6823. private _friendlyGroups = allGroups select {side _x == _side && count (units _x) > 0};
  6824. {
  6825. private _group = _x;
  6826. private _casualties = {damage _x > 0.3} count (units _group);
  6827. if (_casualties >= 2 && behaviour leader _group == "COMBAT") then {
  6828. _casualtyAreas pushBack (getPos leader _group);
  6829. };
  6830. } forEach _friendlyGroups;
  6831. if (count _casualtyAreas > 0) then {
  6832. _target = selectRandom _casualtyAreas;
  6833. };
  6834. };
  6835.  
  6836. default {
  6837. private _recentCombatAreas = [];
  6838. { _recentCombatAreas pushBackUnique (_x select 2); } forEach _recentReports;
  6839. _target = if (count _recentCombatAreas > 0) then {selectRandom _recentCombatAreas} else {[_side] call fnc_getBattleHotspot};
  6840. };
  6841. };
  6842.  
  6843. if !(_target isEqualTo [0,0,0]) then {
  6844. switch (_support) do {
  6845. case "FLARE": { [_side, _target] call fnc_callFlareSupport; };
  6846. case "SMOKE": { [_side, _target] call fnc_callSmokeSupport; };
  6847. case "DRONE": { [_side, _target] call fnc_callReconDroneSupport; };
  6848. case "ARTILLERY": { [_side, _target] call fnc_callArtillerySupport; };
  6849. };
  6850. };
  6851. };
  6852.  
  6853. while {true} do {
  6854. [west] call _fnc_handleSideSupport;
  6855. sleep 30;
  6856. [east] call _fnc_handleSideSupport;
  6857. sleep 30;
  6858. };
  6859. };
  6860.  
  6861. // Battlefield situation monitor - reassigns groups based on changing conditions
  6862. [] spawn {
  6863. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  6864. sleep 30;
  6865.  
  6866. while {true} do {
  6867. // Check both sides for groups that need reassignment
  6868. {
  6869. private _side = _x;
  6870. private _allGroups = [_side] call fnc_getStrategicGroups;
  6871.  
  6872. {
  6873. private _group = _x;
  6874. private _assignTime = _group getVariable ["HC_ASSIGNMENT_TIME", 0];
  6875. private _currentRole = _group getVariable ["HC_ROLE", ""];
  6876. private _target = _group getVariable ["HC_TARGET", [0,0,0]];
  6877.  
  6878. // Validate target is an array before using distance
  6879. if (typeName _target == "ARRAY" && count _target >= 2) then {
  6880. // Check if group has been on same task for too long without progress
  6881. if (time - _assignTime > 180 && !([_group] call fnc_isGroupLocked)) then {
  6882. private _leader = leader _group;
  6883. if (!isNull _leader && alive _leader) then {
  6884. // If group hasn't moved much toward target, reassign
  6885. if (!(_target isEqualTo [0,0,0]) && ((_leader distance _target) > 300)) then {
  6886. // Clear old assignment
  6887. _group setVariable ["HC_ASSIGNMENT_TIME", nil];
  6888. _group setVariable ["HC_TARGET", nil];
  6889. _group setVariable ["HC_ORDER", nil];
  6890. _group setVariable ["HC_ROLE", nil];
  6891. HC_Active_Groups deleteAt (groupId _group);
  6892. };
  6893. };
  6894. };
  6895. };
  6896.  
  6897. // Reassign defenders if base is no longer under threat
  6898. if (_currentRole == "DEFENDER" && !([_side, 800] call fnc_enemiesNearBase)) then {
  6899. if (time - _assignTime > 120) then {
  6900. _group setVariable ["HC_ASSIGNMENT_TIME", nil];
  6901. _group setVariable ["HC_TARGET", nil];
  6902. _group setVariable ["HC_ORDER", nil];
  6903. _group setVariable ["HC_ROLE", nil];
  6904. HC_Active_Groups deleteAt (groupId _group);
  6905. };
  6906. };
  6907.  
  6908. } forEach _allGroups;
  6909. } forEach [west, east];
  6910.  
  6911. sleep 45;
  6912. };
  6913. };
  6914. };
  6915.  
  6916. ==================== END OF: highcommand.sqf ====================
  6917.  
  6918.  
  6919.  
  6920. ==================== START OF: init.sqf ====================
  6921.  
  6922. // init.sqf
  6923. // Mission initialization script for Arma 3
  6924.  
  6925. // ==================== CHANGE START ====================
  6926. // Explicitly set date and time multiplier on the server to ensure consistency
  6927. // This overrides mission.sqm settings and prevents unpredictable time acceleration.
  6928. if (isServer) then {
  6929. setDate [2035, 6, 24, 8, 0]; // Set start time to 08:00
  6930. setTimeMultiplier 1; // Ensure default timescale is realtime (1x)
  6931.  
  6932. // Randomize overcast and set rain based on it.
  6933. private _overcast = random 1; // Generate a random overcast value from 0 to 1.
  6934. 0 setOvercast _overcast; // Apply the new overcast value.
  6935.  
  6936. private _rain = 0; // Initialize _rain to 0 (no rain).
  6937.  
  6938. // If overcast is greater than 70%, enable rain.
  6939. if (_overcast > 0.7) then {
  6940. // Rain intensity is a random value up to the current overcast level.
  6941. _rain = random _overcast;
  6942. };
  6943.  
  6944. 0 setRain _rain;
  6945. forceWeatherChange;
  6946. };
  6947. // ===================== CHANGE END =====================
  6948.  
  6949. // Global Mission Configuration Variables
  6950. BLUFOR_POINTS = 1000;
  6951. OPFOR_POINTS = 1000;
  6952. BLUFOR_STRENGTH = 0;
  6953. OPFOR_STRENGTH = 0;
  6954.  
  6955. // Base Spawning Configuration
  6956. RANDOMIZE_BASE_LOCATIONS = 1; // 1 to randomize base locations, 0 for fixed North/South
  6957. missionNamespace setVariable ["RANDOMIZE_BASE_LOCATIONS", RANDOMIZE_BASE_LOCATIONS];
  6958. COAST_EXCLUSION_RADIUS = 100; // Minimum distance from coast for base spawns
  6959. PATROL_RADIUS = 300; // Used for general AI patrols (also relevant for HC)
  6960.  
  6961. // AI Spawning & Waves
  6962. maxAI = 170;
  6963. maxLightVehiclesPerSide = 2; // Max light vehicles (APCs, MRAPs) per side
  6964. maxTanksPerSide = 1; // Max tanks per side
  6965. maxAttackHelisPerSide = 1; // Max attack helicopters per side
  6966. waveDelay = 3; // Base delay in seconds between AI waves
  6967. BLUFOR_LAST_WAVES = []; // History of last spawned BLUFOR wave types
  6968. OPFOR_LAST_WAVES = []; // History of last spawned OPFOR wave types
  6969. WAVE_HISTORY_SIZE = 100; // How many past wave types to remember
  6970.  
  6971. // AI Unit Values (points gained per kill, or points cost per spawn)
  6972. INFANTRY_VALUE = 1;
  6973. SPECOPS_VALUE = 2;
  6974. UPGRADED_VALUE = 2;
  6975. SNIPER_VALUE = 2;
  6976. ELITE_VALUE = 3;
  6977. VEHICLE_VALUE = 20; // Cost for spawning AI light vehicles
  6978. TANK_VALUE = 40; // Cost for spawning AI tanks
  6979. ATTACK_HELI_VALUE = 30; // Cost for spawning AI attack helicopters
  6980.  
  6981. // AI Wave Composition Weights (used by fnc_getRandomWaveType)
  6982. WAVE_WEIGHTS = [
  6983. ["infantry", 50],
  6984. ["upgraded", 30],
  6985. ["specops", 20],
  6986. ["sniper", 15],
  6987. ["elite", 10],
  6988. ["light_vehicle", 25],
  6989. ["tank", 10],
  6990. ["attack_heli", 10]
  6991. ];
  6992.  
  6993. // AAF (Syndicate/Looter) Configuration
  6994. AAF_START_SIZE = round(maxAI / 5); // Initial AAF garrison size at mission start
  6995.  
  6996. // Strategy System Configuration
  6997. STRATEGY_ENABLED = true; // Enable strategic thinking system
  6998. STRATEGY_MIN_SCORE = 10; // Minimum score to execute an objective
  6999. STRATEGY_CASUALTY_TOLERANCE = 0.4; // Maximum acceptable casualty rate (40%)
  7000.  
  7001. // AI Behavior (botai.sqf)
  7002. BOTAI_SUPPRESSION_THRESHOLD = 50; // Distance threshold for AI to react to nearby fire
  7003. BOTAI_REACTION_COOLDOWN = 5; // Seconds between AI tactical decisions
  7004. BOTAI_CRITICAL_DAMAGE = 0.6; // Damage level for defensive actions
  7005. BOTAI_HEAVY_SUPPRESSION = 5; // Suppression threshold
  7006. BOTAI_SUPPRESSION_DECAY = 0.95; // Suppression decay per second
  7007. BOTAI_GRENADE_RANGE_MAX = 50; // Max grenade throw distance
  7008. BOTAI_GRENADE_RANGE_MIN = 15; // Min grenade throw distance
  7009.  
  7010. // Cleanup System Configuration
  7011. CLEANUP_BODY_LIMIT = 20;
  7012. CLEANUP_DELAY = 30; // Check every 30 seconds
  7013. CLEANUP_BODY_TIMER = 300; // Delete bodies older than 5 minutes
  7014. CLEANUP_WEAPON_TIMER = 300; // Delete dropped weapons after 5 minutes
  7015. CLEANUP_VEHICLE_TIMER = 120; // Delete destroyed vehicles after 2 minutes
  7016. CLEANUP_MIN_DISTANCE = 300; // Min distance from players for cleanup
  7017.  
  7018. // High Command Configuration
  7019. HC_DECISION_INTERVAL = 90; // Strategic decision interval (seconds)
  7020. HC_THREAT_EVAL_RADIUS = 4000; // Radius for threat evaluation
  7021. HC_MIN_ATTACK_FORCE = 10; // Min force strength for major attack
  7022. HC_AGGRESSIVE_THRESHOLD = 0.8; // Force ratio for aggressive stance
  7023. HC_RETREAT_THRESHOLD = 0.2; // Force ratio for retreat
  7024. HC_REGROUP_THRESHOLD = 0.7; // Force ratio for regrouping
  7025. HC_INTEL_SHARE_RADIUS = 10000; // Intel sharing radius
  7026. HC_INTEL_DECAY_TIME = 180; // Intel validity duration (seconds)
  7027. HC_SEARCH_GRID_SIZE = 200; // Grid cell size for search operations
  7028.  
  7029. // Enhanced High Command Configuration
  7030. HC_COORDINATED_ATTACK_RADIUS = 600; // Coordination distance for assaults
  7031. HC_SUPPORT_REQUEST_COOLDOWN = 240; // Cooldown for support requests
  7032. HC_SUPPORT_MIN_ENEMY_BASE_DISTANCE = 600; // NEW: Minimum distance from enemy base to call support
  7033. HC_MORALE_BASE = 1.0; // Base morale multiplier
  7034. HC_SUPPLY_CHECK_INTERVAL = 60; // Supply check interval
  7035. HC_REINFORCEMENT_THRESHOLD = 0.8; // Reinforcement request threshold
  7036. HC_FLARE_COOLDOWN = 600; // 10 minute cooldown for flare support
  7037. HC_SMOKE_COOLDOWN = 900; // 15 minute cooldown for smoke support
  7038. HC_SMOKE_ROUNDS = 3; // Number of smoke shells
  7039. HC_DRONE_COOLDOWN = 900; // 15 minute cooldown for recon drone
  7040. HC_ARTILLERY_COOLDOWN = 1800; // 30 minute cooldown for artillery support
  7041. HC_ARTILLERY_ROUNDS = 5; // Number of artillery shells
  7042.  
  7043. // Group Lock Configuration (HC)
  7044. HC_LOCK_DURATION = 300; // Task persistence duration
  7045. HC_ENGAGEMENT_LOCK_RADIUS = 150; // Combat lock distance
  7046. HC_SUCCESS_UNLOCK_RADIUS = 100; // Success unlock distance
  7047. HC_COMBAT_LOCK_TIME = 180; // Combat lock duration
  7048.  
  7049. // Commander Personality Types (HC)
  7050. HC_COMMANDER_PERSONALITIES = ["AGGRESSIVE", "DEFENSIVE", "BALANCED", "CUNNING"];
  7051.  
  7052. // Player Purchase System Configuration (player_server.sqf)
  7053. PURCHASE_VEHICLE_DATA = createHashMapFromArray [
  7054. // Utility (Cost: 1)
  7055. ["B_Quadbike_01_F", ["Quad Bike", 1, west]],
  7056. ["O_Quadbike_01_F", ["Quad Bike", 1, east]],
  7057. // Light Vehicles (Cost: 10)
  7058. ["B_MRAP_01_hmg_F", ["Hunter HMG", 10, west]],
  7059. ["B_Heli_Light_01_F", ["Pawnee", 10, west]],
  7060. ["O_MRAP_02_hmg_F", ["Ifrit HMG", 10, east]],
  7061. ["O_Heli_Light_02_unarmed_F", ["Orca", 10, east]],
  7062. // Heavy Vehicles (Cost: 20)
  7063. ["B_APC_Wheeled_01_cannon_F", ["Marshall", 20, west]],
  7064. ["O_APC_Wheeled_02_rcws_v2_F", ["Marid", 20, east]],
  7065. // Tanks (Cost: 30)
  7066. ["B_MBT_01_cannon_F", ["Slammer", 30, west]],
  7067. ["O_MBT_02_cannon_F", ["Varsuk", 30, east]]
  7068. ];
  7069.  
  7070. // ==================== CHANGE START ====================
  7071. PLAYER_SQUAD_COST = 15;
  7072. PLAYER_SQUAD_COOLDOWN = 120; // 2 minutes
  7073. // ===================== CHANGE END =====================
  7074.  
  7075. // Compile and preprocess mission scripts
  7076. [] call compile preprocessFileLineNumbers "highcommand.sqf";
  7077. [] call compile preprocessFileLineNumbers "strategy.sqf";
  7078. [] call compile preprocessFileLineNumbers "botai.sqf";
  7079. [] call compile preprocessFileLineNumbers "performance.sqf";
  7080. [] call compile preprocessFileLineNumbers "AIstuff.sqf";
  7081. [] call compile preprocessFileLineNumbers "AAF.sqf";
  7082. [] call compile preprocessFileLineNumbers "cleanup.sqf";
  7083. [] call compile preprocessFileLineNumbers "arsenal.sqf";
  7084. if (isServer) then {
  7085. [] call compile preprocessFileLineNumbers "player_server.sqf";
  7086. };
  7087. if (hasInterface) then {
  7088. [] call compile preprocessFileLineNumbers "playerinit.sqf";
  7089. };
  7090.  
  7091. // Main mission setup
  7092. [] call compile preprocessFileLineNumbers "mission_setup.sqf";
  7093.  
  7094. fnc_disablePlayerStaminaAndSway = {
  7095. if (hasInterface) then {
  7096. waitUntil {!isNull player};
  7097. player enableStamina false;
  7098. player enableFatigue false;
  7099. player setCustomAimCoef 0;
  7100. while {true} do {
  7101. player setFatigue 0;
  7102. player setCustomAimCoef 0;
  7103. sleep 1;
  7104. };
  7105. };
  7106. };
  7107. [] call fnc_disablePlayerStaminaAndSway;
  7108.  
  7109. // Server-side loop to re-task lost groups and merge small groups
  7110. if (isServer) then {
  7111. [] spawn {
  7112. waitUntil {!isNil "mission_centralPoint" && !isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  7113.  
  7114. private _centerPos = missionNamespace getVariable ["mission_centralPoint", [worldSize/2, worldSize/2, 0]];
  7115. private _maxDistance = 5000;
  7116. private _maxHeliDistance = 8000;
  7117.  
  7118. while {true} do {
  7119. sleep 60;
  7120.  
  7121. // OPTIMIZATION: Pre-calculate base positions once per cycle
  7122. private _bluforBase = getPos bluforSpawnObj;
  7123. private _opforBase = getPos opforSpawnObj;
  7124.  
  7125. // OPTIMIZATION: Pre-filter groups more aggressively
  7126. private _aiGroups = allGroups select {
  7127. !isNull _x &&
  7128. count (units _x) > 0 &&
  7129. ({isPlayer _x} count (units _x) == 0) &&
  7130. (side _x in [west, east]) &&
  7131. {!isNull leader _x && alive leader _x} // Only process groups with valid leaders
  7132. };
  7133.  
  7134. // Check for small groups that need merging
  7135. {
  7136. [_x] call fnc_checkAndMergeSmallGroups;
  7137. } forEach _aiGroups;
  7138.  
  7139. // Re-filter after potential merging (some groups may have been deleted)
  7140. _aiGroups = allGroups select {
  7141. !isNull _x &&
  7142. count (units _x) > 0 &&
  7143. ({isPlayer _x} count (units _x) == 0) &&
  7144. (side _x in [west, east]) &&
  7145. {!isNull leader _x && alive leader _x}
  7146. };
  7147.  
  7148. {
  7149. private _group = _x;
  7150. private _leader = leader _group;
  7151. private _vehicle = vehicle _leader;
  7152. private _isVehicleGroup = (_vehicle != _leader);
  7153. private _side = side _group;
  7154. private _distanceFromCenter = _leader distance2D _centerPos;
  7155.  
  7156. // OPTIMIZATION: Pre-calculate positions based on side
  7157. private _enemyBasePos = if (_side == west) then {_opforBase} else {_bluforBase};
  7158. private _ownBasePos = if (_side == west) then {_bluforBase} else {_opforBase};
  7159.  
  7160. // Handle attack helicopters
  7161. if (_isVehicleGroup && {_vehicle isKindOf "Air"}) then {
  7162. // OPTIMIZATION: Only check waypoints if distance threshold exceeded
  7163. if (_distanceFromCenter > _maxHeliDistance) then {
  7164. private _wpPos = waypointPosition [_group, currentWaypoint _group];
  7165. private _hasValidWaypoints = (count (waypoints _group) > 0) && !(_wpPos isEqualTo [0,0,0]);
  7166.  
  7167. if (!_hasValidWaypoints) then {
  7168. [_group, _enemyBasePos, _centerPos] call fnc_createHelicopterAttackWaypoints;
  7169. };
  7170. };
  7171. } else {
  7172. // Ground unit logic
  7173. if (_distanceFromCenter > _maxDistance) then {
  7174. if (_isVehicleGroup) then {
  7175. [_group, _enemyBasePos, _ownBasePos] call fnc_createVehiclePatrolWaypoints;
  7176. } else {
  7177. [_group, _enemyBasePos] call fnc_createPatrolWaypoints;
  7178. };
  7179. };
  7180. };
  7181. } forEach _aiGroups;
  7182. };
  7183. };
  7184. };
  7185.  
  7186. ==================== END OF: init.sqf ====================
  7187.  
  7188.  
  7189.  
  7190. ==================== START OF: loadoutlimitations.sqf ====================
  7191.  
  7192. // Allowed gear lists for Virtual Arsenal
  7193.  
  7194. // BLUFOR allowed uniforms
  7195. BLUFOR_UNIFORMS = [
  7196. "U_B_CombatUniform_mcam",
  7197. "U_B_CombatUniform_mcam_tshirt",
  7198. "U_B_CombatUniform_mcam_vest",
  7199. "U_B_CombatUniform_mcam_worn",
  7200. "U_B_CTRG_1",
  7201. "U_B_CTRG_2",
  7202. "U_B_CTRG_3",
  7203. "U_B_CTRG_Soldier_F",
  7204. "U_B_CTRG_Soldier_2_F",
  7205. "U_B_CTRG_Soldier_3_F",
  7206. "U_B_CTRG_Soldier_urb_1_F",
  7207. "U_B_CTRG_Soldier_urb_2_F",
  7208. "U_B_CTRG_Soldier_urb_3_F",
  7209. "U_B_GhillieSuit",
  7210. "U_B_T_Soldier_F",
  7211. "U_B_T_Soldier_AR_F",
  7212. "U_B_T_Soldier_SL_F",
  7213. "U_B_T_Sniper_F",
  7214. "U_B_T_FullGhillie_tna_F",
  7215. "U_B_survival_uniform"
  7216. ];
  7217.  
  7218. // BLUFOR allowed vests
  7219. BLUFOR_VESTS = [
  7220. "V_PlateCarrier1_rgr",
  7221. "V_PlateCarrier2_rgr",
  7222. "V_PlateCarrierGL_rgr",
  7223. "V_PlateCarrierSpec_rgr",
  7224. "V_Chestrig_rgr",
  7225. "V_Chestrig_khk",
  7226. "V_Chestrig_oli",
  7227. "V_BandollierB_rgr",
  7228. "V_BandollierB_khk",
  7229. "V_BandollierB_oli"
  7230. ];
  7231.  
  7232. // BLUFOR allowed headgear
  7233. BLUFOR_HEADGEAR = [
  7234. "H_HelmetB",
  7235. "H_HelmetB_black",
  7236. "H_HelmetB_camo",
  7237. "H_HelmetB_desert",
  7238. "H_HelmetB_grass",
  7239. "H_HelmetB_sand",
  7240. "H_HelmetB_snakeskin",
  7241. "H_HelmetSpecB",
  7242. "H_HelmetSpecB_black",
  7243. "H_HelmetSpecB_paint2",
  7244. "H_HelmetSpecB_paint1",
  7245. "H_HelmetB_light",
  7246. "H_HelmetB_light_black",
  7247. "H_HelmetB_light_desert",
  7248. "H_HelmetB_light_grass",
  7249. "H_HelmetB_light_sand",
  7250. "H_HelmetB_light_snakeskin",
  7251. "H_Booniehat_mcam",
  7252. "H_Booniehat_khk",
  7253. "H_MilCap_mcam",
  7254. "H_Watchcap_camo",
  7255. "H_Bandanna_cbr",
  7256. "H_Bandanna_khk",
  7257. "H_Bandanna_sgg",
  7258. "H_Shemag_olive",
  7259. "H_ShemagOpen_tan",
  7260. "H_MilCap_mcamo" // ADDED: BLUFOR Officer Cap
  7261. ];
  7262.  
  7263. // OPFOR allowed uniforms
  7264. OPFOR_UNIFORMS = [
  7265. "U_O_CombatUniform_ocamo",
  7266. "U_O_CombatUniform_oucamo",
  7267. "U_O_OfficerUniform_ocamo",
  7268. "U_O_SpecopsUniform_ocamo",
  7269. "U_O_SpecopsUniform_blk",
  7270. "U_O_V_Soldier_Viper_F",
  7271. "U_O_V_Soldier_Viper_hex_F",
  7272. "U_O_GhillieSuit",
  7273. "U_O_T_Soldier_F",
  7274. "U_O_T_Officer_F",
  7275. "U_O_T_Sniper_F",
  7276. "U_O_T_FullGhillie_tna_F"
  7277. ];
  7278.  
  7279. // OPFOR allowed vests
  7280. OPFOR_VESTS = [
  7281. "V_TacVest_brn",
  7282. "V_TacVest_camo",
  7283. "V_TacVest_khk",
  7284. "V_TacVest_oli",
  7285. "V_HarnessO_brn",
  7286. "V_HarnessO_gry",
  7287. "V_HarnessOGL_brn",
  7288. "V_HarnessOGL_gry",
  7289. "V_BandollierB_cbr",
  7290. "V_BandollierB_khk",
  7291. "V_BandollierB_oli",
  7292. "V_BandollierB_rgr"
  7293. ];
  7294.  
  7295. // OPFOR allowed headgear
  7296. OPFOR_HEADGEAR = [
  7297. "H_HelmetO_ocamo",
  7298. "H_HelmetO_oucamo",
  7299. "H_HelmetLeaderO_ocamo",
  7300. "H_HelmetLeaderO_oucamo",
  7301. "H_HelmetSpecO_ocamo",
  7302. "H_HelmetSpecO_blk",
  7303. "H_Booniehat_ocamo",
  7304. "H_MilCap_ocamo",
  7305. "H_MilCap_oucamo",
  7306. "H_Bandanna_cbr",
  7307. "H_Bandanna_khk",
  7308. "H_Bandanna_sgg",
  7309. "H_Shemag_olive",
  7310. "H_ShemagOpen_tan",
  7311. "H_Beret_ocamo" // ADDED: OPFOR Officer Beret
  7312. ];
  7313.  
  7314. ==================== END OF: loadoutlimitations.sqf ====================
  7315.  
  7316.  
  7317.  
  7318. ==================== START OF: mission_setup.sqf ====================
  7319.  
  7320. // mission_setup.sqf
  7321. // Contains core mission initialization logic and main game loops.
  7322.  
  7323. // REVISED: The SITREP function is now simplified to report team points every 5 minutes.
  7324. fnc_broadcastSitRep = {
  7325. while {true} do {
  7326. sleep 300; // Report every 5 minutes
  7327.  
  7328. private _bluforPoints = round (missionNamespace getVariable ["BLUFOR_POINTS", 0]);
  7329. private _opforPoints = round (missionNamespace getVariable ["OPFOR_POINTS", 0]);
  7330.  
  7331. systemChat format ["[SITREP] Team Points | BLUFOR: %1 | OPFOR: %2", _bluforPoints, _opforPoints];
  7332. };
  7333. };
  7334.  
  7335. fnc_isPositionValidForBase = {
  7336. params ["_pos"];
  7337.  
  7338. // Check 1: Must not be in water.
  7339. if (surfaceIsWater _pos) exitWith {false};
  7340.  
  7341. // Check 2: Ground must not be too steep - made stricter for tower base access.
  7342. private _normal = surfaceNormal [(_pos select 0), (_pos select 1)];
  7343. if ((_normal select 2) < 0.98) exitWith {false};
  7344.  
  7345. // Check 3: IMPROVED - Must not be too close to the coast.
  7346. // Test multiple distances and more directions to ensure no water nearby
  7347. private _exclusionRadius = COAST_EXCLUSION_RADIUS;
  7348.  
  7349. // Check 16 directions instead of 8 for better coverage
  7350. for "_angle" from 0 to 337.5 step 22.5 do {
  7351. // Check at multiple distances from center outward
  7352. for "_dist" from 20 to _exclusionRadius step 20 do {
  7353. private _checkPos = _pos getPos [_dist, _angle];
  7354. if (surfaceIsWater _checkPos) exitWith {false};
  7355. };
  7356. };
  7357.  
  7358. // Additional concentric ring check at the exact exclusion radius
  7359. for "_angle" from 0 to 360 step 15 do {
  7360. private _checkPos = _pos getPos [_exclusionRadius, _angle];
  7361. if (surfaceIsWater _checkPos) exitWith {false};
  7362. };
  7363.  
  7364. // NEW Check 4: Ensure the entire 600m base marker radius is clear of water
  7365. // This is the radius used for the base area marker in fnc_createRandomizedBases
  7366. private _baseMarkerRadius = 600;
  7367.  
  7368. // Check perimeter of the base circle at 600m radius (24 points around the circle)
  7369. for "_angle" from 0 to 345 step 15 do {
  7370. private _perimeterPos = _pos getPos [_baseMarkerRadius, _angle];
  7371. if (surfaceIsWater _perimeterPos) exitWith {false};
  7372. };
  7373.  
  7374. // Check intermediate rings within the base circle to ensure no water inside
  7375. // Check at 200m, 400m, and 600m radius
  7376. for "_radius" from 200 to 600 step 200 do {
  7377. for "_angle" from 0 to 330 step 30 do {
  7378. private _ringPos = _pos getPos [_radius, _angle];
  7379. if (surfaceIsWater _ringPos) exitWith {false};
  7380. };
  7381. };
  7382.  
  7383. // Check 5: Must not have terrain objects (trees, rocks, etc.) within 20 meters.
  7384. private _terrainObjects = nearestTerrainObjects [_pos, ["TREE", "SMALL TREE", "BUSH", "ROCK", "ROCKS"], 20];
  7385. if (count _terrainObjects > 0) exitWith {false};
  7386.  
  7387. // Check 6: Must not have buildings or structures within 20 meters.
  7388. private _nearbyObjects = nearestObjects [_pos, ["Building", "House", "Wall", "Fence"], 20];
  7389. if (count _nearbyObjects > 0) exitWith {false};
  7390.  
  7391. // Check 7: Get ALL objects within 20 meters and filter out harmless ones.
  7392. private _allObjects = nearestObjects [_pos, [], 20];
  7393. private _blockingObjects = _allObjects select {
  7394. !(typeOf _x in ["Logic", "EmptyDetector"]) &&
  7395. !(_x isKindOf "Man") &&
  7396. !(_x isKindOf "Air") &&
  7397. !(_x isKindOf "WeaponHolder")
  7398. };
  7399. if (count _blockingObjects > 0) exitWith {false};
  7400.  
  7401. // If all checks pass, the position is valid.
  7402. true
  7403. };
  7404.  
  7405. // MODIFIED: Robust base placement to ensure balanced, opposing starting locations.
  7406. fnc_createRandomizedBases = {
  7407. if (!isServer) exitWith {};
  7408.  
  7409. // Ensure a default value is set if the variable isn't defined in init.sqf
  7410. if (isNil "RANDOMIZE_BASE_LOCATIONS") then { RANDOMIZE_BASE_LOCATIONS = 1; };
  7411.  
  7412. private _bluforPos = [];
  7413. private _opforPos = [];
  7414.  
  7415. if (RANDOMIZE_BASE_LOCATIONS == 0) then {
  7416. // --- MARKER-BASED BASE LOCATIONS ---
  7417. _bluforPos = markerPos "basemarker1";
  7418. _opforPos = markerPos "basemarker2";
  7419.  
  7420. private _centralPoint = [((_bluforPos select 0) + (_opforPos select 0)) / 2, ((_bluforPos select 1) + (_opforPos select 1)) / 2, 0];
  7421. missionNamespace setVariable ["mission_centralPoint", _centralPoint, true];
  7422.  
  7423. if (_bluforPos isEqualTo [0,0,0] || _opforPos isEqualTo [0,0,0]) then {
  7424. systemChat "ERROR: 'basemarker1' or 'basemarker2' not found. Mission cannot start correctly. Please place these markers in the editor.";
  7425. };
  7426. } else {
  7427. // --- NEW MULTI-STAGE RANDOMIZED BASE LOGIC ---
  7428. private _layoutFound = false;
  7429.  
  7430. // Step 1: Find all MEDIUM AND LARGE CITIES ONLY (no villages) to serve as potential battle locations
  7431. private _allTowns = nearestLocations [
  7432. getArray (configFile >> "CfgWorlds" >> worldName >> "centerPosition"),
  7433. ["NameCity", "NameCityCapital"],
  7434. worldSize
  7435. ];
  7436.  
  7437. // ==================== CHANGE START ====================
  7438. // Exclude Neochori from the list of potential central cities
  7439. _allTowns = _allTowns select { toLower (text _x) != "neochori" };
  7440. // ===================== CHANGE END =====================
  7441.  
  7442. // Shuffle the list of all towns to ensure variety in each session.
  7443. _allTowns = _allTowns call BIS_fnc_arrayShuffle;
  7444.  
  7445. // Helper function to find a valid spot within a search arc
  7446. private _fnc_findValidSpotInArc = {
  7447. params ["_center", "_distance", "_baseAngle"];
  7448. private _foundPos = [];
  7449. // Search in a 90-degree arc for a valid spot - INCREASED attempts from 15 to 30
  7450. for "_i" from 0 to 30 do {
  7451. private _testAngle = _baseAngle + (random 90) - 45;
  7452. private _potentialPos = _center getPos [_distance, _testAngle];
  7453. // NOW ACTUALLY USE THE VALIDATION FUNCTION
  7454. if ([_potentialPos] call fnc_isPositionValidForBase) exitWith {
  7455. _foundPos = _potentialPos;
  7456. };
  7457. };
  7458. _foundPos
  7459. };
  7460.  
  7461. // Step 2: Iterate through the shuffled list of all towns and try to build a valid layout around them.
  7462. {
  7463. private _centralCity = _x;
  7464. private _centralPos = position _centralCity;
  7465.  
  7466. // Skip if the central city itself is too close to water
  7467. if (surfaceIsWater _centralPos) then { continue; };
  7468.  
  7469. // Try 20 different conflict axes for the current town (INCREASED from 10)
  7470. for "_i" from 1 to 20 do {
  7471. // The distance is now randomized for each placement attempt.
  7472. // 1000m base + (100 to 700m random) = 1100m to 1700m total distance.
  7473. private _baseDistance = 1400 + (random 300);
  7474.  
  7475. private _conflictAxis = random 360;
  7476.  
  7477. // Find a spot for BLUFOR
  7478. private _potentialBluforPos = [_centralPos, _baseDistance, _conflictAxis] call _fnc_findValidSpotInArc;
  7479.  
  7480. if (count _potentialBluforPos > 0) then {
  7481. // Find a spot for OPFOR on the opposite side
  7482. private _potentialOpforPos = [_centralPos, _baseDistance, _conflictAxis + 180] call _fnc_findValidSpotInArc;
  7483.  
  7484. if (count _potentialOpforPos > 0) then {
  7485. // DOUBLE CHECK both positions are valid
  7486. if ([_potentialBluforPos] call fnc_isPositionValidForBase && [_potentialOpforPos] call fnc_isPositionValidForBase) then {
  7487. // SUCCESS! We have a valid town and two opposing bases.
  7488. _bluforPos = _potentialBluforPos;
  7489. _opforPos = _potentialOpforPos;
  7490. missionNamespace setVariable ["mission_centralPoint", _centralPos, true];
  7491. _layoutFound = true;
  7492. break; // Exit the conflict axis loop
  7493. };
  7494. };
  7495. };
  7496. };
  7497.  
  7498. if (_layoutFound) then {
  7499. break; // Exit the town iteration loop
  7500. };
  7501. } forEach _allTowns; // This now iterates through the full, shuffled list.
  7502. };
  7503.  
  7504. // --- COMMON BASE CREATION LOGIC ---
  7505.  
  7506. // Emergency fallback if the entire random placement process fails
  7507. if (count _bluforPos == 0 || count _opforPos == 0) then {
  7508. _bluforPos = markerPos "basemarker1";
  7509. _opforPos = markerPos "basemarker2";
  7510. private _centralPoint = [((_bluforPos select 0) + (_opforPos select 0)) / 2, ((_bluforPos select 1) + (_opforPos select 1)) / 2, 0];
  7511. missionNamespace setVariable ["mission_centralPoint", _centralPoint, true];
  7512. };
  7513.  
  7514. // Create BLUFOR spawn object
  7515. bluforSpawnObj = createVehicle ["Land_Cargo_Tower_V1_F", _bluforPos, [], 0, "NONE"];
  7516. bluforSpawnObj allowDamage false;
  7517. bluforSpawnObj enableSimulationGlobal false;
  7518. bluforSpawnObj setVectorUp [0,0,1];
  7519.  
  7520. private _bluforGroundPos = getPosATL bluforSpawnObj;
  7521. _bluforGroundPos set [2, 0];
  7522. bluforSpawnObj setPosATL _bluforGroundPos;
  7523. publicVariable "bluforSpawnObj";
  7524.  
  7525. // Create OPFOR spawn object
  7526. opforSpawnObj = createVehicle ["Land_Cargo_Tower_V3_F", _opforPos, [], 0, "NONE"];
  7527. opforSpawnObj allowDamage false;
  7528. opforSpawnObj enableSimulationGlobal false;
  7529. opforSpawnObj setVectorUp [0,0,1];
  7530.  
  7531. private _opforGroundPos = getPosATL opforSpawnObj;
  7532. _opforGroundPos set [2, 0];
  7533. opforSpawnObj setPosATL _opforGroundPos;
  7534. publicVariable "opforSpawnObj";
  7535.  
  7536. // Create BLUFOR Base Area Marker with a randomized offset
  7537. private _bluforMarkerCenter = _bluforPos getPos [random 300, random 360];
  7538. private _bluforMarker = createMarker ["blufor_base_area", _bluforMarkerCenter];
  7539. _bluforMarker setMarkerShape "ELLIPSE";
  7540. _bluforMarker setMarkerSize [600, 600];
  7541. _bluforMarker setMarkerType "mil_unknown";
  7542. _bluforMarker setMarkerColor "ColorBlue";
  7543. _bluforMarker setMarkerBrush "Border";
  7544. _bluforMarker setMarkerText "";
  7545.  
  7546. // Create OPFOR Base Area Marker with a randomized offset
  7547. private _opforMarkerCenter = _opforPos getPos [random 300, random 360];
  7548. private _opforMarker = createMarker ["opfor_base_area", _opforMarkerCenter];
  7549. _opforMarker setMarkerShape "ELLIPSE";
  7550. _opforMarker setMarkerSize [600, 600];
  7551. _opforMarker setMarkerType "mil_unknown";
  7552. _opforMarker setMarkerColor "ColorRed";
  7553. _opforMarker setMarkerBrush "Border";
  7554. _opforMarker setMarkerText "";
  7555. };
  7556.  
  7557. fnc_decorateFOB = {
  7558. params ["_baseCenterObj", "_side"];
  7559.  
  7560. private _basePos = getPosATL _baseCenterObj;
  7561.  
  7562. private _sideFlag = if (_side == west) then { "Flag_NATO_F" } else { "Flag_CSAT_F" };
  7563. private _sideCamoNet = if (_side == west) then { "Land_CamoNet_BLUFOR_F" } else { "Land_CamoNet_OPFOR_F" };
  7564. private _sideTower = if (_side == west) then { "Land_Cargo_Patrol_V4_F" } else { "Land_Cargo_Patrol_V3_F" };
  7565.  
  7566. // Calculate direction to enemy base for barrier placement
  7567. private _enemyBasePos = if (_side == west) then {
  7568. if (!isNil "opforSpawnObj") then {getPos opforSpawnObj} else {[0,0,0]}
  7569. } else {
  7570. if (!isNil "bluforSpawnObj") then {getPos bluforSpawnObj} else {[0,0,0]}
  7571. };
  7572.  
  7573. private _dirToEnemy = _basePos getDir _enemyBasePos;
  7574.  
  7575. // Create square wall perimeter around tower with gaps
  7576. private _wallDistance = 15; // Distance from center to walls
  7577. private _wallSegments = [
  7578. // North wall (2 segments with gap in middle)
  7579. [[-_wallDistance, _wallDistance, 0], 0, "Land_HBarrierWall6_F"],
  7580. [[_wallDistance * 0.5, _wallDistance, 0], 0, "Land_HBarrierWall6_F"],
  7581.  
  7582. // South wall (2 segments with gap in middle)
  7583. [[-_wallDistance, -_wallDistance, 0], 180, "Land_HBarrierWall6_F"],
  7584. [[_wallDistance * 0.5, -_wallDistance, 0], 180, "Land_HBarrierWall6_F"],
  7585.  
  7586. // East wall (2 segments with gap in middle)
  7587. [[_wallDistance, -_wallDistance * 0.5, 0], 90, "Land_HBarrierWall6_F"],
  7588. [[_wallDistance, _wallDistance * 0.5, 0], 90, "Land_HBarrierWall4_F"],
  7589.  
  7590. // West wall (2 segments with gap in middle)
  7591. [[-_wallDistance, -_wallDistance * 0.5, 0], 270, "Land_HBarrierWall6_F"],
  7592. [[-_wallDistance, _wallDistance * 0.5, 0], 270, "Land_HBarrierWall4_F"]
  7593. ];
  7594.  
  7595. // Create the wall segments
  7596. {
  7597. private _relPos = _x select 0;
  7598. private _dir = _x select 1;
  7599. private _className = _x select 2;
  7600.  
  7601. private _worldPos = _basePos vectorAdd _relPos;
  7602. private _wall = createVehicle [_className, _worldPos, [], 0, "NONE"];
  7603.  
  7604. private _wallGroundPos = getPosATL _wall;
  7605. _wallGroundPos set [2, 0];
  7606. _wall setPosATL _wallGroundPos;
  7607. _wall setDir _dir;
  7608. _wall allowDamage true;
  7609. _wall enableSimulationGlobal false;
  7610. _wall setVectorUp [0,0,1];
  7611. } forEach _wallSegments;
  7612.  
  7613. // Create defensive barriers facing enemy base (30 meters from tower)
  7614. private _barrierDistance = 30;
  7615. private _barrierCount = 5; // Number of barriers in the arc
  7616. private _arcWidth = 120; // Total arc width in degrees
  7617.  
  7618. for "_i" from 0 to (_barrierCount - 1) do {
  7619. // Calculate angle for each barrier in the arc
  7620. private _angleOffset = -(_arcWidth / 2) + (_i * (_arcWidth / (_barrierCount - 1)));
  7621. private _barrierDir = _dirToEnemy + _angleOffset;
  7622.  
  7623. // Calculate position for barrier
  7624. private _barrierPos = _basePos getPos [_barrierDistance, _barrierDir];
  7625.  
  7626. // Create the barrier
  7627. private _barrier = createVehicle ["Land_HBarrierBig_F", _barrierPos, [], 0, "NONE"];
  7628.  
  7629. private _barrierGroundPos = getPosATL _barrier;
  7630. _barrierGroundPos set [2, 0];
  7631. _barrier setPosATL _barrierGroundPos;
  7632. _barrier setDir (_barrierDir); // Face towards enemy
  7633. _barrier allowDamage true;
  7634. _barrier enableSimulationGlobal false;
  7635. _barrier setVectorUp [0,0,1];
  7636. };
  7637.  
  7638. // Additional corner reinforcement barriers
  7639. private _cornerBarriers = [
  7640. [_barrierDistance, _dirToEnemy - 70],
  7641. [_barrierDistance, _dirToEnemy + 70]
  7642. ];
  7643.  
  7644. {
  7645. private _distance = _x select 0;
  7646. private _dir = _x select 1;
  7647.  
  7648. private _pos = _basePos getPos [_distance, _dir];
  7649. private _barrier = createVehicle ["Land_HBarrierWall_corner_F", _pos, [], 0, "NONE"];
  7650.  
  7651. private _groundPos = getPosATL _barrier;
  7652. _groundPos set [2, 0];
  7653. _barrier setPosATL _groundPos;
  7654. _barrier setDir _dir;
  7655. _barrier allowDamage true;
  7656. _barrier enableSimulationGlobal false;
  7657. _barrier setVectorUp [0,0,1];
  7658. } forEach _cornerBarriers;
  7659.  
  7660. // Fix watchtower positioning: Create at position, then place on ground
  7661. private _towerPosRel = [-70, -70, 0];
  7662. private _towerWorldPos = _basePos vectorAdd _towerPosRel;
  7663.  
  7664. private _towerObj = createVehicle [_sideTower, _towerWorldPos, [], 0, "NONE"];
  7665. _towerObj setDir 225;
  7666. _towerObj allowDamage false;
  7667. _towerObj enableSimulationGlobal false;
  7668. _towerObj setVectorUp [0,0,1];
  7669.  
  7670. private _towerGroundPos = getPosATL _towerObj;
  7671. _towerGroundPos set [2, 0];
  7672. _towerObj setPosATL _towerGroundPos;
  7673.  
  7674. private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
  7675. };
  7676.  
  7677. // Create and manage the play area restriction zone
  7678. fnc_createPlayArea = {
  7679. if (!isServer) exitWith {};
  7680.  
  7681. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  7682.  
  7683. private _bluforPos = getPos bluforSpawnObj;
  7684. private _opforPos = getPos opforSpawnObj;
  7685.  
  7686. // Calculate center point between bases
  7687. private _centerX = ((_bluforPos select 0) + (_opforPos select 0)) / 2;
  7688. private _centerY = ((_bluforPos select 1) + (_opforPos select 1)) / 2;
  7689. private _playAreaCenter = [_centerX, _centerY, 0];
  7690.  
  7691. // Calculate required radius (distance between bases / 2 + 200m buffer)
  7692. private _baseDistance = _bluforPos distance2D _opforPos;
  7693. private _playAreaRadius = (_baseDistance / 2) + 200;
  7694.  
  7695. // Store globally for reference
  7696. missionNamespace setVariable ["PLAY_AREA_CENTER", _playAreaCenter, true];
  7697. missionNamespace setVariable ["PLAY_AREA_RADIUS", _playAreaRadius, true];
  7698.  
  7699. // Create visual marker for the play area
  7700. private _marker = createMarker ["play_area_boundary", _playAreaCenter];
  7701. _marker setMarkerShape "ELLIPSE";
  7702. _marker setMarkerSize [_playAreaRadius, _playAreaRadius];
  7703. _marker setMarkerColor "ColorRed";
  7704. _marker setMarkerBrush "Border";
  7705. _marker setMarkerAlpha 0.5;
  7706.  
  7707. // Spawn Land_Wall_Tin_4 objects around the perimeter to mark the boundary
  7708. private _tapeSignSpacing = 12.5; // Spawn a sign every 12.5 meters around the perimeter (doubled from 25)
  7709. private _circumference = 2 * pi * _playAreaRadius;
  7710. private _numberOfSigns = floor (_circumference / _tapeSignSpacing);
  7711. private _angleStep = 360 / _numberOfSigns;
  7712. private _signsPerStack = 5; // Number of signs to stack vertically at each position
  7713. private _stackSpacing = 3; // Vertical spacing between stacked signs in meters
  7714. private _totalSignsSpawned = 0;
  7715.  
  7716. // Calculate sleep delay to spread spawning over 10 seconds
  7717. private _sleepDelay = 10 / _numberOfSigns;
  7718.  
  7719. for "_i" from 0 to (_numberOfSigns - 1) do {
  7720. private _angle = _i * _angleStep;
  7721. private _signPos = _playAreaCenter getPos [_playAreaRadius, _angle];
  7722.  
  7723. // Spawn 5 signs stacked vertically at this position
  7724. for "_stack" from 0 to (_signsPerStack - 1) do {
  7725. private _height = 10 + (_stack * _stackSpacing); // Start at 10m, then 13m, 16m, 19m, 22m
  7726. private _stackedPos = +_signPos;
  7727. _stackedPos set [2, _height];
  7728.  
  7729. private _sign = createVehicle ["Land_Wall_Tin_4", _stackedPos, [], 0, "CAN_COLLIDE"];
  7730. _sign setPosATL _stackedPos;
  7731. _sign setDir _angle; // Orient the sign to face radially
  7732.  
  7733. // Freeze the sign in place: disable simulation prevents physics and collision
  7734. _sign enableSimulation false;
  7735. _sign enableSimulationGlobal false;
  7736. _sign allowDamage false;
  7737.  
  7738. // Remove any possible interactions
  7739. removeAllActions _sign;
  7740.  
  7741. _totalSignsSpawned = _totalSignsSpawned + 1;
  7742. };
  7743.  
  7744. // Stagger spawning to reduce lag spike
  7745. sleep _sleepDelay;
  7746. };
  7747.  
  7748. diag_log format ["Play Area Created - Center: %1, Radius: %2m, Wall Positions: %3, Total Walls Spawned: %4", _playAreaCenter, round _playAreaRadius, _numberOfSigns, _totalSignsSpawned];
  7749.  
  7750. // Start player monitoring
  7751. [] spawn fnc_monitorPlayAreaPlayers;
  7752.  
  7753. // Start AI waypoint validation
  7754. [] spawn fnc_monitorPlayAreaAI;
  7755. };
  7756.  
  7757. // Monitor players and warn/kill those outside play area
  7758. fnc_monitorPlayAreaPlayers = {
  7759. if (!isServer) exitWith {};
  7760.  
  7761. waitUntil {!isNil "PLAY_AREA_CENTER" && !isNil "PLAY_AREA_RADIUS"};
  7762.  
  7763. private _center = missionNamespace getVariable "PLAY_AREA_CENTER";
  7764. private _radius = missionNamespace getVariable "PLAY_AREA_RADIUS";
  7765.  
  7766. while {true} do {
  7767. {
  7768. if (isPlayer _x && alive _x && side _x != civilian) then {
  7769. private _player = _x;
  7770. private _distance = _player distance2D _center;
  7771.  
  7772. if (_distance > _radius) then {
  7773. // Player is outside play area
  7774. private _warningTime = _player getVariable ["playAreaWarningTime", 0];
  7775.  
  7776. if (_warningTime == 0) then {
  7777. // First warning
  7778. _player setVariable ["playAreaWarningTime", time, true];
  7779. [_center, _radius] remoteExecCall ["fnc_client_showPlayAreaWarning", owner _player];
  7780. } else {
  7781. // Check if 10 seconds have passed
  7782. if (time - _warningTime >= 10) then {
  7783. // Kill the player
  7784. _player setDamage 1;
  7785. _player setVariable ["playAreaWarningTime", 0, true];
  7786. ["You were eliminated for leaving the battle area!"] remoteExecCall ["hint", owner _player];
  7787. };
  7788. };
  7789. } else {
  7790. // Player is inside, clear any warnings
  7791. if ((_player getVariable ["playAreaWarningTime", 0]) > 0) then {
  7792. _player setVariable ["playAreaWarningTime", 0, true];
  7793. [] remoteExecCall ["fnc_client_hidePlayAreaWarning", owner _player];
  7794. };
  7795. };
  7796. };
  7797. } forEach allPlayers;
  7798.  
  7799. sleep 1;
  7800. };
  7801. };
  7802.  
  7803. // Monitor AI groups and prevent waypoints outside play area
  7804. fnc_monitorPlayAreaAI = {
  7805. if (!isServer) exitWith {};
  7806.  
  7807. waitUntil {!isNil "PLAY_AREA_CENTER" && !isNil "PLAY_AREA_RADIUS" && !isNil "mission_centralPoint"};
  7808.  
  7809. private _center = missionNamespace getVariable "PLAY_AREA_CENTER";
  7810. private _radius = missionNamespace getVariable "PLAY_AREA_RADIUS";
  7811. private _strongholdPos = missionNamespace getVariable "mission_centralPoint";
  7812.  
  7813. while {true} do {
  7814. {
  7815. private _group = _x;
  7816. if (!isNull _group && count (units _group) > 0 && {!isPlayer leader _group}) then {
  7817. private _leader = leader _group;
  7818.  
  7819. // Skip if group is in helicopters (exception for air units)
  7820. private _isAirUnit = false;
  7821. if (!isNull _leader && vehicle _leader != _leader) then {
  7822. if ((vehicle _leader) isKindOf "Air") then {
  7823. _isAirUnit = true;
  7824. };
  7825. };
  7826.  
  7827. if (!_isAirUnit) then {
  7828. // Check all waypoints
  7829. private _waypoints = waypoints _group;
  7830. {
  7831. private _wpPos = waypointPosition _x;
  7832. // Safety check for [0,0,0] waypoints
  7833. if (count _wpPos >= 2 && !(_wpPos isEqualTo [0,0,0])) then {
  7834. private _wpDistance = _wpPos distance2D _center;
  7835. if (_wpDistance > _radius) then {
  7836. deleteWaypoint _x;
  7837. };
  7838. } else {
  7839. // Remove invalid [0,0,0] waypoints immediately
  7840. if (_wpPos isEqualTo [0,0,0]) then { deleteWaypoint _x; };
  7841. };
  7842. } forEach _waypoints;
  7843.  
  7844. // Check Group Positions
  7845. private _groupResetNeeded = false;
  7846.  
  7847. // 1. Check Leader (Triggers Group Reset)
  7848. if (!isNull _leader && alive _leader) then {
  7849. if ((_leader distance2D _center) > _radius) then {
  7850. _groupResetNeeded = true;
  7851. };
  7852. };
  7853.  
  7854. if (_groupResetNeeded) then {
  7855. // --- LEADER IS OUT: Teleport entire group back to THEIR BASE ---
  7856.  
  7857. private _side = side _group;
  7858. private _respawnPos = _strongholdPos; // Default fallback to center (for AAF/Indep)
  7859.  
  7860. if (_side == west) then {
  7861. if (!isNil "bluforSpawnObj") then {
  7862. // Find safe spot near BLUFOR base
  7863. _respawnPos = [getPos bluforSpawnObj, 15, 60, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
  7864. if (count _respawnPos == 0) then { _respawnPos = getPos bluforSpawnObj; };
  7865. };
  7866. };
  7867.  
  7868. if (_side == east) then {
  7869. if (!isNil "opforSpawnObj") then {
  7870. // Find safe spot near OPFOR base
  7871. _respawnPos = [getPos opforSpawnObj, 15, 60, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
  7872. if (count _respawnPos == 0) then { _respawnPos = getPos opforSpawnObj; };
  7873. };
  7874. };
  7875.  
  7876. // Teleport entire group
  7877. {
  7878. if (alive _x) then {
  7879. // If driver of vehicle, move vehicle. Else move unit.
  7880. if (vehicle _x != _x && driver (vehicle _x) == _x) then {
  7881. (vehicle _x) setPos _respawnPos;
  7882. (vehicle _x) setVelocity [0,0,0];
  7883. } else {
  7884. _x setPos _respawnPos;
  7885. };
  7886. };
  7887. } forEach (units _group);
  7888.  
  7889. // Clear old tasks immediately
  7890. while {count (waypoints _group) > 0} do {
  7891. deleteWaypoint ((waypoints _group) select 0);
  7892. };
  7893.  
  7894. // Give them a new task to move towards the center battlefield.
  7895. private _wp = _group addWaypoint [_strongholdPos, 0];
  7896. _wp setWaypointType "MOVE";
  7897. _wp setWaypointBehaviour "AWARE"; // Back to AWARE since they are safe at base
  7898. _wp setWaypointSpeed "FULL";
  7899. _wp setWaypointCompletionRadius 50;
  7900.  
  7901. // Apply a 60-second HC lock to ensure they start moving back to the fight
  7902. _group setVariable ["HC_FORCED_LOCK", true];
  7903.  
  7904. // Restore combat mode
  7905. _group setCombatMode "YELLOW";
  7906.  
  7907. [_group] spawn {
  7908. params ["_grp"];
  7909. sleep 60;
  7910. if (!isNull _grp) then {
  7911. _grp setVariable ["HC_FORCED_LOCK", false];
  7912. };
  7913. };
  7914.  
  7915. diag_log format ["PLAY AREA RESET: Group %1 teleported back to base.", _group];
  7916. } else {
  7917. // --- LEADER IS IN: Check for individual stragglers ---
  7918. {
  7919. private _unit = _x;
  7920. // Only check living units, on foot, that are NOT the leader
  7921. if (alive _unit && _unit != _leader && vehicle _unit == _unit) then {
  7922. if ((_unit distance2D _center) > _radius) then {
  7923.  
  7924. // Teleport straggler to leader
  7925. private _targetPos = [];
  7926. if (!isNull _leader && alive _leader) then {
  7927. _targetPos = (getPosATL _leader) vectorAdd [random 4 - 2, random 4 - 2, 0];
  7928. } else {
  7929. // If leader dead/null, teleport to center stronghold
  7930. _targetPos = _strongholdPos;
  7931. };
  7932.  
  7933. if (count _targetPos > 0) then {
  7934. _unit setPosATL _targetPos;
  7935. _unit setVelocity [0,0,0];
  7936. };
  7937. };
  7938. };
  7939. } forEach (units _group);
  7940. };
  7941. };
  7942. };
  7943. } forEach allGroups;
  7944.  
  7945. sleep 3;
  7946. };
  7947. };
  7948.  
  7949. // Player capture related functions
  7950. fnc_captureInProgress = { params ["_unit"]; _unit getVariable ["capturing", false] };
  7951.  
  7952. fnc_startCapture = {
  7953. params ["_unit", "_targetBase", "_targetSide"];
  7954. if ([_unit] call fnc_captureInProgress) exitWith { systemChat "Already capturing!"; };
  7955. _unit setVariable ["capturing", true];
  7956. _unit setVariable ["captureProgress", 0];
  7957. private _captureStartTime = time;
  7958.  
  7959. [_unit, _targetBase, _targetSide, _captureStartTime] spawn {
  7960. params ["_unit", "_targetBase", "_targetSide", "_startTime"];
  7961. while {alive _unit && [_unit] call fnc_captureInProgress && (_unit distance _targetBase) < 5} do {
  7962. private _progress = ((time - _startTime) / 60) * 100;
  7963. _unit setVariable ["captureProgress", _progress];
  7964. hintSilent format ["Capturing %1 Base: %2%3", _targetSide, round _progress, "%"];
  7965. if (_progress >= 100) exitWith {
  7966. _unit setVariable ["capturing", false];
  7967. hint "";
  7968. private _winnerSide = if (side _unit == west) then {"BLUFOR"} else {"OPFOR"};
  7969. private _loserSide = if (_targetSide == "OPFOR") then {"OPFOR"} else {"BLUFOR"};
  7970. // ==================== CHANGE START ====================
  7971. // Use the new centralized function to end the round.
  7972. private _reason = format ["%1 has captured the %2 base!", _winnerSide, _loserSide];
  7973. [_winnerSide, _loserSide, _reason] call fnc_endRound;
  7974. // ===================== CHANGE END =====================
  7975. };
  7976. sleep 0.1;
  7977. };
  7978. _unit setVariable ["capturing", false];
  7979. _unit setVariable ["captureProgress", 0];
  7980. hint "";
  7981. if (!alive _unit) then {
  7982. systemChat "Capture failed - capturer died!";
  7983. } else {
  7984. if ((_unit distance _targetBase) >= 5) then {
  7985. systemChat "Capture failed - moved too far from base!";
  7986. };
  7987. };
  7988. };
  7989. };
  7990.  
  7991. fnc_cancelCapture = { params ["_unit"]; _unit setVariable ["capturing", false]; _unit setVariable ["captureProgress", 0]; hint ""; systemChat "Capture cancelled!"; };
  7992.  
  7993. fnc_setupCaptureActions = {
  7994. if (!hasInterface) exitWith {};
  7995. [] spawn {
  7996. waitUntil {!isNull player};
  7997. sleep 1;
  7998. private _captureActionBlufor = -1;
  7999. private _captureActionOpfor = -1;
  8000. private _cancelActionID = -1;
  8001. while {true} do {
  8002. private _playerSide = side player;
  8003. private _nearOpforBase = (player distance opforSpawnObj) < 5;
  8004. private _nearBluforBase = (player distance bluforSpawnObj) < 5;
  8005.  
  8006. if (_playerSide == west && _nearOpforBase && _captureActionOpfor == -1 && !([player] call fnc_captureInProgress)) then {
  8007. _captureActionOpfor = player addAction [
  8008. "<t color='#FF0000'>Capture OPFOR Base</t>",
  8009. {[_this select 0, opforSpawnObj, "OPFOR"] call fnc_startCapture;},
  8010. nil, 10, true, true, "", "true", 5
  8011. ];
  8012. };
  8013. if (_playerSide == west && (!_nearOpforBase || [player] call fnc_captureInProgress) && _captureActionOpfor != -1) then {
  8014. player removeAction _captureActionOpfor; _captureActionOpfor = -1;
  8015. };
  8016.  
  8017. if (_playerSide == east && _nearBluforBase && _captureActionBlufor == -1 && !([player] call fnc_captureInProgress)) then {
  8018. _captureActionBlufor = player addAction [
  8019. "<t color='#0000FF'>Capture BLUFOR Base</t>",
  8020. {[_this select 0, bluforSpawnObj, "BLUFOR"] call fnc_startCapture;},
  8021. nil, 10, true, true, "", "true", 5
  8022. ];
  8023. };
  8024. if (_playerSide == east && (!_nearBluforBase || [player] call fnc_captureInProgress) && _captureActionBlufor != -1) then {
  8025. player removeAction _captureActionBlufor; _captureActionBlufor = -1;
  8026. };
  8027.  
  8028. if ([player] call fnc_captureInProgress && _cancelActionID == -1) then {
  8029. _cancelActionID = player addAction [
  8030. "<t color='#FFFF00'>Cancel Capture</t>",
  8031. {[_this select 0] call fnc_cancelCapture;},
  8032. nil, 11, true, true, "", "true"
  8033. ];
  8034. };
  8035. if (!([player] call fnc_captureInProgress) && _cancelActionID != -1) then {
  8036. player removeAction _cancelActionID; _cancelActionID = -1;
  8037. };
  8038. sleep 0.5;
  8039. };
  8040. };
  8041. };
  8042.  
  8043. // Function to get unit value for kill rewards
  8044. fnc_getUnitValue = {
  8045. params ["_unit"];
  8046. private "_value";
  8047. if (_unit getVariable ["isAAF", false]) then {
  8048. _value = 1;
  8049. } else {
  8050. if (_unit getVariable ["isElite", false]) then {
  8051. _value = ELITE_VALUE;
  8052. } else {
  8053. if (_unit getVariable ["isSpecOps", false]) then {
  8054. _value = SPECOPS_VALUE;
  8055. } else {
  8056. if (_unit getVariable ["isUpgraded", false]) then {
  8057. _value = UPGRADED_VALUE;
  8058. } else {
  8059. if (_unit getVariable ["isSniper", false]) then {
  8060. _value = SNIPER_VALUE;
  8061. } else {
  8062. _value = INFANTRY_VALUE;
  8063. };
  8064. };
  8065. };
  8066. };
  8067. };
  8068. _value
  8069. };
  8070.  
  8071. // ==================== CHANGE START ====================
  8072. // NEW: Centralized function to end the mission and declare a winner.
  8073. fnc_endRound = {
  8074. params ["_winnerSide", "_loserSide", "_reason"];
  8075. if (!isServer) exitWith {};
  8076.  
  8077. // Prevent this function from running more than once if multiple triggers happen at the same time.
  8078. if (missionNamespace getVariable ["missionEnding", false]) exitWith {};
  8079. missionNamespace setVariable ["missionEnding", true, true];
  8080.  
  8081. private _winnerSideName = toUpper _winnerSide;
  8082.  
  8083. private _endMessage = format ["%1 WINS! %2", _winnerSideName, _reason];
  8084. [
  8085. "EndMission",
  8086. ["", _endMessage]
  8087. ] remoteExec ["BIS_fnc_showNotification", 0]; // Show a large notification to all players.
  8088.  
  8089. private _endType = if (_winnerSide == "BLUFOR") then {"end1"} else {"end2"};
  8090.  
  8091. // End the mission after a short delay to let players see the message.
  8092. sleep 5;
  8093. [_endType, true, true] remoteExec ["BIS_fnc_endMission", 0];
  8094. };
  8095.  
  8096. // Function to prevent infantry from swimming
  8097. fnc_preventInfantrySwimming = {
  8098. if (!isServer) exitWith {};
  8099.  
  8100. [] spawn {
  8101. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  8102. sleep 30; // Wait for mission to stabilize
  8103.  
  8104. while {true} do {
  8105. // Get all AI infantry groups
  8106. private _infantryGroups = allGroups select {
  8107. !isNull _x &&
  8108. count (units _x) > 0 &&
  8109. ({isPlayer _x} count (units _x) == 0) &&
  8110. (side _x in [west, east, independent]) &&
  8111. {
  8112. private _leader = leader _x;
  8113. !isNull _leader &&
  8114. alive _leader &&
  8115. vehicle _leader == _leader // Only check infantry on foot
  8116. }
  8117. };
  8118.  
  8119. {
  8120. private _group = _x;
  8121. private _leader = leader _group;
  8122. private _leaderPos = getPosASL _leader;
  8123.  
  8124. // Check if leader is in water
  8125. if (surfaceIsWater _leaderPos) then {
  8126. // Check cooldown to prevent spam
  8127. private _lastRedirect = _group getVariable ["lastSwimRedirect", 0];
  8128.  
  8129. if (time - _lastRedirect > 10) then {
  8130. _group setVariable ["lastSwimRedirect", time];
  8131.  
  8132. // Find nearest land position
  8133. private _landPos = [];
  8134. private _searchRadius = 50;
  8135. private _maxSearchRadius = 300;
  8136.  
  8137. // Expand search radius until land is found
  8138. while {count _landPos == 0 && _searchRadius <= _maxSearchRadius} do {
  8139. for "_angle" from 0 to 330 step 30 do {
  8140. private _testPos = _leaderPos getPos [_searchRadius, _angle];
  8141. if (!surfaceIsWater _testPos) exitWith {
  8142. _landPos = _testPos;
  8143. };
  8144. };
  8145.  
  8146. if (count _landPos == 0) then {
  8147. _searchRadius = _searchRadius + 50;
  8148. };
  8149. };
  8150.  
  8151. // If land found, create forced waypoint
  8152. if (count _landPos > 0) then {
  8153. // Clear existing waypoints
  8154. while {count (waypoints _group) > 0} do {
  8155. deleteWaypoint ((waypoints _group) select 0);
  8156. };
  8157.  
  8158. // Create urgent move waypoint to land
  8159. private _wp = _group addWaypoint [_landPos, 0];
  8160. _wp setWaypointType "MOVE";
  8161. _wp setWaypointSpeed "FULL";
  8162. _wp setWaypointBehaviour "AWARE";
  8163. _wp setWaypointCompletionRadius 30;
  8164.  
  8165. // Force immediate compliance
  8166. _group setSpeedMode "FULL";
  8167. {
  8168. if (alive _x) then {
  8169. _x doMove _landPos;
  8170. };
  8171. } forEach (units _group);
  8172.  
  8173. diag_log format ["[SWIM PREVENTION] Redirected group %1 from water at %2 to land at %3",
  8174. groupId _group, _leaderPos, _landPos];
  8175. };
  8176. };
  8177. };
  8178. } forEach _infantryGroups;
  8179.  
  8180. sleep 10; // Check every 10 seconds
  8181. };
  8182. };
  8183. };
  8184.  
  8185. if (isServer) then {
  8186. // ==================== CHANGE START ====================
  8187. // Initialize the hash map for storing player team selections to prevent team switching.
  8188. missionNamespace setVariable ["PLAYER_TEAM_LOCKS", createHashMap, true];
  8189. // ===================== CHANGE END =====================
  8190.  
  8191. // Initialize randomized bases first (blocking call)
  8192. [] call fnc_createRandomizedBases;
  8193.  
  8194. // Main server-side loops and event handlers
  8195. [] spawn {
  8196. // Wait until all base objects are public and known to missionNamespace
  8197. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  8198.  
  8199. // Spawn the new SITREP broadcast loop
  8200. [] spawn fnc_broadcastSitRep;
  8201.  
  8202. // Initialize all playable units as players
  8203. { if (isPlayer _x) then { [_x] call fnc_server_initializePlayer; } } forEach playableUnits;
  8204.  
  8205. // ==================== CHANGE START ====================
  8206. // Handle players connecting after mission start, now with team lock enforcement.
  8207. addMissionEventHandler ["PlayerConnected", {
  8208. params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
  8209. if (_jip) then {
  8210. // Short delay to ensure the player's lobby unit has been created.
  8211. [_uid, _owner] spawn {
  8212. params ["_uid", "_owner"];
  8213. sleep 3; // Longer delay for JIP synchronization
  8214.  
  8215. // Check if this player has a locked team (meaning they disconnected earlier)
  8216. private _locks = missionNamespace getVariable ["PLAYER_TEAM_LOCKS", createHashMap];
  8217. private _lockedSide = _locks getOrDefault [_uid, sideUnknown];
  8218.  
  8219. if (_lockedSide != sideUnknown) then {
  8220. // Player is rejoining - create a new unit for them at base
  8221. [objNull, _lockedSide, _uid, _owner] call fnc_server_playerChooseSide;
  8222. } else {
  8223. // New JIP player - find their unit and set up team selection
  8224. private _playerUnit = objNull;
  8225. {
  8226. if (getPlayerUID _x == _uid) exitWith {
  8227. _playerUnit = _x;
  8228. };
  8229. } forEach allPlayers;
  8230.  
  8231. if (!isNull _playerUnit && side _playerUnit == civilian) then {
  8232. [_playerUnit] remoteExecCall ["fnc_client_setupTeamSelection", owner _playerUnit];
  8233. };
  8234. };
  8235. };
  8236. };
  8237. }];
  8238.  
  8239. // Handle players disconnecting - remove their unit
  8240. addMissionEventHandler ["PlayerDisconnected", {
  8241. params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
  8242.  
  8243. // Find the disconnecting player's unit by UID
  8244. private _disconnectedUnit = objNull;
  8245. {
  8246. if (getPlayerUID _x == _uid) exitWith {
  8247. _disconnectedUnit = _x;
  8248. };
  8249. } forEach allPlayers;
  8250.  
  8251. // If we found their unit, store data and delete it
  8252. if (!isNull _disconnectedUnit) then {
  8253. // Store their side if not already locked (team lock system already handles this)
  8254. private _locks = missionNamespace getVariable ["PLAYER_TEAM_LOCKS", createHashMap];
  8255. private _currentSide = side _disconnectedUnit;
  8256.  
  8257. // Only store side if they're actually on a team (not civilian)
  8258. // Fixed: Check if the key doesn't exist in the hashmap
  8259. if (_currentSide in [west, east] && {!(_uid in _locks)}) then {
  8260. _locks set [_uid, _currentSide];
  8261. missionNamespace setVariable ["PLAYER_TEAM_LOCKS", _locks, true];
  8262. };
  8263.  
  8264. // Delete the disconnected player's unit
  8265. deleteVehicle _disconnectedUnit;
  8266.  
  8267. // Log the disconnection
  8268. diag_log format ["Player %1 (UID: %2) disconnected and unit removed", _name, _uid];
  8269. };
  8270. }];
  8271. // ===================== CHANGE END =====================
  8272.  
  8273. // EntityKilled Event Handler for points and respawn penalties
  8274. addMissionEventHandler ["EntityKilled", {
  8275. params ["_victim", "_killer", "_instigator"];
  8276.  
  8277. // UPDATE STRENGTH TALLY ON DEATH
  8278. if (!isPlayer _victim) then { // We don't tally players
  8279. private _victimSide = side _victim;
  8280. private _strengthVal = _victim getVariable ["strengthValue", 0]; // Get stored value
  8281. if (_strengthVal > 0) then {
  8282. if (_victimSide == west) then {
  8283. BLUFOR_STRENGTH = (BLUFOR_STRENGTH - _strengthVal) max 0; // max 0 prevents negative scores
  8284. } else {
  8285. OPFOR_STRENGTH = (OPFOR_STRENGTH - _strengthVal) max 0;
  8286. };
  8287. };
  8288. };
  8289.  
  8290. private _actualKiller = if (!isNull _killer) then { _killer } else { _instigator };
  8291.  
  8292. if (isNull _actualKiller || {_actualKiller == _victim}) exitWith {};
  8293.  
  8294. private _killerGroup = group _actualKiller;
  8295. private _victimGroup = group _victim;
  8296.  
  8297. if (isNull _killerGroup || isNull _victimGroup) exitWith {};
  8298.  
  8299. private _killerSide = side _killerGroup;
  8300. private _victimSide = side _victimGroup;
  8301.  
  8302. if (_killerSide == _victimSide) then {
  8303. if (isPlayer _actualKiller && _victim isKindOf "CAManBase") then {
  8304. private _currentPoints = _actualKiller getVariable ["playerPoints", 0];
  8305. private _newPoints = (_currentPoints - 5) max 0;
  8306. _actualKiller setVariable ["playerPoints", _newPoints, true];
  8307. private _tkCount = _actualKiller getVariable ["teamkillCount", 0];
  8308. _tkCount = _tkCount + 1;
  8309. _actualKiller setVariable ["teamkillCount", _tkCount, true];
  8310.  
  8311. if (_tkCount >= 3) then {
  8312. _actualKiller setDamage 1;
  8313. _actualKiller setVariable ["teamkillCount", 0, true];
  8314. _actualKiller setVariable ["penaltyRespawn", true, true];
  8315. systemChat format ["%1 has been executed for excessive teamkilling.", name _actualKiller];
  8316. } else {
  8317. [format["TEAMKILL! (%1/3). -5 Points. You now have %2 points.", _tkCount, _newPoints]] remoteExecCall ["hint", owner _actualKiller];
  8318. };
  8319. };
  8320. } else {
  8321. private _victimValue = if (_victim isKindOf "CAManBase") then {
  8322. [_victim] call fnc_getUnitValue;
  8323. } else {
  8324. _victim getVariable ["unitValue", 0];
  8325. };
  8326.  
  8327. if (_killerSide == west) then {
  8328. BLUFOR_POINTS = BLUFOR_POINTS + _victimValue; publicVariable "BLUFOR_POINTS";
  8329. } else {
  8330. if (_killerSide == east) then {
  8331. OPFOR_POINTS = OPFOR_POINTS + _victimValue; publicVariable "OPFOR_POINTS";
  8332. };
  8333. };
  8334.  
  8335. private _pointRecipient = objNull;
  8336. if (isPlayer _actualKiller) then {
  8337. _pointRecipient = _actualKiller;
  8338. } else {
  8339. // Check if the AI killer is owned by a player
  8340. private _owner = _actualKiller getVariable ["ownerPlayer", objNull];
  8341. if (!isNull _owner && isPlayer _owner && alive _owner) then {
  8342. _pointRecipient = _owner;
  8343. };
  8344. };
  8345.  
  8346. // If we have a valid player to give points to (either the killer or the owner)
  8347. if (!isNull _pointRecipient) then {
  8348. private _currentPoints = _pointRecipient getVariable ["playerPoints", 0];
  8349.  
  8350. // ==================== CHANGE START ====================
  8351. private _pointsAwarded = 0;
  8352. if (_victim isKindOf "CAManBase") then {
  8353. _pointsAwarded = 1;
  8354. } else {
  8355. // Award 10 points for tanks (includes APCs) and all air vehicles
  8356. if (_victim isKindOf "Tank" || _victim isKindOf "Air") then {
  8357. _pointsAwarded = 10;
  8358. } else {
  8359. // For other vehicles (MRAPs, Quads), award half their point value, with a minimum of 1
  8360. _pointsAwarded = (_victimValue / 2) max 1;
  8361. };
  8362. };
  8363. // ===================== CHANGE END =====================
  8364.  
  8365. private _isOwner = (_pointRecipient != _actualKiller); // Check if the recipient is the bot's owner
  8366.  
  8367. _pointRecipient setVariable ["playerPoints", _currentPoints + _pointsAwarded, true];
  8368.  
  8369. private _hintMessage = "";
  8370. if (_isOwner) then {
  8371. _hintMessage = format["Your AI squad got a kill! +%1 Personal Points! You now have %2.", _pointsAwarded, _currentPoints + _pointsAwarded];
  8372. } else {
  8373. _hintMessage = format["+%1 Personal Points! You now have %2.", _pointsAwarded, _currentPoints + _pointsAwarded];
  8374. };
  8375. [_hintMessage] remoteExecCall ["hint", owner _pointRecipient];
  8376. };
  8377. };
  8378.  
  8379. // ==================== CHANGE START ====================
  8380. // Deduct points on player death and check for bankruptcy
  8381. if (isPlayer _victim) then {
  8382. // Deduct personal point
  8383. private _playerPoints = _victim getVariable ["playerPoints", 0];
  8384. private _newPlayerPoints = (_playerPoints - 1) max 0;
  8385. _victim setVariable ["playerPoints", _newPlayerPoints, true];
  8386. [format["You have died. -1 Point. You now have %1 points.", _newPlayerPoints]] remoteExecCall ["hint", owner _victim];
  8387.  
  8388. // Deduct team point and check for game end condition
  8389. if (_victimSide == west) then {
  8390. BLUFOR_POINTS = BLUFOR_POINTS - 1;
  8391. publicVariable "BLUFOR_POINTS";
  8392. if (BLUFOR_POINTS < 1) then {
  8393. ["OPFOR", "BLUFOR", "BLUFOR has run out of points to reinforce their troops."] call fnc_endRound;
  8394. };
  8395. } else {
  8396. OPFOR_POINTS = OPFOR_POINTS - 1;
  8397. publicVariable "OPFOR_POINTS";
  8398. if (OPFOR_POINTS < 1) then {
  8399. ["BLUFOR", "OPFOR", "OPFOR has run out of points to reinforce their troops."] call fnc_endRound;
  8400. };
  8401. };
  8402. };
  8403. // ===================== CHANGE END =====================
  8404. }];
  8405.  
  8406. // Spawn the first waves of standard infantry spread around the battlefield
  8407. [] spawn {
  8408. sleep 15;
  8409. waitUntil {!isNil "mission_centralPoint"};
  8410. private _centerPos = missionNamespace getVariable "mission_centralPoint";
  8411. private _bluforBase = getPos bluforSpawnObj;
  8412. private _opforBase = getPos opforSpawnObj;
  8413.  
  8414. // Calculate different approach vectors for varied initial deployment
  8415. private _bluforApproachAngles = [];
  8416. private _opforApproachAngles = [];
  8417.  
  8418. // BLUFOR approaches from their base direction with spread
  8419. private _bluforBaseAngle = _centerPos getDir _bluforBase;
  8420. _bluforApproachAngles pushBack (_bluforBaseAngle + 180); // Center
  8421. _bluforApproachAngles pushBack (_bluforBaseAngle + 180 - 60); // Left flank
  8422. _bluforApproachAngles pushBack (_bluforBaseAngle + 180 + 60); // Right flank
  8423.  
  8424. // OPFOR approaches from their base direction with spread
  8425. private _opforBaseAngle = _centerPos getDir _opforBase;
  8426. _opforApproachAngles pushBack (_opforBaseAngle + 180); // Center
  8427. _opforApproachAngles pushBack (_opforBaseAngle + 180 - 60); // Left flank
  8428. _opforApproachAngles pushBack (_opforBaseAngle + 180 + 60); // Right flank
  8429.  
  8430. for "_i" from 0 to 2 do {
  8431. if (count allUnits >= maxAI) exitWith {};
  8432.  
  8433. // BLUFOR INFANTRY WAVE - spread to different positions
  8434. private _bluforGroup = ["infantry"] call fnc_spawnBluforWave;
  8435. if (!isNull _bluforGroup) then {
  8436. // Each wave goes to a different angle around the center
  8437. private _angle = _bluforApproachAngles select _i;
  8438. private _distance = 400 + (random 300); // 400-700m from center, randomized
  8439. private _targetPos = _centerPos getPos [_distance, _angle];
  8440. [_bluforGroup, _targetPos] call fnc_createPatrolWaypoints;
  8441. };
  8442.  
  8443. sleep 1;
  8444.  
  8445. if (count allUnits >= maxAI) exitWith {};
  8446.  
  8447. // OPFOR INFANTRY WAVE - spread to different positions
  8448. private _opforGroup = ["infantry"] call fnc_spawnOpforWave;
  8449. if (!isNull _opforGroup) then {
  8450. // Each wave goes to a different angle around the center
  8451. private _angle = _opforApproachAngles select _i;
  8452. private _distance = 400 + (random 300); // 400-700m from center, randomized
  8453. private _targetPos = _centerPos getPos [_distance, _angle];
  8454. [_opforGroup, _targetPos] call fnc_createPatrolWaypoints;
  8455. };
  8456.  
  8457. sleep 2;
  8458. };
  8459. };
  8460.  
  8461. // Main AI Wave Spawning Loop (for continuous reinforcement)
  8462. [] spawn {
  8463. sleep 15; // This now acts as a delay AFTER the initial waves
  8464. while {true} do {
  8465. if (count allUnits < maxAI) then {
  8466. // Count alive non-player AI for each side
  8467. private _bluforAliveCount = count (allUnits select {side _x == west && !isPlayer _x && alive _x});
  8468. private _opforAliveCount = count (allUnits select {side _x == east && !isPlayer _x && alive _x});
  8469.  
  8470. // Define the population cap threshold (110% of the enemy)
  8471. private _bluforCap = _opforAliveCount * 1.1;
  8472. private _opforCap = _bluforAliveCount * 1.1;
  8473.  
  8474. // Define the low-point threshold to ignore the cap
  8475. private _lowPointThreshold = 20;
  8476.  
  8477. // Determine if each side is allowed to spawn based on population
  8478. private _bluforCanSpawn = (_bluforAliveCount <= _bluforCap) || (OPFOR_POINTS < _lowPointThreshold);
  8479. private _opforCanSpawn = (_opforAliveCount <= _opforCap) || (BLUFOR_POINTS < _lowPointThreshold);
  8480.  
  8481. // BLUFOR WAVE
  8482. if (_bluforCanSpawn) then {
  8483. private _bluforWaveType = [BLUFOR_POINTS, west] call fnc_getRandomWaveType;
  8484. if (!isNil "_bluforWaveType") then {
  8485. [_bluforWaveType] call fnc_spawnBluforWave;
  8486. };
  8487. };
  8488.  
  8489. sleep (waveDelay / 2);
  8490.  
  8491. // OPFOR WAVE
  8492. if (_opforCanSpawn) then {
  8493. private _opforWaveType = [OPFOR_POINTS, east] call fnc_getRandomWaveType;
  8494. if (!isNil "_opforWaveType") then {
  8495. [_opforWaveType] call fnc_spawnOpforWave;
  8496. };
  8497. };
  8498.  
  8499. sleep waveDelay;
  8500. } else {
  8501. sleep 60;
  8502. };
  8503. };
  8504. };
  8505. // ==================== CHANGE START ====================
  8506. // NEW: Game Win/Loss Condition Check by Unit Count
  8507. [] spawn {
  8508. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  8509.  
  8510. // Do not start checks until 10 minutes (600 seconds) have passed.
  8511. waitUntil {time > 600};
  8512.  
  8513. while {true} do {
  8514. if (missionNamespace getVariable ["missionEnding", false]) exitWith {};
  8515.  
  8516. // Count ALL alive units (players + AI) for each side
  8517. private _bluforUnitCount = count (allUnits select {side _x == west && alive _x});
  8518. private _opforUnitCount = count (allUnits select {side _x == east && alive _x});
  8519.  
  8520. // Check for BLUFOR loss due to catastrophic casualties (if they have players)
  8521. if (_bluforUnitCount > 0 && _bluforUnitCount < 10) then {
  8522. if (count (allPlayers select {side _x == west}) > 0) then {
  8523. ["OPFOR", "BLUFOR", "BLUFOR's forces have been eliminated."] call fnc_endRound;
  8524. break;
  8525. };
  8526. };
  8527.  
  8528. // Check for OPFOR loss due to catastrophic casualties (if they have players)
  8529. if (_opforUnitCount > 0 && _opforUnitCount < 10) then {
  8530. if (count (allPlayers select {side _x == east}) > 0) then {
  8531. ["BLUFOR", "OPFOR", "OPFOR's forces have been eliminated."] call fnc_endRound;
  8532. break;
  8533. };
  8534. };
  8535.  
  8536. sleep 15; // Check every 15 seconds
  8537. };
  8538. };
  8539. // ===================== CHANGE END =====================
  8540.  
  8541. // FOB Decoration and Respawn Position Setup
  8542. [] spawn {
  8543. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  8544. sleep 3;
  8545.  
  8546. [bluforSpawnObj, west] call fnc_decorateFOB;
  8547. [opforSpawnObj, east] call fnc_decorateFOB;
  8548.  
  8549. [west, getPos bluforSpawnObj, "BLUFOR Base"] call BIS_fnc_addRespawnPosition;
  8550. [east, getPos opforSpawnObj, "OPFOR Base"] call BIS_fnc_addRespawnPosition;
  8551.  
  8552. // Create play area restriction zone
  8553. [] call fnc_createPlayArea;
  8554. };
  8555. };
  8556. };
  8557.  
  8558. if (hasInterface) then {
  8559. [] spawn { [] call fnc_setupCaptureActions; };
  8560. };
  8561.  
  8562. // Global friendly collision damage reduction (always active)
  8563. [] spawn {
  8564. while {true} do {
  8565. {
  8566. if (_x isKindOf "LandVehicle" && !(_x getVariable ["friendlyCollisionSetup", false])) then {
  8567. _x setVariable ["friendlyCollisionSetup", true];
  8568. _x addEventHandler ["HandleDamage", {
  8569. params ["_vehicle", "_selection", "_damage", "_source", "_projectile", "_hitIndex", "_instigator", "_hitPoint"];
  8570. if (_source isKindOf "LandVehicle" && _projectile == "" && _selection == "") then {
  8571. private _vehicleSide = side (effectiveCommander _source);
  8572. private _unitSide = side _vehicle;
  8573. if (_vehicleSide == _unitSide) then {
  8574. _damage = _damage * 0.05;
  8575. };
  8576. };
  8577. _damage
  8578. }];
  8579. };
  8580. } forEach vehicles;
  8581. sleep 10;
  8582. };
  8583. };
  8584.  
  8585. ==================== END OF: mission_setup.sqf ====================
  8586.  
  8587.  
  8588.  
  8589. ==================== START OF: performance.sqf ====================
  8590.  
  8591. // performance.sqf
  8592. // IMPROVED: Enhanced performance monitoring with proper averaging, FPS integration, clean threshold logic,
  8593. // smooth mathematical scaling, hysteresis for stability, and bug fixes (e.g., batch sizes).
  8594. // Performance states: 10 (excellent, ~40+ FPS) to 1 (emergency, <10 FPS).
  8595. // Changes:
  8596. // - Increased history size to 20 for stable 3+ minute averaging.
  8597. // - Replaced nested ifs with efficient array-based threshold counting.
  8598. // - Added diag_fps as third metric for comprehensive server perf monitoring.
  8599. // - Switched to smooth linear interpolation for scales (no more switch/stepped inconsistencies).
  8600. // - Added hysteresis (change only if differs by 1+ state) to prevent oscillation.
  8601. // - Aligned FPS scaling: 40 FPS = 10, 20 FPS ≈ 5, 10 FPS ≈ 3.
  8602. // - Initial scale adjustment on startup.
  8603. // - FIXED: Threshold arrays scoped locally to monitor function.
  8604. // - FIXED: Global init with type checks (prevents String/NaN errors on PERF_STATE /= 10.0).
  8605.  
  8606. // --- Global Performance Variables --- (with type safety)
  8607. if (isNil "PERF_STATE" || {typeName PERF_STATE != "SCALAR"}) then { PERF_STATE = 10; };
  8608. if (isNil "PERF_MAIN_LOOP_MULTIPLIER" || {typeName PERF_MAIN_LOOP_MULTIPLIER != "SCALAR"}) then { PERF_MAIN_LOOP_MULTIPLIER = 1.0; };
  8609. if (isNil "PERF_AI_BATCH_SIZE" || {typeName PERF_AI_BATCH_SIZE != "SCALAR"}) then { PERF_AI_BATCH_SIZE = 10; };
  8610. if (isNil "PERF_SEARCH_RADIUS_MULTIPLIER" || {typeName PERF_SEARCH_RADIUS_MULTIPLIER != "SCALAR"}) then { PERF_SEARCH_RADIUS_MULTIPLIER = 1.0; };
  8611. if (isNil "PERF_COOLDOWN_MULTIPLIER" || {typeName PERF_COOLDOWN_MULTIPLIER != "SCALAR"}) then { PERF_COOLDOWN_MULTIPLIER = 1.0; };
  8612.  
  8613. // Monitor server performance and adjust dynamically
  8614. fnc_monitorPerformance = {
  8615. private _deltaHistory = [];
  8616. private _execTimeHistory = [];
  8617. private _historySize = 20; // 3.3+ min average for stability
  8618. private _hysteresis = 1; // Min state change to trigger update
  8619.  
  8620. // Threshold arrays (local to this function)
  8621. private _deltaThresholds = [0.020, 0.025, 0.030, 0.040, 0.050, 0.060, 0.075, 0.090, 0.110];
  8622. private _execThresholds = [0.015, 0.020, 0.030, 0.040, 0.050, 0.060, 0.075, 0.090, 0.110];
  8623.  
  8624. while {true} do {
  8625. private _schedulerDelta = diag_deltaTime;
  8626. private _lastExecTime = missionNamespace getVariable ["BOTAI_lastExecTime", 0];
  8627. private _fps = diag_fps;
  8628.  
  8629. _deltaHistory pushBack _schedulerDelta;
  8630. _execTimeHistory pushBack _lastExecTime;
  8631.  
  8632. if (count _deltaHistory > _historySize) then { _deltaHistory deleteAt 0; };
  8633. if (count _execTimeHistory > _historySize) then { _execTimeHistory deleteAt 0; };
  8634.  
  8635. private _avgDelta = 0;
  8636. {_avgDelta = _avgDelta + _x;} forEach _deltaHistory;
  8637. _avgDelta = _avgDelta / count _deltaHistory;
  8638.  
  8639. private _avgExecTime = 0;
  8640. {_avgExecTime = _avgExecTime + _x;} forEach _execTimeHistory;
  8641. _avgExecTime = _avgExecTime / count _execTimeHistory;
  8642.  
  8643. // Clean state calculation: 10 - number of exceeded thresholds
  8644. private _deltaState = 10 - (count (_deltaThresholds select {_avgDelta >= _x}));
  8645. private _execState = 10 - (count (_execThresholds select {_avgExecTime >= _x}));
  8646. private _fpsState = round (_fps * 0.25) max 1 min 10; // 40 FPS=10, 20 FPS=5, 10 FPS=3
  8647.  
  8648. private _newState = _deltaState min _execState min _fpsState;
  8649. private _oldState = PERF_STATE;
  8650.  
  8651. // Ensure number, clamp
  8652. _newState = round (_newState max 1 min 10);
  8653.  
  8654. // Hysteresis: only update on meaningful change
  8655. if (_newState <= _oldState - _hysteresis || _newState >= _oldState + _hysteresis) then {
  8656. PERF_STATE = _newState;
  8657. diag_log format ["[PERF] State Change: %1 -> %2 (Delta: %3ms, Exec: %4ms)",
  8658. _oldState, PERF_STATE,
  8659. round (_avgDelta * 1000),
  8660. round (_avgExecTime * 1000)];
  8661. [] call fnc_adjustPerformanceScales;
  8662. };
  8663.  
  8664. sleep 10;
  8665. };
  8666. };
  8667.  
  8668. // Adaptive sleep helper (unchanged)
  8669. fnc_getAdaptiveSleep = {
  8670. params ["_baseSleep"];
  8671. (_baseSleep * PERF_MAIN_LOOP_MULTIPLIER)
  8672. };
  8673.  
  8674. // Smooth mathematical scaling (no switch; fixes batch size bugs)
  8675. fnc_adjustPerformanceScales = {
  8676. // Safe PERF_STATE handling (fallback + clamp)
  8677. private _perfState = 10;
  8678. if (!isNil "PERF_STATE" && {typeName PERF_STATE == "SCALAR"}) then {
  8679. _perfState = PERF_STATE;
  8680. } else {
  8681. PERF_STATE = 10;
  8682. };
  8683. _perfState = round (_perfState max 1 min 10);
  8684. PERF_STATE = _perfState;
  8685.  
  8686. private _scale = _perfState / 10.0; // 1.0 (best) to 0.1 (worst)
  8687.  
  8688. PERF_MAIN_LOOP_MULTIPLIER = 1.0 + 9.0 * (1.0 - _scale); // 1x to 10x slowdown
  8689. PERF_AI_BATCH_SIZE = round(10.0 * _scale); // 10 to 1
  8690. PERF_SEARCH_RADIUS_MULTIPLIER = 0.6 + 0.4 * _scale; // 1.0 to 0.6
  8691. PERF_COOLDOWN_MULTIPLIER = 1.0 + 4.0 * (1.0 - _scale); // 1x to 5x
  8692. };
  8693.  
  8694. if (isServer) then {
  8695. [] spawn {
  8696. sleep 5;
  8697. // Initial scale set
  8698. [] call fnc_adjustPerformanceScales;
  8699. // Start monitoring
  8700. [] spawn fnc_monitorPerformance;
  8701. diag_log "[PERFORMANCE] System initialized.";
  8702. };
  8703. };
  8704.  
  8705. ==================== END OF: performance.sqf ====================
  8706.  
  8707.  
  8708.  
  8709. ==================== START OF: playerinit.sqf ====================
  8710.  
  8711. // playerinit.sqf
  8712. // Client-side initialization and UI functions
  8713.  
  8714. fnc_client_showBriefing = {
  8715. if (isNull (findDisplay 9300)) then {
  8716. if (createDialog "MissionBriefingDialog") then {
  8717. waitUntil {!isNull (findDisplay 9300)};
  8718. };
  8719. };
  8720. };
  8721. missionNamespace setVariable ["fnc_client_showBriefing", fnc_client_showBriefing, true];
  8722.  
  8723. fnc_client_openPlayerMenu = {
  8724. if (isNull (findDisplay 9000)) then {
  8725. if (createDialog "PlayerPurchaseMenu") then {
  8726. private _pointsCtrl = (findDisplay 9000) displayCtrl 9002;
  8727. private _points = player getVariable ["playerPoints", 0];
  8728. _pointsCtrl ctrlSetText format ["Points: %1", _points];
  8729. };
  8730. };
  8731. };
  8732. missionNamespace setVariable ["fnc_client_openPlayerMenu", fnc_client_openPlayerMenu, true];
  8733.  
  8734. fnc_client_addVehicleWaypoint = {
  8735. params ["_vehiclePos", "_displayName"];
  8736. private _grp = group player;
  8737.  
  8738. _grp addWaypoint [_vehiclePos, 0];
  8739.  
  8740. private _newWPIndex = (count (waypoints _grp)) - 1;
  8741.  
  8742. private _wp = (waypoints _grp) select _newWPIndex;
  8743.  
  8744. _wp setWaypointType "MOVE";
  8745. _wp setWaypointCompletionRadius 30;
  8746.  
  8747. [_grp, _newWPIndex] setWaypointDescription format["Your %1", _displayName];
  8748.  
  8749. hint format ["Waypoint added to your map for the %1.", _displayName];
  8750. };
  8751. missionNamespace setVariable ["fnc_client_addVehicleWaypoint", fnc_client_addVehicleWaypoint, true];
  8752. player setVariable ["spawnTime", time, true];
  8753.  
  8754. fnc_client_completeSideSwitch = {
  8755. params ["_newUnit"];
  8756.  
  8757. // CHANGE: Remove the local check that was preventing execution
  8758. // Wait for the unit to become local if it isn't already
  8759. if (!local _newUnit) then {
  8760. waitUntil {local _newUnit || !alive _newUnit};
  8761. };
  8762.  
  8763. // Exit if unit died during the wait
  8764. if (!alive _newUnit) exitWith {
  8765. hint "Failed to join team - unit was destroyed.";
  8766. };
  8767.  
  8768. selectPlayer _newUnit;
  8769.  
  8770. waitUntil {player == _newUnit};
  8771. player setVariable ["sideSwitchComplete", true, true];
  8772.  
  8773. [player] call fnc_initializePlayerUnit;
  8774. [player] call fnc_teleportToBase;
  8775. [player] call fnc_client_initializePlayer;
  8776.  
  8777. waitUntil {!isNil "BLUFOR_POINTS"};
  8778. [] call fnc_client_createTeamScoreUI;
  8779.  
  8780. hint "You have joined a team. Good luck, Officer.";
  8781.  
  8782. // SHOW BRIEFING ON TEAM SWITCH
  8783. [] call fnc_client_showBriefing;
  8784. };
  8785. missionNamespace setVariable ["fnc_client_completeSideSwitch", fnc_client_completeSideSwitch, true];
  8786.  
  8787. fnc_client_createTeamScoreUI = {
  8788. // UI has been disabled by user request as the information is provided in chat.
  8789. };
  8790.  
  8791. fnc_teleportToBase = {
  8792. params ["_unit"];
  8793. if (!local _unit) exitWith {};
  8794. private _side = side _unit;
  8795. private _baseObj = objNull;
  8796.  
  8797. if (_side == west) then {
  8798. _baseObj = bluforSpawnObj;
  8799. } else {
  8800. if (_side == east) then {
  8801. _baseObj = opforSpawnObj;
  8802. };
  8803. };
  8804.  
  8805. if (!isNull _baseObj) then {
  8806. private _spawnPos = getPosATL _baseObj;
  8807. _spawnPos set [2, 0.5];
  8808.  
  8809. if (count _spawnPos < 3 || {!finite (_spawnPos select 0)} || {!finite (_spawnPos select 1)}) exitWith {
  8810. systemChat "Error: Invalid base position detected";
  8811. };
  8812.  
  8813. _unit setPosATL _spawnPos;
  8814. _unit setDir (random 360);
  8815. _unit setVelocity [0,0,0];
  8816. _unit setDamage 0;
  8817.  
  8818. } else {
  8819. systemChat format ["Error: No base object found for side %1", _side];
  8820. };
  8821. };
  8822.  
  8823. fnc_singleplayerRespawn = {
  8824. params ["_deadUnit"];
  8825.  
  8826. private _unitType = typeOf _deadUnit;
  8827. private _unitGroup = group _deadUnit;
  8828. private _unitSide = side _unitGroup;
  8829. private _unitLoadout = _deadUnit getVariable ["savedLoadout", getUnitLoadout _deadUnit];
  8830. private _playerPoints = _deadUnit getVariable ["playerPoints", 0];
  8831. private _teamkillCount = _deadUnit getVariable ["teamkillCount", 0];
  8832. private _isPenaltyRespawn = _deadUnit getVariable ["penaltyRespawn", false];
  8833.  
  8834. private _respawnDelay = if (_isPenaltyRespawn) then {600} else {5};
  8835.  
  8836. private _baseObj = if (_unitSide == west) then {bluforSpawnObj} else {opforSpawnObj};
  8837. private _spawnPos = getPosATL _baseObj;
  8838. _spawnPos set [2, 0.5];
  8839.  
  8840. private _newUnit = _unitGroup createUnit [_unitType, _spawnPos, [], 0, "NONE"];
  8841. _newUnit setPosATL _spawnPos;
  8842. _newUnit setDir (random 360);
  8843.  
  8844. _newUnit enableSimulation false;
  8845. _newUnit hideObjectGlobal true;
  8846. _newUnit allowDamage false;
  8847.  
  8848. selectPlayer _newUnit;
  8849.  
  8850. deleteVehicle _deadUnit;
  8851.  
  8852. cutText ["", "BLACK OUT", 0];
  8853.  
  8854. private _respawnEndTime = time + _respawnDelay;
  8855. while {time < _respawnEndTime} do {
  8856. private _timeLeft = ceil (_respawnEndTime - time);
  8857. private _message = if (_isPenaltyRespawn) then {
  8858. format ["PENALTY RESPAWN IN: %1", [_timeLeft, "MM:SS"] call BIS_fnc_secondsToString]
  8859. } else {
  8860. format ["RESPAWNING IN: %1", _timeLeft]
  8861. };
  8862. cutText [_message, "BLACK FADED", 0];
  8863. sleep 0.1;
  8864. };
  8865.  
  8866. _newUnit enableSimulation true;
  8867. _newUnit hideObjectGlobal false;
  8868.  
  8869. _newUnit setUnitLoadout _unitLoadout;
  8870. _newUnit setVariable ["playerPoints", _playerPoints, true];
  8871. _newUnit setVariable ["teamkillCount", _teamkillCount, true];
  8872.  
  8873. if (_isPenaltyRespawn) then {
  8874. _newUnit setVariable ["penaltyRespawn", false, true];
  8875. _newUnit setVariable ["clearMyPenaltyFlag", true, true];
  8876. };
  8877.  
  8878. [_newUnit] call fnc_initializePlayerUnit;
  8879.  
  8880. cutText ["", "BLACK IN", 2];
  8881.  
  8882. _newUnit setDamage 0;
  8883. _newUnit setFatigue 0;
  8884.  
  8885. // Apply respawn protection
  8886. [_newUnit] call fnc_applyRespawnProtection;
  8887.  
  8888. "DynamicBlur" ppEffectEnable true;
  8889. "DynamicBlur" ppEffectAdjust [0];
  8890. "DynamicBlur" ppEffectCommit 0;
  8891. "DynamicBlur" ppEffectEnable false;
  8892.  
  8893. "RadialBlur" ppEffectEnable true;
  8894. "RadialBlur" ppEffectAdjust [0, 0, 0, 0];
  8895. "RadialBlur" ppEffectCommit 0;
  8896. "RadialBlur" ppEffectEnable false;
  8897.  
  8898. "chromAberration" ppEffectEnable true;
  8899. "chromAberration" ppEffectAdjust [0,0,true];
  8900. "chromAberration" ppEffectCommit 0;
  8901. "chromAberration" ppEffectEnable false;
  8902.  
  8903. "ColorCorrections" ppEffectEnable true;
  8904. "ColorCorrections" ppEffectAdjust [1, 1, 0, [0,0,0,0], [0,0,0,0], [0,0,0,0]];
  8905. "ColorCorrections" ppEffectCommit 0;
  8906. "ColorCorrections" ppEffectEnable false;
  8907.  
  8908. "FilmGrain" ppEffectEnable true;
  8909. "FilmGrain" ppEffectAdjust [0, 1, 1, 1, 0, false];
  8910. "FilmGrain" ppEffectCommit 0;
  8911. "FilmGrain" ppEffectEnable false;
  8912.  
  8913. sleep 0.1;
  8914.  
  8915. [] call fnc_client_showBriefing;
  8916.  
  8917. // Force reset all screen effects 1 second after respawn
  8918. [] spawn {
  8919. sleep 1;
  8920. "DynamicBlur" ppEffectEnable true;
  8921. "DynamicBlur" ppEffectAdjust [0];
  8922. "DynamicBlur" ppEffectCommit 0;
  8923. "DynamicBlur" ppEffectEnable false;
  8924.  
  8925. "RadialBlur" ppEffectEnable true;
  8926. "RadialBlur" ppEffectAdjust [0, 0, 0, 0];
  8927. "RadialBlur" ppEffectCommit 0;
  8928. "RadialBlur" ppEffectEnable false;
  8929.  
  8930. "chromAberration" ppEffectEnable true;
  8931. "chromAberration" ppEffectAdjust [0,0,true];
  8932. "chromAberration" ppEffectCommit 0;
  8933. "chromAberration" ppEffectEnable false;
  8934.  
  8935. "ColorCorrections" ppEffectEnable true;
  8936. "ColorCorrections" ppEffectAdjust [1, 1, 0, [0,0,0,0], [1,1,1,1], [1,1,1,0]];
  8937. "ColorCorrections" ppEffectCommit 0;
  8938. "ColorCorrections" ppEffectEnable false;
  8939.  
  8940. "FilmGrain" ppEffectEnable true;
  8941. "FilmGrain" ppEffectAdjust [0, 1, 1, 1, 0, false];
  8942. "FilmGrain" ppEffectCommit 0;
  8943. "FilmGrain" ppEffectEnable false;
  8944.  
  8945. "WetDistortion" ppEffectEnable true;
  8946. "WetDistortion" ppEffectAdjust [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
  8947. "WetDistortion" ppEffectCommit 0;
  8948. "WetDistortion" ppEffectEnable false;
  8949.  
  8950. "ColorInversion" ppEffectEnable true;
  8951. "ColorInversion" ppEffectAdjust [0,0,0];
  8952. "ColorInversion" ppEffectCommit 0;
  8953. "ColorInversion" ppEffectEnable false;
  8954.  
  8955. enableCamShake false;
  8956. resetCamShake;
  8957. };
  8958. };
  8959.  
  8960. fnc_multiplayerRespawn = {
  8961. params ["_deadUnit"];
  8962.  
  8963. private _unitLoadout = _deadUnit getVariable ["savedLoadout", getUnitLoadout _deadUnit];
  8964. private _playerPoints = _deadUnit getVariable ["playerPoints", 0];
  8965. private _teamkillCount = _deadUnit getVariable ["teamkillCount", 0];
  8966. private _isPenaltyRespawn = _deadUnit getVariable ["penaltyRespawn", false];
  8967.  
  8968. private _respawnDelay = if (_isPenaltyRespawn) then {600} else {5};
  8969.  
  8970. // Store data on the server for post-respawn processing - ONLY for this specific player
  8971. [_deadUnit, _unitLoadout, _playerPoints, _teamkillCount, _isPenaltyRespawn] remoteExecCall ["fnc_server_storeRespawnData", 2, false];
  8972.  
  8973. // Set respawn time ONLY for this player
  8974. setPlayerRespawnTime _respawnDelay;
  8975. };
  8976.  
  8977. // Helper function to check and replace disallowed gear
  8978. fnc_checkAndReplaceGear = {
  8979. params ["_unit"];
  8980.  
  8981. if (isNil "BLUFOR_UNIFORMS" || isNil "OPFOR_UNIFORMS") exitWith {};
  8982.  
  8983. private _side = playerSide;
  8984. private _allowedUniforms = if (_side == west) then {BLUFOR_UNIFORMS} else {OPFOR_UNIFORMS};
  8985. private _allowedVests = if (_side == west) then {BLUFOR_VESTS} else {OPFOR_VESTS};
  8986. private _allowedHeadgear = if (_side == west) then {BLUFOR_HEADGEAR} else {OPFOR_HEADGEAR};
  8987. private _changed = false;
  8988.  
  8989. private _fnc_findReplacement = {
  8990. params ["_invalidItem", "_allowedList"];
  8991. private _replacement = _allowedList # 0;
  8992. private _invalidLower = toLower _invalidItem;
  8993. {
  8994. if (_invalidLower find (toLower (getText (configFile >> "CfgWeapons" >> _x >> "displayName"))) > -1) exitWith {
  8995. _replacement = _x;
  8996. };
  8997. } forEach _allowedList;
  8998. _replacement
  8999. };
  9000.  
  9001. private _currentUniform = uniform _unit;
  9002. // MODIFIED: Check for no uniform OR disallowed uniform
  9003. if (_currentUniform == "" || !(_currentUniform in _allowedUniforms)) then {
  9004. private _uniformItems = [];
  9005.  
  9006. // Only save items if there was a uniform to begin with
  9007. if (_currentUniform != "") then {
  9008. _uniformItems = uniformItems _unit;
  9009. removeUniform _unit;
  9010. };
  9011.  
  9012. // Determine replacement uniform
  9013. private _replacementUniform = if (_currentUniform != "") then {
  9014. [_currentUniform, _allowedUniforms] call _fnc_findReplacement
  9015. } else {
  9016. // No uniform - just use the first default uniform for the faction
  9017. _allowedUniforms # 0
  9018. };
  9019.  
  9020. _unit forceAddUniform _replacementUniform;
  9021. {_unit addItemToUniform _x} forEach _uniformItems;
  9022. _changed = true;
  9023. };
  9024.  
  9025. private _currentVest = vest _unit;
  9026. if (_currentVest != "" && !(_currentVest in _allowedVests)) then {
  9027. private _vestItems = vestItems _unit;
  9028. removeVest _unit;
  9029. private _replacementVest = [_currentVest, _allowedVests] call _fnc_findReplacement;
  9030. _unit addVest _replacementVest;
  9031. {_unit addItemToVest _x} forEach _vestItems;
  9032. _changed = true;
  9033. };
  9034.  
  9035. private _currentHeadgear = headgear _unit;
  9036. if (_currentHeadgear != "" && !(_currentHeadgear in _allowedHeadgear)) then {
  9037. removeHeadgear _unit;
  9038. private _replacementHeadgear = [_currentHeadgear, _allowedHeadgear] call _fnc_findReplacement;
  9039. _unit addHeadgear _replacementHeadgear;
  9040. _changed = true;
  9041. };
  9042.  
  9043. if (_changed) then {
  9044. hint "Invalid gear detected and replaced with allowed items.";
  9045. };
  9046. };
  9047.  
  9048. fnc_initializePlayerUnit = {
  9049. params ["_unit"];
  9050.  
  9051. _unit disableAI "ALL";
  9052. _unit enableAI "ANIM";
  9053. _unit enableAI "MOVE";
  9054.  
  9055. _unit addEventHandler ["InventoryOpened", {
  9056. params ["_unit"];
  9057. [_unit] call fnc_checkAndReplaceGear;
  9058. }];
  9059.  
  9060. _unit addEventHandler ["InventoryClosed", {
  9061. params ["_unit"];
  9062. _unit setVariable ["savedLoadout", getUnitLoadout _unit];
  9063. [_unit] call fnc_checkAndReplaceGear;
  9064. }];
  9065.  
  9066. if (!isMultiplayer) then {
  9067. _unit addEventHandler ["Killed", {
  9068. params ["_unit"];
  9069.  
  9070. // ==================== CHANGE START ====================
  9071. // Get AI units in squad before respawn logic runs
  9072. private _aiToDelete = units (group _unit) select {!isPlayer _x};
  9073. if (count _aiToDelete > 0) then {
  9074. // Tell the server to delete these specific AI units
  9075. [_aiToDelete] remoteExecCall ["fnc_server_deletePlayerAISquad", 2];
  9076. };
  9077. // ===================== CHANGE END =====================
  9078.  
  9079. _unit spawn {
  9080. sleep 0.01;
  9081. [_this] call fnc_singleplayerRespawn;
  9082. };
  9083. }];
  9084. } else {
  9085. _unit addEventHandler ["Killed", {
  9086. params ["_unit"];
  9087.  
  9088. // ==================== CHANGE START ====================
  9089. // Get AI units in squad before respawn logic runs
  9090. private _aiToDelete = units (group _unit) select {!isPlayer _x};
  9091. if (count _aiToDelete > 0) then {
  9092. // Tell the server to delete these specific AI units
  9093. [_aiToDelete] remoteExecCall ["fnc_server_deletePlayerAISquad", 2];
  9094. };
  9095. // ===================== CHANGE END =====================
  9096.  
  9097. _unit spawn {
  9098. sleep 0.01;
  9099. [_this] call fnc_multiplayerRespawn;
  9100. };
  9101. }];
  9102.  
  9103. // NEW: Add Respawn event handler to immediately teleport to base in MP
  9104. _unit addEventHandler ["Respawn", {
  9105. params ["_unit", "_corpse"];
  9106. // Mark the spawn time for tracking
  9107. _unit setVariable ["spawnTime", time, true];
  9108. // Immediately teleport to base
  9109. [_unit] call fnc_teleportToBase;
  9110. }];
  9111. };
  9112.  
  9113. // ==================== CHANGE START ====================
  9114. // MOVED & REWORKED: Player intel reporting loop.
  9115. // This now runs for each new player unit, ensuring intel continues after respawn.
  9116. if (isNil {_unit getVariable "intelLoopStarted"}) then {
  9117. _unit setVariable ["intelLoopStarted", true];
  9118.  
  9119. [_unit] spawn {
  9120. params ["_playerUnit"];
  9121. // Wait for the unit to be on a team.
  9122. waitUntil { !isNull _playerUnit && side _playerUnit in [west, east] };
  9123.  
  9124. // Loop only as long as this specific unit is alive.
  9125. while {alive _playerUnit} do {
  9126. // Call the intel gathering function that AI also uses.
  9127. [_playerUnit, side _playerUnit] call HC_fnc_gatherIntelligence;
  9128.  
  9129. // Check every 8-13 seconds.
  9130. sleep (8 + random 5);
  9131. };
  9132. };
  9133. };
  9134. // ===================== CHANGE END =====================
  9135. };
  9136.  
  9137. fnc_applyRespawnProtection = {
  9138. params ["_unit"];
  9139. if (!local _unit) exitWith {};
  9140.  
  9141. // Set protection flag and make unit invulnerable
  9142. _unit setVariable ["respawnProtection", true, true];
  9143. _unit allowDamage false;
  9144.  
  9145. // Visual feedback - slight transparency effect
  9146. _unit setUnitTrait ["audibleCoef", 0.5];
  9147. _unit setUnitTrait ["camouflageCoef", 0.5];
  9148.  
  9149. // Show protection status
  9150. hint "Spawn Protection Active: 10 seconds";
  9151.  
  9152. // Create the protection removal script
  9153. [_unit] spawn {
  9154. params ["_protectedUnit"];
  9155. private _protectionEndTime = time + 10;
  9156.  
  9157. // Countdown loop
  9158. while {time < _protectionEndTime && alive _protectedUnit} do {
  9159. private _timeLeft = ceil (_protectionEndTime - time);
  9160. hintSilent format ["Spawn Protection: %1 seconds remaining", _timeLeft];
  9161. sleep 0.5;
  9162. };
  9163.  
  9164. // Remove protection
  9165. if (!isNull _protectedUnit) then {
  9166. _protectedUnit allowDamage true;
  9167. _protectedUnit setVariable ["respawnProtection", false, true];
  9168. _protectedUnit setUnitTrait ["audibleCoef", 1];
  9169. _protectedUnit setUnitTrait ["camouflageCoef", 1];
  9170. hintSilent "Spawn Protection Ended";
  9171.  
  9172. // Clear hint after 2 seconds
  9173. [] spawn {
  9174. sleep 2;
  9175. hintSilent "";
  9176. };
  9177. };
  9178. };
  9179.  
  9180. // Failsafe: Additional safety check to prevent permanent godmode
  9181. [_unit] spawn {
  9182. params ["_protectedUnit"];
  9183. sleep 15; // 5 seconds after protection should have ended
  9184.  
  9185. // Force remove protection if it somehow still exists
  9186. if (!isNull _protectedUnit && (_protectedUnit getVariable ["respawnProtection", false])) then {
  9187. _protectedUnit allowDamage true;
  9188. _protectedUnit setVariable ["respawnProtection", false, true];
  9189. _protectedUnit setUnitTrait ["audibleCoef", 1];
  9190. _protectedUnit setUnitTrait ["camouflageCoef", 1];
  9191. diag_log format ["WARNING: Respawn protection failsafe triggered for %1", name _protectedUnit];
  9192. };
  9193. };
  9194. };
  9195.  
  9196. fnc_client_moveIntoVehicle = {
  9197. params ["_vehicle"];
  9198. player assignAsDriver _vehicle;
  9199. player moveInDriver _vehicle;
  9200. };
  9201.  
  9202. if (hasInterface) then {
  9203. // Display play area warning to player
  9204. fnc_client_showPlayAreaWarning = {
  9205. params ["_center", "_radius"];
  9206.  
  9207. // Create warning display
  9208. if (isNil "PLAY_AREA_WARNING_ACTIVE" || {!PLAY_AREA_WARNING_ACTIVE}) then {
  9209. PLAY_AREA_WARNING_ACTIVE = true;
  9210.  
  9211. [] spawn {
  9212. while {!isNil "PLAY_AREA_WARNING_ACTIVE" && {PLAY_AREA_WARNING_ACTIVE}} do {
  9213. private _warningTime = player getVariable ["playAreaWarningTime", 0];
  9214. if (_warningTime == 0) exitWith {
  9215. PLAY_AREA_WARNING_ACTIVE = false;
  9216. };
  9217.  
  9218. private _timeLeft = 10 - (time - _warningTime);
  9219. if (_timeLeft < 0) then {_timeLeft = 0;};
  9220.  
  9221. hintSilent parseText format [
  9222. "<t color='#ff0000' size='2' align='center'>WARNING!</t><br/>" +
  9223. "<t color='#ffff00' size='1.5' align='center'>RETURN TO BATTLE AREA</t><br/>" +
  9224. "<t color='#ffffff' size='1.2' align='center'>%1 SECONDS REMAINING</t>",
  9225. ceil _timeLeft
  9226. ];
  9227.  
  9228. sleep 0.1;
  9229. };
  9230.  
  9231. // Clean up when loop exits
  9232. hintSilent "";
  9233. PLAY_AREA_WARNING_ACTIVE = false;
  9234. };
  9235. };
  9236. };
  9237.  
  9238. // Hide play area warning from player
  9239. fnc_client_hidePlayAreaWarning = {
  9240. if (!isNil "PLAY_AREA_WARNING_ACTIVE") then {
  9241. PLAY_AREA_WARNING_ACTIVE = false;
  9242. };
  9243. hintSilent "";
  9244. };
  9245.  
  9246. // Existing client-side code continues below...
  9247. [] spawn {
  9248. waitUntil { !isNull player && player == player };
  9249.  
  9250. // Compile loadout limitations on client
  9251. [] call compile preprocessFileLineNumbers "loadoutlimitations.sqf";
  9252.  
  9253. // Compile loadout limitations on client
  9254. [] call compile preprocessFileLineNumbers "loadoutlimitations.sqf";
  9255.  
  9256. // Periodic gear check to catch loadout changes bypassing event handlers
  9257. [] spawn {
  9258. waitUntil {!isNil "BLUFOR_UNIFORMS" && !isNil "OPFOR_UNIFORMS"};
  9259. while {true} do {
  9260. if (playerSide in [west, east]) then {
  9261. [player] call fnc_checkAndReplaceGear;
  9262. };
  9263. sleep 5;
  9264. };
  9265. };
  9266.  
  9267. if (side player == civilian) then {
  9268. player setVariable ["bluforAction", player addAction [
  9269. "<t color='#0040FF'>Join BLUFOR</t>",
  9270. {
  9271. params ["_target", "_caller", "_actionId", "_arguments"];
  9272. _caller removeAction _actionId;
  9273. _caller removeAction (_caller getVariable ["opforAction", -1]);
  9274. [_caller, west] remoteExecCall ["fnc_server_playerChooseSide", 2];
  9275. },
  9276. [], 6, true, true, "", "true"
  9277. ]];
  9278.  
  9279. player setVariable ["opforAction", player addAction [
  9280. "<t color='#FF0000'>Join OPFOR</t>",
  9281. {
  9282. params ["_target", "_caller", "_actionId", "_arguments"];
  9283. _caller removeAction _actionId;
  9284. _caller removeAction (_caller getVariable ["bluforAction", -1]);
  9285. [_caller, east] remoteExecCall ["fnc_server_playerChooseSide", 2];
  9286. },
  9287. [], 5, true, true, "", "true"
  9288. ]];
  9289.  
  9290. hint "Choose your side using the scroll wheel menu.";
  9291. } else {
  9292. [player] call fnc_initializePlayerUnit;
  9293.  
  9294. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  9295. sleep 1;
  9296.  
  9297. [player] call fnc_teleportToBase;
  9298. [player] call fnc_client_initializePlayer;
  9299.  
  9300. waitUntil {!isNil "BLUFOR_POINTS"};
  9301. [] call fnc_client_createTeamScoreUI;
  9302.  
  9303. // SHOW BRIEFING ON INITIAL SPAWN
  9304. [] call fnc_client_showBriefing;
  9305. };
  9306. };
  9307.  
  9308. [] spawn {
  9309. waitUntil { !isNull player };
  9310.  
  9311. private _trackedMarkers = createHashMap;
  9312.  
  9313. while {true} do {
  9314. private _playerSide = side player;
  9315.  
  9316. if (_playerSide in [west, east]) then {
  9317. private _allFriendlyUnits = allUnits select { side _x == _playerSide && alive _x };
  9318. private _infantryToMark = [];
  9319. private _vehiclesToMark = [];
  9320. private _playersToMark = [];
  9321.  
  9322. {
  9323. private _unit = _x;
  9324.  
  9325. // OPTIMIZATION: Cache unit type on the unit itself to avoid repeated checks
  9326. private _cachedType = _unit getVariable ["markerType", ""];
  9327.  
  9328. if (_cachedType == "") then {
  9329. // First time seeing this unit - determine and cache its type
  9330. if (isPlayer _unit) then {
  9331. _unit setVariable ["markerType", "PLAYER"];
  9332. _cachedType = "PLAYER";
  9333. } else {
  9334. private _vehicle = vehicle _unit;
  9335. if (_vehicle == _unit) then {
  9336. _unit setVariable ["markerType", "INFANTRY"];
  9337. _cachedType = "INFANTRY";
  9338. } else {
  9339. _unit setVariable ["markerType", "VEHICLE"];
  9340. _unit setVariable ["markerVehicle", _vehicle];
  9341. _cachedType = "VEHICLE";
  9342. };
  9343. };
  9344. };
  9345.  
  9346. // Use cached type for categorization
  9347. switch (_cachedType) do {
  9348. case "PLAYER": { _playersToMark pushBackUnique _unit; };
  9349. case "INFANTRY": { _infantryToMark pushBack _unit; };
  9350. case "VEHICLE": {
  9351. private _veh = _unit getVariable ["markerVehicle", vehicle _unit];
  9352. if (!isNull _veh && alive _veh) then {
  9353. _vehiclesToMark pushBackUnique _veh;
  9354. };
  9355. };
  9356. };
  9357. } forEach _allFriendlyUnits;
  9358.  
  9359. private _allCurrentNetIDs = (_infantryToMark apply {netId _x}) + (_vehiclesToMark apply {netId _x}) + (_playersToMark apply {netId _x});
  9360.  
  9361. private _markersToDelete = [];
  9362. {
  9363. private _entityNetId = _x;
  9364. if !(_entityNetId in _allCurrentNetIDs) then {
  9365. private _markerName = _y;
  9366. deleteMarkerLocal _markerName;
  9367. _markersToDelete pushBack _entityNetId;
  9368. };
  9369. } forEach _trackedMarkers;
  9370.  
  9371. { _trackedMarkers deleteAt _x } forEach _markersToDelete;
  9372.  
  9373. private _sideColor = if (_playerSide == west) then {"ColorBlue"} else {"ColorRed"};
  9374. private _markerPrefix = if (_playerSide == west) then {"b_"} else {"o_"};
  9375.  
  9376. {
  9377. private _vehicle = _x;
  9378. private _vehicleNetId = netId _vehicle;
  9379. private _markerName = format ["friendly_marker_%1", _vehicleNetId];
  9380.  
  9381. // OPTIMIZATION: Only update position if vehicle moved >10m
  9382. private _lastPos = _vehicle getVariable ["lastMarkerPos", [0,0,0]];
  9383. private _currentPos = getPos _vehicle;
  9384. private _shouldUpdate = (_lastPos distance2D _currentPos) > 10;
  9385.  
  9386. if (isNil {_trackedMarkers get _vehicleNetId}) then {
  9387. // New marker
  9388. private _markerType = "mil_unknown";
  9389. if (_vehicle isKindOf "Air") then {
  9390. _markerType = _markerPrefix + "air";
  9391. } else {
  9392. if (_vehicle isKindOf "Tank") then {
  9393. _markerType = _markerPrefix + "armor";
  9394. } else {
  9395. if (_vehicle isKindOf "APC") then {
  9396. _markerType = _markerPrefix + "mech_inf";
  9397. } else {
  9398. _markerType = _markerPrefix + "motor_inf";
  9399. };
  9400. };
  9401. };
  9402.  
  9403. createMarkerLocal [_markerName, _currentPos];
  9404. _markerName setMarkerTypeLocal _markerType;
  9405. _markerName setMarkerColorLocal _sideColor;
  9406. _markerName setMarkerSizeLocal [0.8, 0.8];
  9407. _markerName setMarkerAlphaLocal 1;
  9408. _markerName setMarkerTextLocal "";
  9409. _trackedMarkers set [_vehicleNetId, _markerName];
  9410. _vehicle setVariable ["lastMarkerPos", _currentPos];
  9411. } else {
  9412. // Existing marker - only update if moved significantly
  9413. if (_shouldUpdate) then {
  9414. _markerName setMarkerPosLocal _currentPos;
  9415. _vehicle setVariable ["lastMarkerPos", _currentPos];
  9416. };
  9417. };
  9418. } forEach _vehiclesToMark;
  9419.  
  9420. {
  9421. private _unit = _x;
  9422. private _unitNetId = netId _unit;
  9423. private _markerName = format ["friendly_marker_%1", _unitNetId];
  9424.  
  9425. // OPTIMIZATION: Only update position if unit moved >10m
  9426. private _lastPos = _unit getVariable ["lastMarkerPos", [0,0,0]];
  9427. private _currentPos = getPos _unit;
  9428. private _shouldUpdate = (_lastPos distance2D _currentPos) > 10;
  9429.  
  9430. if (isNil {_trackedMarkers get _unitNetId}) then {
  9431. createMarkerLocal [_markerName, _currentPos];
  9432. _markerName setMarkerTypeLocal "mil_dot";
  9433. _markerName setMarkerColorLocal _sideColor;
  9434. _markerName setMarkerSizeLocal [0.7, 0.7];
  9435. _markerName setMarkerAlphaLocal 1;
  9436. _markerName setMarkerTextLocal "";
  9437. _trackedMarkers set [_unitNetId, _markerName];
  9438. _unit setVariable ["lastMarkerPos", _currentPos];
  9439. } else {
  9440. if (_shouldUpdate) then {
  9441. _markerName setMarkerPosLocal _currentPos;
  9442. _unit setVariable ["lastMarkerPos", _currentPos];
  9443. };
  9444. };
  9445. } forEach _infantryToMark;
  9446.  
  9447. {
  9448. private _unit = _x;
  9449. private _unitNetId = netId _unit;
  9450. private _markerName = format ["friendly_marker_%1", _unitNetId];
  9451.  
  9452. // OPTIMIZATION: Only update position if unit moved >10m
  9453. private _lastPos = _unit getVariable ["lastMarkerPos", [0,0,0]];
  9454. private _currentPos = getPos _unit;
  9455. private _shouldUpdate = (_lastPos distance2D _currentPos) > 10;
  9456.  
  9457. if (isNil {_trackedMarkers get _unitNetId}) then {
  9458. createMarkerLocal [_markerName, _currentPos];
  9459. _markerName setMarkerTypeLocal "mil_dot";
  9460. _markerName setMarkerColorLocal _sideColor;
  9461. _markerName setMarkerSizeLocal [0.7, 0.7];
  9462. _markerName setMarkerAlphaLocal 1;
  9463. _markerName setMarkerTextLocal (name _unit);
  9464. _trackedMarkers set [_unitNetId, _markerName];
  9465. _unit setVariable ["lastMarkerPos", _currentPos];
  9466. } else {
  9467. if (_shouldUpdate) then {
  9468. _markerName setMarkerPosLocal _currentPos;
  9469. _unit setVariable ["lastMarkerPos", _currentPos];
  9470. };
  9471. };
  9472. } forEach _playersToMark;
  9473. };
  9474.  
  9475. // OPTIMIZATION: Increased from 2s to 3s
  9476. sleep 3;
  9477. };
  9478. };
  9479.  
  9480. [] spawn {
  9481. waitUntil { !isNull player && !isNil "BLUFOR_INTEL" && !isNil "OPFOR_INTEL" };
  9482.  
  9483. private _enemyIntelMarkers = createHashMap;
  9484.  
  9485. while {true} do {
  9486. private _playerSide = side player;
  9487.  
  9488. private _currentIntelData = if (_playerSide == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  9489.  
  9490. private _allKnownIntelNetIds = keys _currentIntelData;
  9491.  
  9492. private _enemyColorName = "ColorRed";
  9493. if (_playerSide == east) then { _enemyColorName = "ColorBlue"; };
  9494. private _aafColorName = "ColorGreen";
  9495.  
  9496. private _markerPrefix = if (_playerSide == west) then {"o_"} else {"b_"};
  9497. private _aafMarkerPrefix = "n_";
  9498.  
  9499. // OPTIMIZATION: Pre-calculate decay threshold (90% of max age)
  9500. private _maxIntelAge = HC_INTEL_DECAY_TIME;
  9501. private _staleThreshold = _maxIntelAge * 0.9;
  9502.  
  9503. {
  9504. private _enemyNetId = _x;
  9505. private _intelReport = _currentIntelData get _enemyNetId;
  9506.  
  9507. if (isNil "_intelReport" || {typeName _intelReport != "HASHMAP"}) then {
  9508. continue;
  9509. };
  9510.  
  9511. private _timeSpotted = _intelReport get "time";
  9512. private _intelAge = time - _timeSpotted;
  9513.  
  9514. // OPTIMIZATION: Skip intel that's about to expire (>90% of max age)
  9515. if (_intelAge > _staleThreshold) then {
  9516. continue;
  9517. };
  9518.  
  9519. private _enemyPos = _intelReport get "position";
  9520. private _isAAF = _intelReport get "isAAF";
  9521. private _entityType = _intelReport getOrDefault ["entityType", "UNKNOWN"];
  9522. private _isTank = _intelReport get "isTank";
  9523.  
  9524. // OPTIMIZATION: Pre-calculate marker name once
  9525. private _markerName = format ["enemy_intel_%1", _enemyNetId];
  9526.  
  9527. private _fadeAlpha = 1 - (_intelAge / _maxIntelAge);
  9528. _fadeAlpha = (_fadeAlpha max 0.2) min 1.0;
  9529.  
  9530. private _markerColorName = if (_isAAF) then { _aafColorName } else { _enemyColorName };
  9531.  
  9532. private _markerType = "mil_dot";
  9533. private _currentPrefix = if (_isAAF) then {_aafMarkerPrefix} else {_markerPrefix};
  9534. if (_entityType == "GROUND_VEHICLE") then {
  9535. if (_isTank) then {
  9536. _markerType = _currentPrefix + "armor";
  9537. } else {
  9538. _markerType = _currentPrefix + "mech_inf";
  9539. };
  9540. } else {
  9541. if (_entityType == "AIR_VEHICLE") then {
  9542. _markerType = _currentPrefix + "air";
  9543. };
  9544. };
  9545.  
  9546. if (isNil {_enemyIntelMarkers get _markerName}) then {
  9547. createMarkerLocal [_markerName, _enemyPos];
  9548. _enemyIntelMarkers set [_markerName, true];
  9549. };
  9550.  
  9551. _markerName setMarkerPosLocal _enemyPos;
  9552. _markerName setMarkerTypeLocal _markerType;
  9553. _markerName setMarkerColorLocal _markerColorName;
  9554. _markerName setMarkerAlphaLocal _fadeAlpha;
  9555. _markerName setMarkerSizeLocal [0.8, 0.8];
  9556. _markerName setMarkerTextLocal "";
  9557.  
  9558. } forEach _allKnownIntelNetIds;
  9559.  
  9560. private _markersToDelete = [];
  9561. {
  9562. private _markerName = _x;
  9563. private _enemyNetId = (_markerName splitString "_") select 2;
  9564.  
  9565. if !(_enemyNetId in _allKnownIntelNetIds) then {
  9566. deleteMarkerLocal _markerName;
  9567. _markersToDelete pushBack _markerName;
  9568. };
  9569. } forEach (keys _enemyIntelMarkers);
  9570.  
  9571. {
  9572. _enemyIntelMarkers deleteAt _x;
  9573. } forEach _markersToDelete;
  9574.  
  9575. // OPTIMIZATION: Increased from 2s to 3s
  9576. sleep 3;
  9577. };
  9578. };
  9579. };
  9580.  
  9581.  
  9582. if (isServer) then {
  9583. [] spawn {
  9584. waitUntil {time > 0};
  9585.  
  9586. {
  9587. if (!isPlayer _x && isPlayer leader _x == false) then {
  9588. _x disableAI "ALL";
  9589. _x enableAI "ANIM";
  9590. _x enableAI "MOVE";
  9591. };
  9592. } forEach playableUnits;
  9593. };
  9594. };
  9595.  
  9596. fnc_client_setupTeamSelection = {
  9597. params ["_unit"];
  9598. if (!hasInterface || _unit != player) exitWith {};
  9599.  
  9600. // Wait for base objects to be available
  9601. waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
  9602.  
  9603. _unit setVariable ["bluforAction", _unit addAction [
  9604. "<t color='#0040FF'>Join BLUFOR</t>",
  9605. {
  9606. params ["_target", "_caller", "_actionId", "_arguments"];
  9607. _caller removeAction _actionId;
  9608. _caller removeAction (_caller getVariable ["opforAction", -1]);
  9609. [_caller, west] remoteExecCall ["fnc_server_playerChooseSide", 2];
  9610. },
  9611. [], 6, true, true, "", "true"
  9612. ]];
  9613.  
  9614. _unit setVariable ["opforAction", _unit addAction [
  9615. "<t color='#FF0000'>Join OPFOR</t>",
  9616. {
  9617. params ["_target", "_caller", "_actionId", "_arguments"];
  9618. _caller removeAction _actionId;
  9619. _caller removeAction (_caller getVariable ["bluforAction", -1]);
  9620. [_caller, east] remoteExecCall ["fnc_server_playerChooseSide", 2];
  9621. },
  9622. [], 5, true, true, "", "true"
  9623. ]];
  9624.  
  9625. hint "Choose your side using the scroll wheel menu.";
  9626. };
  9627.  
  9628. ==================== END OF: playerinit.sqf ====================
  9629.  
  9630.  
  9631.  
  9632. ==================== START OF: player_server.sqf ====================
  9633.  
  9634. // player_server.sqf
  9635. // Contains server-side logic for player points and purchases.
  9636.  
  9637. fnc_server_purchaseVehicle = {
  9638. params ["_player", "_vehicleClass"];
  9639. if (!isServer) exitWith {};
  9640.  
  9641. // Get vehicle data from server-side config (now from init.sqf)
  9642. private _data = PURCHASE_VEHICLE_DATA getOrDefault [_vehicleClass, nil];
  9643.  
  9644. if (isNil "_data") exitWith {
  9645. ["SERVER: Invalid vehicle type selected."] remoteExecCall ["hint", owner _player];
  9646. };
  9647.  
  9648. private _displayName = _data select 0;
  9649. private _cost = _data select 1;
  9650. private _requiredSide = _data select 2;
  9651. private _playerSide = side _player;
  9652. private _playerPoints = _player getVariable ["playerPoints", 0];
  9653.  
  9654. // Check if player is on the correct side for this vehicle
  9655. if (_playerSide != _requiredSide) exitWith {
  9656. ["SERVER: This vehicle is not available for your faction."] remoteExecCall ["hint", owner _player];
  9657. };
  9658.  
  9659. // Verify player has enough points
  9660. if (_playerPoints < _cost) exitWith {
  9661. [format["SERVER: Not enough points. You need %1, you have %2.", _cost, _playerPoints]] remoteExecCall ["hint", owner _player];
  9662. };
  9663.  
  9664. // Deduct points and broadcast the new value
  9665. _player setVariable ["playerPoints", _playerPoints - _cost, true];
  9666.  
  9667. // Find a flatter spawn position in a larger radius.
  9668. private _baseObj = if (_playerSide == west) then { bluforSpawnObj } else { opforSpawnObj };
  9669. private _spawnPos = [getPos _baseObj, 20, 100, 15, 0, 0.1, 0] call BIS_fnc_findSafePos;
  9670. if (count _spawnPos == 0) then { _spawnPos = getPos _baseObj; }; // Fallback
  9671.  
  9672. private _vehicle = createVehicle [_vehicleClass, _spawnPos vectorAdd [0,0,1.5], [], 0, "NONE"];
  9673. _vehicle setPos _spawnPos;
  9674. _vehicle setVectorUp [0,0,1];
  9675. _vehicle setVelocity [0,0,0];
  9676.  
  9677. // ==================== CHANGE START ====================
  9678. _vehicle setVariable ["unitValue", _cost, true]; // Set the point value for kill rewards
  9679. // ===================== CHANGE END =====================
  9680.  
  9681. _vehicle lock 1;
  9682. _vehicle setVariable ["ownerPlayer", _player, true];
  9683.  
  9684. [_vehicle] remoteExecCall ["fnc_client_moveIntoVehicle", owner _player];
  9685.  
  9686. [getPosATL _vehicle, _displayName] remoteExecCall ["fnc_client_addVehicleWaypoint", owner _player];
  9687. [format["%1 delivered. %2 points remaining.", _displayName, _player getVariable "playerPoints"]] remoteExecCall ["hint", owner _player];
  9688. };
  9689.  
  9690. fnc_server_initializePlayer = {
  9691. params ["_player"];
  9692. if (!isServer) exitWith {};
  9693.  
  9694. _player setVariable ["playerPoints", 5, true];
  9695. _player setVariable ["teamkillCount", 0, true];
  9696. _player setVariable ["penaltyRespawn", false, true];
  9697. _player setVariable ["clearMyPenaltyFlag", false, true];
  9698.  
  9699. [
  9700. "Welcome! You start with 1 personal point. Get kills to earn more. Approach the arsenal box to open the purchase menu."
  9701. ] remoteExecCall ["hint", owner _player];
  9702. };
  9703.  
  9704. // Server-side function to paradrop a player near the AAF stronghold
  9705. fnc_server_paradropPlayer = {
  9706. params ["_player"];
  9707. if (!isServer) exitWith {};
  9708.  
  9709. // Check if 2 minutes have passed
  9710. if (time < 120) exitWith {
  9711. ["Paradrop unavailable: Must wait 2 minutes into the mission."] remoteExecCall ["hint", owner _player];
  9712. };
  9713.  
  9714. // Check if mission_centralPoint exists (AAF stronghold position)
  9715. if (isNil "mission_centralPoint") exitWith {
  9716. ["Paradrop unavailable: Stronghold location unknown."] remoteExecCall ["hint", owner _player];
  9717. };
  9718.  
  9719. private _playerPoints = _player getVariable ["playerPoints", 0];
  9720. private _cost = 2;
  9721.  
  9722. // Verify player has enough points
  9723. if (_playerPoints < _cost) exitWith {
  9724. [format["Not enough points. You need %1, you have %2.", _cost, _playerPoints]] remoteExecCall ["hint", owner _player];
  9725. };
  9726.  
  9727. // Get player's side and base position
  9728. private _playerSide = side _player;
  9729. private _ownBase = if (_playerSide == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  9730. private _strongholdPos = missionNamespace getVariable ["mission_centralPoint", [0,0,0]];
  9731.  
  9732. // Validate stronghold position
  9733. if (_strongholdPos isEqualTo [0,0,0]) exitWith {
  9734. ["Paradrop unavailable: Invalid stronghold location."] remoteExecCall ["hint", owner _player];
  9735. };
  9736.  
  9737. // Calculate direction from stronghold to player's base
  9738. private _dirToBase = _strongholdPos getDir _ownBase;
  9739.  
  9740. // Calculate drop position: 300m offset toward own base, then randomize within 100m radius
  9741. private _baseDropPos = _strongholdPos getPos [300, _dirToBase];
  9742.  
  9743. // Add random offset within 100m radius
  9744. private _randomAngle = random 360;
  9745. private _randomDistance = random 100;
  9746. private _dropPos = _baseDropPos getPos [_randomDistance, _randomAngle];
  9747. // Set altitude to a random value between 400 and 600m
  9748. _dropPos set [2, 400 + random 200];
  9749.  
  9750. // Deduct points
  9751. _player setVariable ["playerPoints", _playerPoints - _cost, true];
  9752.  
  9753. private _groupToDrop = units (group _player);
  9754. private _unitData = []; // Store original backpacks and other data
  9755.  
  9756. {
  9757. private _unit = _x;
  9758.  
  9759. // Store unit's current backpack data
  9760. _unitData pushBack [_unit, backpack _unit, backpackItems _unit, backpackMagazines _unit];
  9761.  
  9762. // Calculate a slightly offset drop position for each unit
  9763. private _offsetDropPos = _dropPos getPos [random 25, random 360];
  9764. _offsetDropPos set [2, (_dropPos select 2) + (random 50)];
  9765.  
  9766. // Disable damage before teleporting to prevent accidental death
  9767. if (!isPlayer _unit) then {
  9768. _unit allowDamage false;
  9769. // NEW: Set flag to track parachute status
  9770. _unit setVariable ["parachuteGiven", false, true];
  9771. };
  9772.  
  9773. // Teleport unit
  9774. _unit setPosASL (AGLToASL _offsetDropPos);
  9775. _unit setVelocity [0, 0, 0];
  9776.  
  9777. // Only give the parachute to the human player. AI will get theirs later.
  9778. if (isPlayer _unit) then {
  9779. _unit addBackpack "B_Parachute";
  9780. };
  9781.  
  9782. } forEach _groupToDrop;
  9783.  
  9784. // Notify player
  9785. [format["Paradrop initiated for your squad! %1 points remaining.", _playerPoints - _cost]] remoteExecCall ["hint", owner _player];
  9786.  
  9787. // Monitor for landing and restore backpacks for the whole group
  9788. [_unitData] spawn {
  9789. params ["_allUnitData"];
  9790.  
  9791. while {count _allUnitData > 0} do {
  9792. {
  9793. private _dataEntry = _x;
  9794. private _unit = _dataEntry select 0;
  9795.  
  9796. if (!alive _unit) then {
  9797. _allUnitData = _allUnitData - [_dataEntry];
  9798. };
  9799.  
  9800. if (isPlayer _unit) then {
  9801. // This is the player unit - force their chute open at 120m as a failsafe
  9802. if (alive _unit && !isTouchingGround _unit && backpack _unit == "B_Parachute" && ((getPosATL _unit) select 2) <= 120) then {
  9803. _unit action ["OpenParachute", vehicle _unit];
  9804. };
  9805. } else {
  9806. // This is an AI unit - give them a chute and open it at 120m
  9807. // NEW: Check if parachute was already given using flag and vehicle check
  9808. private _parachuteGiven = _unit getVariable ["parachuteGiven", false];
  9809. private _vehicle = vehicle _unit;
  9810. private _isInParachute = (_vehicle != _unit && {_vehicle isKindOf "ParachuteBase"});
  9811.  
  9812. if (alive _unit && !isTouchingGround _unit && !_parachuteGiven && !_isInParachute && ((getPosATL _unit) select 2) <= 120) then {
  9813. _unit addBackpack "B_Parachute";
  9814. sleep 0.1;
  9815. _unit action ["OpenParachute", vehicle _unit];
  9816. // Set flag to prevent multiple parachutes
  9817. _unit setVariable ["parachuteGiven", true, true];
  9818. // Re-enable damage after the chute is open
  9819. _unit allowDamage true;
  9820. };
  9821. };
  9822.  
  9823. if (alive _unit && isTouchingGround _unit) then {
  9824. private _originalBackpack = _dataEntry select 1;
  9825. private _items = _dataEntry select 2;
  9826. private _magazines = _dataEntry select 3;
  9827.  
  9828. sleep 0.5;
  9829.  
  9830. // Failsafe in case unit lands without ever opening chute
  9831. if (!isPlayer _unit) then {
  9832. _unit allowDamage true;
  9833. // Clear the parachute flag
  9834. _unit setVariable ["parachuteGiven", nil, true];
  9835. };
  9836.  
  9837. if (_originalBackpack != "") then {
  9838. if (backpack _unit == "B_Parachute") then {
  9839. removeBackpack _unit;
  9840. };
  9841.  
  9842. _unit addBackpack _originalBackpack;
  9843. clearBackpackCargoGlobal (backpackContainer _unit);
  9844.  
  9845. { (backpackContainer _unit) addItemCargoGlobal [_x, 1]; } forEach _items;
  9846. { (backpackContainer _unit) addMagazineCargoGlobal [_x, 1]; } forEach _magazines;
  9847. };
  9848.  
  9849. _allUnitData = _allUnitData - [_dataEntry];
  9850. };
  9851. } forEach _allUnitData;
  9852.  
  9853. sleep 0.5;
  9854. };
  9855. };
  9856. };
  9857.  
  9858. // Server-side function to store respawn data and handle post-respawn processing
  9859. fnc_server_storeRespawnData = {
  9860. params ["_deadUnit", "_unitLoadout", "_playerPoints", "_teamkillCount", "_isPenaltyRespawn"];
  9861. if (!isServer) exitWith {};
  9862.  
  9863. private _playerUID = getPlayerUID _deadUnit;
  9864.  
  9865. // Store the data with a unique key for this specific death event
  9866. private _deathTime = time;
  9867. private _respawnDataKey = format["respawnData_%1_%2", _playerUID, _deathTime];
  9868.  
  9869. private _respawnData = createHashMap;
  9870. _respawnData set ["loadout", _unitLoadout];
  9871. _respawnData set ["points", _playerPoints];
  9872. _respawnData set ["teamkills", _teamkillCount];
  9873. _respawnData set ["penalty", _isPenaltyRespawn];
  9874. _respawnData set ["deathTime", _deathTime];
  9875.  
  9876. missionNamespace setVariable [_respawnDataKey, _respawnData, false];
  9877.  
  9878. // Monitor for this specific player's respawn using their UID and death time
  9879. [_playerUID, _deathTime] spawn {
  9880. params ["_uid", "_deathTime"];
  9881.  
  9882. private _timeout = time + 60;
  9883. private _respawnDataKey = format["respawnData_%1_%2", _uid, _deathTime];
  9884.  
  9885. while {time < _timeout} do {
  9886. // Look for a player with matching UID who respawned after the death time
  9887. private _respawnedPlayer = objNull;
  9888. {
  9889. if (isPlayer _x &&
  9890. getPlayerUID _x == _uid &&
  9891. alive _x &&
  9892. (_x getVariable ["spawnTime", 0]) > _deathTime) exitWith {
  9893. _respawnedPlayer = _x;
  9894. };
  9895. } forEach allPlayers;
  9896.  
  9897. if (!isNull _respawnedPlayer) exitWith {
  9898. private _storedData = missionNamespace getVariable [_respawnDataKey, createHashMap];
  9899.  
  9900. if (count _storedData > 0) then {
  9901. private _loadout = _storedData get "loadout";
  9902. private _points = _storedData get "points";
  9903. private _teamkills = _storedData get "teamkills";
  9904. private _penalty = _storedData get "penalty";
  9905.  
  9906. // Apply data to this specific player
  9907. _respawnedPlayer setUnitLoadout _loadout;
  9908. _respawnedPlayer setVariable ["playerPoints", _points, true];
  9909. _respawnedPlayer setVariable ["teamkillCount", _teamkills, true];
  9910.  
  9911. if (_penalty) then {
  9912. _respawnedPlayer setVariable ["clearMyPenaltyFlag", true, true];
  9913. };
  9914.  
  9915. // Apply respawn actions to ONLY this player
  9916. private _playerOwner = owner _respawnedPlayer;
  9917. [_respawnedPlayer] remoteExecCall ["fnc_teleportToBase", _playerOwner, false];
  9918. [_respawnedPlayer] remoteExecCall ["fnc_initializePlayerUnit", _playerOwner, false];
  9919. [_respawnedPlayer] remoteExecCall ["fnc_applyRespawnProtection", _playerOwner, false];
  9920. [] remoteExecCall ["fnc_client_showBriefing", _playerOwner, false];
  9921.  
  9922. // Clean up
  9923. missionNamespace setVariable [_respawnDataKey, nil, false];
  9924. };
  9925. };
  9926.  
  9927. sleep 1;
  9928. };
  9929.  
  9930. // Cleanup on timeout
  9931. missionNamespace setVariable [_respawnDataKey, nil, false];
  9932. };
  9933. };
  9934.  
  9935. // Server-side function to handle a player choosing their side.
  9936. fnc_server_playerChooseSide = {
  9937. params ["_civilianUnit", "_chosenSide"];
  9938. if (!isServer) exitWith {};
  9939.  
  9940. // Handle case where civilian unit might be null (for rejoining players)
  9941. private _isRejoining = isNull _civilianUnit;
  9942.  
  9943. // Get player info differently based on whether they're rejoining
  9944. private _playerUID = "";
  9945. private _playerOwner = 0;
  9946. private _currentPlayerUnit = objNull;
  9947.  
  9948. if (_isRejoining) then {
  9949. // For rejoining players, we need to find them by the side they're joining
  9950. _playerUID = param [2, ""];
  9951. _playerOwner = param [3, 0];
  9952.  
  9953. // Find if this player already has a unit they're controlling
  9954. {
  9955. if (getPlayerUID _x == _playerUID) exitWith {
  9956. _currentPlayerUnit = _x;
  9957. };
  9958. } forEach allPlayers;
  9959. } else {
  9960. // Normal case - player choosing side from civilian
  9961. if (!alive _civilianUnit) exitWith {};
  9962. _playerUID = getPlayerUID _civilianUnit;
  9963. _playerOwner = owner _civilianUnit;
  9964. _currentPlayerUnit = _civilianUnit;
  9965. };
  9966.  
  9967. if (_playerUID != "") then {
  9968. (missionNamespace getVariable "PLAYER_TEAM_LOCKS") set [_playerUID, _chosenSide];
  9969. };
  9970.  
  9971. // Spawn the entire process to allow delays
  9972. [_civilianUnit, _chosenSide, _playerUID, _playerOwner, _isRejoining, _currentPlayerUnit] spawn {
  9973. params ["_civilianUnit", "_chosenSide", "_playerUID", "_playerOwner", "_isRejoining", "_currentPlayerUnit"];
  9974.  
  9975. private _officerType = if (_chosenSide == west) then {"B_officer_F"} else {"O_officer_F"};
  9976. private _spawnObj = if (_chosenSide == west) then {bluforSpawnObj} else {opforSpawnObj};
  9977. private _spawnPos = getPosATL _spawnObj;
  9978. _spawnPos set [2, 0.5];
  9979.  
  9980. private _playerGroup = createGroup _chosenSide;
  9981. _playerGroup setGroupOwner _playerOwner;
  9982.  
  9983. sleep 0.1;
  9984.  
  9985. private _newUnit = _playerGroup createUnit [_officerType, _spawnPos, [], 0, "NONE"];
  9986.  
  9987. // Mark the spawn time for respawn tracking
  9988. _newUnit setVariable ["spawnTime", time, true];
  9989.  
  9990. [_newUnit] call fnc_server_initializePlayer;
  9991.  
  9992. sleep 0.2;
  9993.  
  9994. // Switch player control
  9995. [_newUnit] remoteExecCall ["fnc_client_completeSideSwitch", _playerOwner];
  9996.  
  9997. // Wait for the switch to complete before deleting old unit
  9998. private _timeout = time + 15;
  9999. waitUntil {(_newUnit getVariable ["sideSwitchComplete", false]) || time > _timeout};
  10000.  
  10001. // Delete the old unit (whether it's civilian or any other unit the player was controlling)
  10002. if (!isNull _currentPlayerUnit && _currentPlayerUnit != _newUnit) then {
  10003. deleteVehicle _currentPlayerUnit;
  10004. };
  10005. };
  10006. };
  10007.  
  10008. [] spawn {
  10009. if (!isServer) exitWith {};
  10010.  
  10011. while {true} do {
  10012. sleep 5;
  10013. {
  10014. if (_x getVariable ["clearMyPenaltyFlag", false]) then {
  10015. _x setVariable ["penaltyRespawn", false, true];
  10016. _x setVariable ["clearMyPenaltyFlag", false, true];
  10017. };
  10018. } forEach playableUnits;
  10019. };
  10020. };
  10021.  
  10022. fnc_server_purchaseAISquad = {
  10023. params ["_player"];
  10024. if (!isServer) exitWith {};
  10025.  
  10026. private _cost = missionNamespace getVariable ["PLAYER_SQUAD_COST", 15];
  10027. private _cooldown = missionNamespace getVariable ["PLAYER_SQUAD_COOLDOWN", 120];
  10028. private _lastPurchaseTime = _player getVariable ["lastAISquadPurchaseTime", 0];
  10029.  
  10030. // Cooldown check
  10031. if (time < _lastPurchaseTime + _cooldown) exitWith {
  10032. private _timeLeft = round((_lastPurchaseTime + _cooldown) - time);
  10033. [format["AI Squad purchase on cooldown. Time remaining: %1s", _timeLeft]] remoteExecCall ["hint", owner _player];
  10034. };
  10035.  
  10036. // Cost check
  10037. private _playerPoints = _player getVariable ["playerPoints", 0];
  10038. if (_playerPoints < _cost) exitWith {
  10039. [format["Not enough points to purchase an AI squad. Cost: %1, You have: %2.", _cost, _playerPoints]] remoteExecCall ["hint", owner _player];
  10040. };
  10041.  
  10042. // Deduct points
  10043. _player setVariable ["playerPoints", _playerPoints - _cost, true];
  10044.  
  10045. private _playerGroup = group _player;
  10046. private _playerSide = side _player;
  10047.  
  10048. // Wipe existing group members (except player)
  10049. {
  10050. if (_x != _player) then {
  10051. private _vehicle = vehicle _x;
  10052. // If the AI is in a parachute, delete the parachute as well.
  10053. if (_vehicle != _x && {_vehicle isKindOf "ParachuteBase"}) then {
  10054. deleteVehicle _vehicle;
  10055. };
  10056. deleteVehicle _x;
  10057. };
  10058. } forEach (units _playerGroup);
  10059.  
  10060. // Select unit pool
  10061. private _reconPool = if (_playerSide == west) then { BLUFOR_SPECOPS_POOL } else { OPFOR_SPECOPS_POOL };
  10062. if (isNil "_reconPool" || {count _reconPool == 0}) exitWith { diag_log "ERROR: Recon pool not found for player's side."; };
  10063.  
  10064. private _squadComposition = selectRandom _reconPool;
  10065.  
  10066. // Spawn 3 random recon units
  10067. for "_i" from 1 to 3 do {
  10068. private _unitType = selectRandom _squadComposition;
  10069. private _spawnPos = getPos _player;
  10070.  
  10071. private _unit = _playerGroup createUnit [_unitType, _spawnPos, [], 5, "NONE"];
  10072.  
  10073. // Apply recon AI settings from fnc_spawnBluforSpecOps
  10074. [_unit, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
  10075. _unit setVariable ["isSpecOps", true, true];
  10076. _unit setVariable ["unitValue", SPECOPS_VALUE, true];
  10077. [_unit] call fnc_markAsImportant;
  10078. [_unit, _playerSide] call fnc_updateStrengthTally; // Count towards maxAI/strength
  10079. _unit setVariable ["ownerPlayer", _player, true]; // TAG THE AI WITH ITS OWNER
  10080. };
  10081.  
  10082. // Check and enforce hard AI cap after spawning
  10083. private _sideAICount = count (allUnits select {side _x == _playerSide && !isPlayer _x && alive _x});
  10084. private _hardCap = maxAI + 10;
  10085.  
  10086. if (_sideAICount > _hardCap) then {
  10087. private _unitsToDeleteCount = _sideAICount - _hardCap;
  10088. systemChat format ["%1 side is over AI hard cap. Culling %2 units.", _playerSide, _unitsToDeleteCount];
  10089.  
  10090. // Find all AI on the team, excluding the player's own group
  10091. private _potentialUnitsToDelete = allUnits select {
  10092. side _x == _playerSide &&
  10093. !isPlayer _x &&
  10094. alive _x &&
  10095. group _x != _playerGroup
  10096. };
  10097.  
  10098. // Prioritize deleting standard infantry first
  10099. private _standardInfantry = _potentialUnitsToDelete select {
  10100. (_x getVariable ["unitValue", 999]) == INFANTRY_VALUE
  10101. };
  10102.  
  10103. // Delete the required number of units
  10104. for "_i" from 1 to _unitsToDeleteCount do {
  10105. if (count _standardInfantry > 0) then {
  10106. private _unitToDelete = _standardInfantry select 0; // Get the first one
  10107. deleteVehicle _unitToDelete;
  10108. _standardInfantry deleteAt 0; // Remove it from the list
  10109. } else {
  10110. diag_log "AI hard cap reached, but no more standard infantry to delete.";
  10111. break;
  10112. };
  10113. };
  10114. };
  10115.  
  10116. // Apply cooldown AFTER successful purchase
  10117. _player setVariable ["lastAISquadPurchaseTime", time, true];
  10118.  
  10119. [format["AI Recon Squad purchased for %1 points. %2 points remaining.", _cost, _player getVariable "playerPoints"]] remoteExecCall ["hint", owner _player];
  10120. };
  10121. // =====================================================================
  10122. // Admin/Host/Singleplayer Settings Functions
  10123. // =====================================================================
  10124.  
  10125. // Function to set the max AI count
  10126. fnc_server_setMaxAI = {
  10127. params ["_count"];
  10128. if (!isServer) exitWith {}; // Only server can change this
  10129.  
  10130. maxAI = _count;
  10131. publicVariable "maxAI";
  10132.  
  10133. hint format ["Server: Max AI count has been set to %1.", maxAI];
  10134. };
  10135.  
  10136. // Function to set the time multiplier
  10137. fnc_server_setTimescale = {
  10138. params ["_multiplier"];
  10139. if (!isServer) exitWith {}; // Only server can change this
  10140.  
  10141. setTimeMultiplier _multiplier;
  10142.  
  10143. private _text = "Realtime";
  10144. if (_multiplier == 12) then { _text = "2h = 1 Day"; };
  10145. if (_multiplier == 24) then { _text = "1h = 1 Day"; };
  10146.  
  10147. hint format ["Server: Timescale set to %1.", _text];
  10148. };
  10149.  
  10150. // Function to adjust team points (SP only)
  10151. fnc_server_adjustTeamPoints = {
  10152. params ["_sideStr", "_amount"];
  10153. if (!isServer || isMultiplayer) exitWith {}; // Only server in singleplayer
  10154.  
  10155. if (_sideStr == "BLUFOR") then {
  10156. BLUFOR_POINTS = BLUFOR_POINTS + _amount;
  10157. publicVariable "BLUFOR_POINTS";
  10158. hint format ["BLUFOR points adjusted by %1. New total: %2", _amount, round BLUFOR_POINTS];
  10159. } else {
  10160. OPFOR_POINTS = OPFOR_POINTS + _amount;
  10161. publicVariable "OPFOR_POINTS";
  10162. hint format ["OPFOR points adjusted by %1. New total: %2", _amount, round OPFOR_POINTS];
  10163. };
  10164. };
  10165.  
  10166. // Function to add personal points to the player (SP only)
  10167. fnc_server_addPersonalPoints = {
  10168. params ["_player", "_amount"];
  10169. if (!isServer || isMultiplayer) exitWith {}; // Only server in singleplayer
  10170.  
  10171. private _currentPoints = _player getVariable ["playerPoints", 0];
  10172. _player setVariable ["playerPoints", _currentPoints + _amount, true];
  10173.  
  10174. [format ["Added %1 personal points. You now have %2.", _amount, _player getVariable 'playerPoints']] remoteExecCall ["hint", owner _player];
  10175. };
  10176.  
  10177. // Function to delete AI squad members upon player's death
  10178. fnc_server_deletePlayerAISquad = {
  10179. params ["_unitsToDelete"];
  10180. if (!isServer) exitWith {};
  10181.  
  10182. {
  10183. // Ensure the unit object is not null and is a valid AI before deleting
  10184. if (!isNull _x && !isPlayer _x) then {
  10185. private _vehicle = vehicle _x;
  10186. // If the AI is in a parachute, delete the parachute as well.
  10187. if (_vehicle != _x && {_vehicle isKindOf "ParachuteBase"}) then {
  10188. deleteVehicle _vehicle;
  10189. };
  10190. deleteVehicle _x;
  10191. };
  10192. } forEach _unitsToDelete;
  10193. };
  10194.  
  10195. ==================== END OF: player_server.sqf ====================
  10196.  
  10197.  
  10198.  
  10199. ==================== START OF: strategy.sqf ====================
  10200.  
  10201. // strategy.sqf
  10202. // Strategic thinking and outcome prediction for High Command AI
  10203.  
  10204. // Initialize strategy variables
  10205. if (isNil "BLUFOR_STRATEGIC_PLAN") then { BLUFOR_STRATEGIC_PLAN = createHashMap; };
  10206. if (isNil "OPFOR_STRATEGIC_PLAN") then { OPFOR_STRATEGIC_PLAN = createHashMap; };
  10207. if (isNil "BLUFOR_HELD_POSITIONS") then { BLUFOR_HELD_POSITIONS = []; };
  10208. if (isNil "OPFOR_HELD_POSITIONS") then { OPFOR_HELD_POSITIONS = []; };
  10209.  
  10210. // Find high ground positions on the battlefield
  10211. fnc_findHighGroundPositions = {
  10212. params ["_side", "_centerPos", "_radius"];
  10213.  
  10214. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  10215. private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
  10216.  
  10217. private _highGroundPositions = [];
  10218. private _searchPositions = [];
  10219.  
  10220. // Create search grid around center position
  10221. for "_angle" from 0 to 315 step 45 do {
  10222. for "_dist" from 300 to _radius step 200 do {
  10223. private _testPos = _centerPos getPos [_dist, _angle];
  10224. _searchPositions pushBack _testPos;
  10225. };
  10226. };
  10227.  
  10228. // Evaluate each position for elevation
  10229. {
  10230. private _pos = _x;
  10231. private _height = getTerrainHeightASL _pos;
  10232.  
  10233. // Check surrounding area is lower (confirms it's high ground)
  10234. private _surroundingHeights = [];
  10235. for "_i" from 0 to 7 do {
  10236. private _checkPos = _pos getPos [50, _i * 45];
  10237. _surroundingHeights pushBack (getTerrainHeightASL _checkPos);
  10238. };
  10239.  
  10240. private _avgSurroundingHeight = 0;
  10241. {_avgSurroundingHeight = _avgSurroundingHeight + _x;} forEach _surroundingHeights;
  10242. _avgSurroundingHeight = _avgSurroundingHeight / count _surroundingHeights;
  10243.  
  10244. // If this position is higher than surrounding area
  10245. if (_height > (_avgSurroundingHeight + 10) && !surfaceIsWater _pos) then {
  10246. private _highGroundData = createHashMap;
  10247. _highGroundData set ["position", _pos];
  10248. _highGroundData set ["elevation", _height];
  10249. _highGroundData set ["elevationAdvantage", _height - _avgSurroundingHeight];
  10250. _highGroundData set ["distanceToFront", _pos distance2D _centerPos];
  10251. _highGroundData set ["distanceToEnemyBase", _pos distance2D _enemyBase];
  10252. _highGroundData set ["distanceToOwnBase", _pos distance2D _ownBase];
  10253.  
  10254. _highGroundPositions pushBack _highGroundData;
  10255. };
  10256. } forEach _searchPositions;
  10257.  
  10258. // Sort by tactical value (elevation advantage + proximity to battle)
  10259. _highGroundPositions = [_highGroundPositions, [], {
  10260. private _elevScore = (_x get "elevationAdvantage") * 2;
  10261. private _distScore = 1000 - ((_x get "distanceToFront") min 1000);
  10262. _elevScore + _distScore
  10263. }, "DESCEND"] call BIS_fnc_sortBy;
  10264.  
  10265. // Return top 3 positions
  10266. _highGroundPositions select [0, 3 min count _highGroundPositions]
  10267. };
  10268.  
  10269. // Find containment positions around enemy base to block reinforcements
  10270. fnc_findContainmentPositions = {
  10271. params ["_side", "_enemyBase"];
  10272.  
  10273. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  10274.  
  10275. // Calculate the direction from enemy base to our base (their reinforcement route)
  10276. private _reinforcementAxis = _enemyBase getDir _ownBase;
  10277.  
  10278. private _containmentPositions = [];
  10279. private _containmentRadius = 800; // 800m from enemy base
  10280.  
  10281. // Create 5 containment positions in an arc facing our base (where enemies come from)
  10282. // This blocks the enemy's path between their base and the battlefield
  10283. for "_i" from 0 to 4 do {
  10284. // Spread positions in a 120-degree arc centered on the reinforcement axis
  10285. private _angleOffset = -60 + (_i * 30); // -60, -30, 0, 30, 60 degrees
  10286. private _positionAngle = _reinforcementAxis + _angleOffset;
  10287.  
  10288. private _testPos = _enemyBase getPos [_containmentRadius, _positionAngle];
  10289.  
  10290. // Validate position
  10291. if (!surfaceIsWater _testPos) then {
  10292. private _height = getTerrainHeightASL _testPos;
  10293.  
  10294. private _containmentData = createHashMap;
  10295. _containmentData set ["position", _testPos];
  10296. _containmentData set ["angle", _positionAngle];
  10297. _containmentData set ["arcPosition", _i]; // 0=far left, 2=center, 4=far right
  10298. _containmentData set ["elevation", _height];
  10299. _containmentData set ["distanceToEnemyBase", _testPos distance2D _enemyBase];
  10300. _containmentData set ["distanceToOwnBase", _testPos distance2D _ownBase];
  10301.  
  10302. _containmentPositions pushBack _containmentData;
  10303. };
  10304. };
  10305.  
  10306. _containmentPositions
  10307. };
  10308.  
  10309. // Find nearby friendly groups that could assist an objective
  10310. fnc_findNearbyReinforcements = {
  10311. params ["_side", "_objectivePos", "_excludeGroups", "_maxDistance"];
  10312.  
  10313. private _allFriendlyGroups = allGroups select {
  10314. side _x == _side &&
  10315. count (units _x) > 0 &&
  10316. ({isPlayer _x} count (units _x) == 0) &&
  10317. !(_x in _excludeGroups)
  10318. };
  10319.  
  10320. private _nearbyGroups = [];
  10321.  
  10322. {
  10323. private _group = _x;
  10324. private _leader = leader _group;
  10325.  
  10326. if (!isNull _leader && alive _leader) then {
  10327. private _distance = _leader distance2D _objectivePos;
  10328.  
  10329. if (_distance <= _maxDistance) then {
  10330. // Check if group is locked or busy
  10331. private _isLocked = [_group] call fnc_isGroupLocked;
  10332.  
  10333. if (!_isLocked) then {
  10334. private _reinforcementData = createHashMap;
  10335. _reinforcementData set ["group", _group];
  10336. _reinforcementData set ["distance", _distance];
  10337. _reinforcementData set ["type", [_group] call fnc_classifyGroup];
  10338. _reinforcementData set ["strength", count (units _group select {alive _x})];
  10339.  
  10340. _nearbyGroups pushBack _reinforcementData;
  10341. };
  10342. };
  10343. };
  10344. } forEach _allFriendlyGroups;
  10345.  
  10346. // Sort by distance (closest first)
  10347. _nearbyGroups = [_nearbyGroups, [], {_x get "distance"}, "ASCEND"] call BIS_fnc_sortBy;
  10348.  
  10349. _nearbyGroups
  10350. };
  10351.  
  10352. // Calculate minimum defenders needed for base
  10353. fnc_calculateMinimumDefenders = {
  10354. params ["_side"];
  10355.  
  10356. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  10357. private _enemySide = if (_side == west) then {east} else {west};
  10358.  
  10359. // Count enemies near base
  10360. private _nearbyEnemies = _ownBase nearEntities [["CAManBase", "LandVehicle", "Air"], 1000];
  10361. private _enemyCount = {side _x == _enemySide && alive _x} count _nearbyEnemies;
  10362.  
  10363. // Base minimum defenders
  10364. private _minDefenders = 2;
  10365.  
  10366. // Add defenders based on threat level
  10367. if (_enemyCount > 20) then {
  10368. _minDefenders = 6;
  10369. } else {
  10370. if (_enemyCount > 10) then {
  10371. _minDefenders = 4;
  10372. } else {
  10373. if (_enemyCount > 5) then {
  10374. _minDefenders = 3;
  10375. };
  10376. };
  10377. };
  10378.  
  10379. // Check time - more defenders at night
  10380. if (sunOrMoon < 0.3) then {
  10381. _minDefenders = _minDefenders + 1;
  10382. };
  10383.  
  10384. _minDefenders
  10385. };
  10386.  
  10387. // Check if base has adequate defenders
  10388. fnc_checkBaseDefense = {
  10389. params ["_side"];
  10390.  
  10391. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  10392. private _minDefenders = [_side] call fnc_calculateMinimumDefenders;
  10393.  
  10394. // Count current defenders
  10395. private _currentDefenders = 0;
  10396. {
  10397. if (side _x == _side && count (units _x) > 0) then {
  10398. private _role = _x getVariable ["HC_ROLE", ""];
  10399. if (_role == "DEFENDER" || _role == "BASE_DEFENSE") then {
  10400. _currentDefenders = _currentDefenders + 1;
  10401. } else {
  10402. // Also count groups very close to base as implicit defenders
  10403. private _leader = leader _x;
  10404. if (!isNull _leader && alive _leader && (_leader distance2D _ownBase) < 300) then {
  10405. _currentDefenders = _currentDefenders + 1;
  10406. };
  10407. };
  10408. };
  10409. } forEach allGroups;
  10410.  
  10411. private _defenseStatus = createHashMap;
  10412. _defenseStatus set ["currentDefenders", _currentDefenders];
  10413. _defenseStatus set ["minimumDefenders", _minDefenders];
  10414. _defenseStatus set ["needsReinforcement", _currentDefenders < _minDefenders];
  10415. _defenseStatus set ["defenderShortfall", (_minDefenders - _currentDefenders) max 0];
  10416.  
  10417. _defenseStatus
  10418. };
  10419.  
  10420. // Create defensive perimeter for recon groups
  10421. fnc_createReconPerimeter = {
  10422. params ["_reconGroup", "_centerPos", "_radius"];
  10423.  
  10424. // Create 4 waypoints in a diamond pattern around center
  10425. while {count (waypoints _reconGroup) > 0} do {
  10426. deleteWaypoint ((waypoints _reconGroup) select 0);
  10427. };
  10428.  
  10429. private _angles = [0, 90, 180, 270];
  10430.  
  10431. {
  10432. private _angle = _x;
  10433. private _perimeterPos = _centerPos getPos [_radius, _angle];
  10434.  
  10435. // Try to find cover at this position
  10436. private _coverPos = [_perimeterPos, 0, 30, 3, 0, 0.7, 0] call BIS_fnc_findSafePos;
  10437. if (count _coverPos == 0) then {
  10438. _coverPos = _perimeterPos;
  10439. };
  10440.  
  10441. private _wp = _reconGroup addWaypoint [_coverPos, 10];
  10442. _wp setWaypointType "MOVE";
  10443. _wp setWaypointBehaviour "STEALTH";
  10444. _wp setWaypointSpeed "LIMITED";
  10445. _wp setWaypointCombatMode "YELLOW";
  10446. _wp setWaypointCompletionRadius 15;
  10447.  
  10448. if (_forEachIndex == (count _angles - 1)) then {
  10449. _wp setWaypointStatements ["true", ""];
  10450. };
  10451. } forEach _angles;
  10452.  
  10453. // Add cycle waypoint to patrol the perimeter
  10454. if (count (waypoints _reconGroup) > 0) then {
  10455. private _cycleWp = _reconGroup addWaypoint [waypointPosition [_reconGroup, 0], 0];
  10456. _cycleWp setWaypointType "CYCLE";
  10457. };
  10458.  
  10459. _reconGroup setVariable ["HC_PERIMETER_CENTER", _centerPos, true];
  10460. _reconGroup setVariable ["HC_PERIMETER_RADIUS", _radius, true];
  10461. };
  10462.  
  10463. // Evaluate potential objectives from intel
  10464. fnc_identifyPotentialObjectives = {
  10465. params ["_side"];
  10466.  
  10467. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  10468. private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
  10469. private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
  10470.  
  10471. private _objectives = [];
  10472. private _enemyPositions = [];
  10473.  
  10474. // Collect recent enemy positions from intel
  10475. {
  10476. private _report = _y;
  10477. private _age = time - (_report get "time");
  10478. if (_age < 120) then {
  10479. _enemyPositions pushBack (_report get "position");
  10480. };
  10481. } forEach _intel;
  10482.  
  10483. // Calculate battle center from enemy positions
  10484. private _battleCenter = _enemyBase;
  10485. if (count _enemyPositions > 0) then {
  10486. private _avgX = 0;
  10487. private _avgY = 0;
  10488. {
  10489. _avgX = _avgX + (_x select 0);
  10490. _avgY = _avgY + (_x select 1);
  10491. } forEach _enemyPositions;
  10492. _battleCenter = [_avgX / count _enemyPositions, _avgY / count _enemyPositions, 0];
  10493. };
  10494.  
  10495. // PRIORITY 1: Base Defense (always consider this)
  10496. private _defenseStatus = [_side] call fnc_checkBaseDefense;
  10497. if (_defenseStatus get "needsReinforcement") then {
  10498. private _defendObjective = createHashMap;
  10499. _defendObjective set ["position", _ownBase];
  10500. _defendObjective set ["type", "BASE_DEFENSE"];
  10501. _defendObjective set ["enemyCount", 0];
  10502. _defendObjective set ["priority", 100]; // Highest priority
  10503. _defendObjective set ["requiredGroups", _defenseStatus get "defenderShortfall"];
  10504. _objectives pushBack _defendObjective;
  10505. };
  10506.  
  10507. // PRIORITY 2: CONTAINMENT - Surround enemy base to block their reinforcements
  10508. // This is the key strategic objective to win
  10509. private _heldPositions = if (_side == west) then {BLUFOR_HELD_POSITIONS} else {OPFOR_HELD_POSITIONS};
  10510. private _containmentPositions = [_side, _enemyBase] call fnc_findContainmentPositions;
  10511.  
  10512. {
  10513. private _containmentData = _x;
  10514. private _containmentPos = _containmentData get "position";
  10515.  
  10516. // Check if we already hold this containment position
  10517. private _alreadyHeld = false;
  10518. {
  10519. if ((_x distance2D _containmentPos) < 150) then {
  10520. _alreadyHeld = true;
  10521. };
  10522. } forEach _heldPositions;
  10523.  
  10524. if (!_alreadyHeld) then {
  10525. private _containmentObjective = createHashMap;
  10526. _containmentObjective set ["position", _containmentPos];
  10527. _containmentObjective set ["type", "CONTAINMENT"];
  10528. _containmentObjective set ["enemyCount", 3]; // Assume moderate resistance
  10529. _containmentObjective set ["priority", 85]; // Very high priority - key to victory
  10530. _containmentObjective set ["arcPosition", _containmentData get "arcPosition"];
  10531. _containmentObjective set ["requiresHolding", true]; // Must hold position
  10532. _objectives pushBack _containmentObjective;
  10533. };
  10534. } forEach _containmentPositions;
  10535.  
  10536. // PRIORITY 3: High Ground positions for snipers/elite
  10537. private _highGroundPositions = [_side, _battleCenter, 2000] call fnc_findHighGroundPositions;
  10538.  
  10539. {
  10540. private _hgData = _x;
  10541. private _hgPos = _hgData get "position";
  10542.  
  10543. // Check if we already hold this position
  10544. private _alreadyHeld = false;
  10545. {
  10546. if ((_x distance2D _hgPos) < 100) then {
  10547. _alreadyHeld = true;
  10548. };
  10549. } forEach _heldPositions;
  10550.  
  10551. if (!_alreadyHeld) then {
  10552. private _hgObjective = createHashMap;
  10553. _hgObjective set ["position", _hgPos];
  10554. _hgObjective set ["type", "HIGH_GROUND"];
  10555. _hgObjective set ["enemyCount", 2]; // Assume light resistance
  10556. _hgObjective set ["priority", 70];
  10557. _hgObjective set ["elevationAdvantage", _hgData get "elevationAdvantage"];
  10558. _hgObjective set ["requiresSpecialized", true]; // Needs sniper/elite
  10559. _objectives pushBack _hgObjective;
  10560. };
  10561. } forEach _highGroundPositions;
  10562.  
  10563. // PRIORITY 4: Enemy concentrations from intel
  10564. if (count _enemyPositions > 0) then {
  10565. private _clusters = [_enemyPositions, 200] call fnc_clusterPositions;
  10566.  
  10567. {
  10568. private _cluster = _x;
  10569. if (count _cluster >= 3) then {
  10570. // Calculate cluster center
  10571. private _avgX = 0;
  10572. private _avgY = 0;
  10573. {
  10574. _avgX = _avgX + (_x select 0);
  10575. _avgY = _avgY + (_x select 1);
  10576. } forEach _cluster;
  10577.  
  10578. private _centerPos = [
  10579. _avgX / count _cluster,
  10580. _avgY / count _cluster,
  10581. 0
  10582. ];
  10583.  
  10584. // Determine if this is behind enemy lines
  10585. private _distToEnemyBase = _centerPos distance2D _enemyBase;
  10586. private _distToOwnBase = _centerPos distance2D _ownBase;
  10587. private _isBehindEnemyLines = _distToEnemyBase < (_distToOwnBase * 0.5);
  10588.  
  10589. private _objectiveType = if (_isBehindEnemyLines) then {
  10590. "ENEMY_REAR"
  10591. } else {
  10592. "ENEMY_CONCENTRATION"
  10593. };
  10594.  
  10595. // Create objective for this enemy concentration
  10596. private _objective = createHashMap;
  10597. _objective set ["position", _centerPos];
  10598. _objective set ["type", _objectiveType];
  10599. _objective set ["enemyCount", count _cluster];
  10600. _objective set ["priority", if (_isBehindEnemyLines) then {60} else {50}];
  10601.  
  10602. if (_objectiveType == "ENEMY_REAR") then {
  10603. _objective set ["requiresReconPerimeter", true];
  10604. };
  10605.  
  10606. _objectives pushBack _objective;
  10607. };
  10608. } forEach _clusters;
  10609. };
  10610.  
  10611. // PRIORITY 5: Enemy base assault (final objective after containment)
  10612. private _baseObjective = createHashMap;
  10613. _baseObjective set ["position", _enemyBase];
  10614. _baseObjective set ["type", "ENEMY_BASE"];
  10615. _baseObjective set ["enemyCount", 15]; // Assume base is well defended
  10616. _baseObjective set ["priority", 40];
  10617. _objectives pushBack _baseObjective;
  10618.  
  10619. // PRIORITY 6: Defensive positions between own base and battle front
  10620. if (count _enemyPositions > 0) then {
  10621. private _dirToEnemy = _ownBase getDir _battleCenter;
  10622. private _distToFront = _ownBase distance2D _battleCenter;
  10623.  
  10624. // Create defensive line halfway to battle
  10625. private _defensivePos = _ownBase getPos [_distToFront * 0.4, _dirToEnemy];
  10626.  
  10627. private _defensiveObjective = createHashMap;
  10628. _defensiveObjective set ["position", _defensivePos];
  10629. _defensiveObjective set ["type", "DEFENSIVE_LINE"];
  10630. _defensiveObjective set ["enemyCount", 0];
  10631. _defensiveObjective set ["priority", 55];
  10632. _objectives pushBack _defensiveObjective;
  10633. };
  10634.  
  10635. _objectives
  10636. };
  10637.  
  10638. // Predict outcome of sending forces to an objective
  10639. fnc_predictBattleOutcome = {
  10640. params ["_side", "_objective", "_assignedGroups", ["_reinforcements", []]];
  10641.  
  10642. private _objectivePos = _objective get "position";
  10643. private _enemyCount = _objective get "enemyCount";
  10644. private _objectiveType = _objective get "type";
  10645.  
  10646. // Calculate friendly force strength including reinforcements
  10647. private _friendlyStrength = 0;
  10648. private _friendlyUnits = 0;
  10649. private _hasArmor = false;
  10650. private _hasAir = false;
  10651. private _hasSnipers = false;
  10652. private _hasElite = false;
  10653.  
  10654. private _allGroups = _assignedGroups + _reinforcements;
  10655.  
  10656. {
  10657. private _group = _x;
  10658. private _groupType = [_group] call fnc_classifyGroup;
  10659. private _unitCount = count (units _group select {alive _x});
  10660.  
  10661. _friendlyUnits = _friendlyUnits + _unitCount;
  10662.  
  10663. switch (_groupType) do {
  10664. case "ARMOR": {
  10665. _friendlyStrength = _friendlyStrength + (_unitCount * 4);
  10666. _hasArmor = true;
  10667. };
  10668. case "MECHANIZED": {
  10669. _friendlyStrength = _friendlyStrength + (_unitCount * 2);
  10670. };
  10671. case "AIR": {
  10672. _friendlyStrength = _friendlyStrength + (_unitCount * 3);
  10673. _hasAir = true;
  10674. };
  10675. case "ELITE": {
  10676. _friendlyStrength = _friendlyStrength + (_unitCount * 1.8);
  10677. _hasElite = true;
  10678. };
  10679. case "SPECOPS": {
  10680. _friendlyStrength = _friendlyStrength + (_unitCount * 1.5);
  10681. };
  10682. case "SNIPER": {
  10683. _friendlyStrength = _friendlyStrength + (_unitCount * 1.5);
  10684. _hasSnipers = true;
  10685. };
  10686. default {
  10687. _friendlyStrength = _friendlyStrength + _unitCount;
  10688. };
  10689. };
  10690. } forEach _allGroups;
  10691.  
  10692. // Estimate enemy strength at objective
  10693. private _enemyStrength = _enemyCount * 1.2;
  10694.  
  10695. // Adjust for objective type
  10696. switch (_objectiveType) do {
  10697. case "ENEMY_BASE": {
  10698. _enemyStrength = _enemyStrength * 1.5; // Base defenders have advantage
  10699. };
  10700. case "HIGH_GROUND": {
  10701. _enemyStrength = _enemyStrength * 0.8; // Usually lightly defended
  10702. // Bonus if we have snipers for high ground
  10703. if (_hasSnipers || _hasElite) then {
  10704. _friendlyStrength = _friendlyStrength * 1.2;
  10705. };
  10706. };
  10707. case "DEFENSIVE_LINE": {
  10708. _enemyStrength = 0; // Setting up defense, no immediate enemy
  10709. };
  10710. case "BASE_DEFENSE": {
  10711. _enemyStrength = _enemyCount * 1.3; // Attackers have initiative
  10712. };
  10713. case "ENEMY_REAR": {
  10714. _enemyStrength = _enemyStrength * 1.1; // Slightly harder (surrounded)
  10715. // SpecOps bonus for behind enemy lines
  10716. if (_hasElite) then {
  10717. _friendlyStrength = _friendlyStrength * 1.15;
  10718. };
  10719. };
  10720. };
  10721.  
  10722. // Calculate force ratio
  10723. private _forceRatio = if (_enemyStrength > 0) then {
  10724. _friendlyStrength / _enemyStrength
  10725. } else {
  10726. 999
  10727. };
  10728.  
  10729. // Predict success probability (0 to 1)
  10730. private _successProbability = 0;
  10731. if (_forceRatio >= 2.5) then {
  10732. _successProbability = 0.95;
  10733. } else {
  10734. if (_forceRatio >= 2.0) then {
  10735. _successProbability = 0.85;
  10736. } else {
  10737. if (_forceRatio >= 1.5) then {
  10738. _successProbability = 0.7;
  10739. } else {
  10740. if (_forceRatio >= 1.0) then {
  10741. _successProbability = 0.5;
  10742. } else {
  10743. if (_forceRatio >= 0.7) then {
  10744. _successProbability = 0.3;
  10745. } else {
  10746. _successProbability = 0.1;
  10747. };
  10748. };
  10749. };
  10750. };
  10751. };
  10752.  
  10753. // Predict expected casualties (as percentage of friendly force)
  10754. private _expectedCasualties = 0;
  10755. if (_forceRatio >= 2.5) then {
  10756. _expectedCasualties = 0.05;
  10757. } else {
  10758. if (_forceRatio >= 2.0) then {
  10759. _expectedCasualties = 0.1;
  10760. } else {
  10761. if (_forceRatio >= 1.5) then {
  10762. _expectedCasualties = 0.2;
  10763. } else {
  10764. if (_forceRatio >= 1.0) then {
  10765. _expectedCasualties = 0.35;
  10766. } else {
  10767. if (_forceRatio >= 0.7) then {
  10768. _expectedCasualties = 0.5;
  10769. } else {
  10770. _expectedCasualties = 0.7;
  10771. };
  10772. };
  10773. };
  10774. };
  10775. };
  10776.  
  10777. // Adjust for tactical advantages
  10778. if (_hasArmor && !(_objectiveType in ["ENEMY_BASE", "HIGH_GROUND"])) then {
  10779. _successProbability = _successProbability + 0.1;
  10780. _expectedCasualties = _expectedCasualties * 0.8;
  10781. };
  10782.  
  10783. if (_hasAir) then {
  10784. _successProbability = _successProbability + 0.05;
  10785. _expectedCasualties = _expectedCasualties * 0.9;
  10786. };
  10787.  
  10788. // Reinforcement bonus
  10789. if (count _reinforcements > 0) then {
  10790. _successProbability = _successProbability + 0.1;
  10791. _expectedCasualties = _expectedCasualties * 0.85;
  10792. };
  10793.  
  10794. // Clamp values
  10795. _successProbability = (_successProbability min 0.95) max 0.05;
  10796. _expectedCasualties = (_expectedCasualties min 0.9) max 0.02;
  10797.  
  10798. // Return prediction
  10799. private _prediction = createHashMap;
  10800. _prediction set ["successProbability", _successProbability];
  10801. _prediction set ["expectedCasualties", _expectedCasualties];
  10802. _prediction set ["expectedCasualtyCount", round (_friendlyUnits * _expectedCasualties)];
  10803. _prediction set ["forceRatio", _forceRatio];
  10804. _prediction set ["friendlyStrength", _friendlyStrength];
  10805. _prediction set ["enemyStrength", _enemyStrength];
  10806. _prediction set ["hasReinforcements", count _reinforcements > 0];
  10807. _prediction set ["reinforcementCount", count _reinforcements];
  10808.  
  10809. _prediction
  10810. };
  10811.  
  10812. // Evaluate risk vs reward for an objective
  10813. fnc_evaluateObjectiveValue = {
  10814. params ["_side", "_objective", "_prediction"];
  10815.  
  10816. private _objectiveType = _objective get "type";
  10817. private _successProb = _prediction get "successProbability";
  10818. private _expectedCasualties = _prediction get "expectedCasualties";
  10819. private _hasReinforcements = _prediction get "hasReinforcements";
  10820.  
  10821. // Base value by objective type
  10822. private _baseValue = 0;
  10823. switch (_objectiveType) do {
  10824. case "ENEMY_BASE": { _baseValue = 100; };
  10825. case "BASE_DEFENSE": { _baseValue = 95; }; // Critical
  10826. case "HIGH_GROUND": { _baseValue = 70; }; // Very valuable
  10827. case "DEFENSIVE_LINE": { _baseValue = 60; };
  10828. case "ENEMY_REAR": { _baseValue = 55; }; // Disruptive
  10829. case "ENEMY_CONCENTRATION": { _baseValue = 50; };
  10830. default { _baseValue = 30; };
  10831. };
  10832.  
  10833. // Calculate expected value (probability of success * base value)
  10834. private _expectedValue = _baseValue * _successProb;
  10835.  
  10836. // Calculate risk cost (casualties are bad)
  10837. private _riskCost = 50 * _expectedCasualties;
  10838.  
  10839. // Net value = expected benefit - risk cost
  10840. private _netValue = _expectedValue - _riskCost;
  10841.  
  10842. // Urgency modifier
  10843. private _urgency = 1.0;
  10844. switch (_objectiveType) do {
  10845. case "BASE_DEFENSE": {
  10846. // Critical urgency for base defense
  10847. private _enemiesNearBase = [_side, 800] call fnc_enemiesNearBase;
  10848. if (_enemiesNearBase) then {
  10849. _urgency = 5.0; // Extreme priority
  10850. } else {
  10851. _urgency = 3.0; // Still important
  10852. };
  10853. };
  10854. case "HIGH_GROUND": {
  10855. // Good to have, not critical
  10856. _urgency = 1.2;
  10857. };
  10858. case "DEFENSIVE_LINE": {
  10859. // More urgent if enemies are pushing
  10860. private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
  10861. if (count _intel > 10) then {
  10862. _urgency = 1.5;
  10863. } else {
  10864. _urgency = 1.0;
  10865. };
  10866. };
  10867. case "ENEMY_REAR": {
  10868. // Opportunistic
  10869. _urgency = 1.3;
  10870. };
  10871. };
  10872.  
  10873. // Reinforcement availability bonus
  10874. if (_hasReinforcements) then {
  10875. _netValue = _netValue * 1.15;
  10876. };
  10877.  
  10878. private _finalScore = _netValue * _urgency;
  10879.  
  10880. // Return evaluation
  10881. private _evaluation = createHashMap;
  10882. _evaluation set ["score", _finalScore];
  10883. _evaluation set ["baseValue", _baseValue];
  10884. _evaluation set ["expectedValue", _expectedValue];
  10885. _evaluation set ["riskCost", _riskCost];
  10886. _evaluation set ["urgency", _urgency];
  10887.  
  10888. _evaluation
  10889. };
  10890.  
  10891. // Select best groups for an objective type
  10892. fnc_selectGroupsForObjective = {
  10893. params ["_objective", "_availableGroups", "_count"];
  10894.  
  10895. private _objectiveType = _objective get "type";
  10896. private _objectivePos = _objective get "position";
  10897. private _selectedGroups = [];
  10898.  
  10899. // For specialized objectives, filter for appropriate group types
  10900. switch (_objectiveType) do {
  10901. case "CONTAINMENT": {
  10902. // Prefer infantry and mechanized for holding positions
  10903. // Avoid using all elite/specops for containment - save them for other tasks
  10904. private _holdingGroups = _availableGroups select {
  10905. private _type = [_x] call fnc_classifyGroup;
  10906. _type in ["INFANTRY", "MECHANIZED", "UPGRADED", "SPECOPS"]
  10907. };
  10908.  
  10909. if (count _holdingGroups > 0) then {
  10910. // Sort by distance to position
  10911. _holdingGroups = [_holdingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10912. _selectedGroups = _holdingGroups select [0, _count min count _holdingGroups];
  10913. };
  10914.  
  10915. // Fill remaining slots if needed
  10916. if (count _selectedGroups < _count) then {
  10917. private _remainingGroups = _availableGroups - _selectedGroups;
  10918. private _needed = _count - count _selectedGroups;
  10919. _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10920. _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
  10921. };
  10922. };
  10923.  
  10924. case "HIGH_GROUND": {
  10925. // Prefer snipers and elite for high ground
  10926. private _specializedGroups = _availableGroups select {
  10927. private _type = [_x] call fnc_classifyGroup;
  10928. _type in ["SNIPER", "ELITE", "SPECOPS"]
  10929. };
  10930.  
  10931. if (count _specializedGroups > 0) then {
  10932. // Sort by distance to position
  10933. _specializedGroups = [_specializedGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10934. _selectedGroups = _specializedGroups select [0, _count min count _specializedGroups];
  10935. };
  10936.  
  10937. // Fill remaining slots with regular infantry if needed
  10938. if (count _selectedGroups < _count) then {
  10939. private _remainingGroups = _availableGroups - _selectedGroups;
  10940. private _needed = _count - count _selectedGroups;
  10941. _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10942. _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
  10943. };
  10944. };
  10945.  
  10946. case "ENEMY_REAR": {
  10947. // Prefer elite/specops for behind enemy lines
  10948. private _stealthGroups = _availableGroups select {
  10949. private _type = [_x] call fnc_classifyGroup;
  10950. _type in ["ELITE", "SPECOPS", "SNIPER"]
  10951. };
  10952.  
  10953. if (count _stealthGroups > 0) then {
  10954. _stealthGroups = [_stealthGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10955. _selectedGroups = _stealthGroups select [0, _count min count _stealthGroups];
  10956. };
  10957.  
  10958. if (count _selectedGroups < _count) then {
  10959. private _remainingGroups = _availableGroups - _selectedGroups;
  10960. private _needed = _count - count _selectedGroups;
  10961. _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10962. _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
  10963. };
  10964. };
  10965.  
  10966. case "BASE_DEFENSE": {
  10967. // Prefer groups already close to base
  10968. private _sortedGroups = [_availableGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10969. _selectedGroups = _sortedGroups select [0, _count min count _sortedGroups];
  10970. };
  10971.  
  10972. case "ENEMY_BASE": {
  10973. // Prefer armor and mechanized for base assault
  10974. private _heavyGroups = _availableGroups select {
  10975. private _type = [_x] call fnc_classifyGroup;
  10976. _type in ["ARMOR", "MECHANIZED", "AIR"]
  10977. };
  10978.  
  10979. if (count _heavyGroups > 0) then {
  10980. _selectedGroups = _heavyGroups select [0, (_count / 2) min count _heavyGroups];
  10981. };
  10982.  
  10983. if (count _selectedGroups < _count) then {
  10984. private _remainingGroups = _availableGroups - _selectedGroups;
  10985. private _needed = _count - count _selectedGroups;
  10986. _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10987. _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
  10988. };
  10989. };
  10990.  
  10991. default {
  10992. // Default: pick closest groups
  10993. private _sortedGroups = [_availableGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
  10994. _selectedGroups = _sortedGroups select [0, _count min count _sortedGroups];
  10995. };
  10996. };
  10997.  
  10998. _selectedGroups
  10999. };
  11000.  
  11001. // Create strategic plan by evaluating all possible objectives
  11002. fnc_createStrategicPlan = {
  11003. params ["_side", "_availableGroups"];
  11004.  
  11005. if (count _availableGroups == 0) exitWith { createHashMap };
  11006.  
  11007. private _objectives = [_side] call fnc_identifyPotentialObjectives;
  11008. private _scoredObjectives = [];
  11009.  
  11010. // Evaluate each objective
  11011. {
  11012. private _objective = _x;
  11013. private _objectivePos = _objective get "position";
  11014. private _objectiveType = _objective get "type";
  11015.  
  11016. // Handle fixed-requirement objectives (like base defense)
  11017. if (_objectiveType == "BASE_DEFENSE") then {
  11018. private _requiredGroups = _objective get "requiredGroups";
  11019. private _selectedGroups = [_objective, _availableGroups, _requiredGroups] call fnc_selectGroupsForObjective;
  11020.  
  11021. if (count _selectedGroups > 0) then {
  11022. private _prediction = [_side, _objective, _selectedGroups] call fnc_predictBattleOutcome;
  11023. private _evaluation = [_side, _objective, _prediction] call fnc_evaluateObjectiveValue;
  11024.  
  11025. private _scoredObjective = createHashMap;
  11026. _scoredObjective set ["objective", _objective];
  11027. _scoredObjective set ["score", _evaluation get "score"];
  11028. _scoredObjective set ["recommendedGroupCount", count _selectedGroups];
  11029. _scoredObjective set ["selectedGroups", _selectedGroups];
  11030. _scoredObjective set ["prediction", _prediction];
  11031. _scoredObjective set ["evaluation", _evaluation];
  11032.  
  11033. _scoredObjectives pushBack _scoredObjective;
  11034. };
  11035. } else {
  11036. // For other objectives, test different force allocations
  11037. private _minForce = 1;
  11038. private _maxForce = (count _availableGroups) min 8; // Don't commit everything
  11039.  
  11040. // For high ground, only need 1-2 groups
  11041. if (_objectiveType == "HIGH_GROUND") then {
  11042. _maxForce = 2;
  11043. };
  11044.  
  11045. private _bestScore = -999999;
  11046. private _bestAllocation = _minForce;
  11047. private _bestPrediction = createHashMap;
  11048. private _bestEvaluation = createHashMap;
  11049. private _bestGroups = [];
  11050.  
  11051. for "_groupCount" from _minForce to _maxForce step 1 do {
  11052. // Select appropriate groups for this objective
  11053. private _testGroups = [_objective, _availableGroups, _groupCount] call fnc_selectGroupsForObjective;
  11054.  
  11055. if (count _testGroups > 0) then {
  11056. // Check for potential reinforcements
  11057. private _reinforcements = [_side, _objectivePos, _testGroups, 1500] call fnc_findNearbyReinforcements;
  11058. private _reinforcementGroups = [];
  11059.  
  11060. // Take up to 2 nearest reinforcement groups
  11061. if (count _reinforcements > 0) then {
  11062. for "_i" from 0 to ((2 min count _reinforcements) - 1) do {
  11063. _reinforcementGroups pushBack ((_reinforcements select _i) get "group");
  11064. };
  11065. };
  11066.  
  11067. // Predict outcome with reinforcements
  11068. private _prediction = [_side, _objective, _testGroups, _reinforcementGroups] call fnc_predictBattleOutcome;
  11069.  
  11070. // Evaluate value
  11071. private _evaluation = [_side, _objective, _prediction] call fnc_evaluateObjectiveValue;
  11072.  
  11073. private _score = _evaluation get "score";
  11074.  
  11075. // Efficiency penalty for using too many groups
  11076. private _efficiencyPenalty = (_groupCount / count _availableGroups) * 8;
  11077. _score = _score - _efficiencyPenalty;
  11078.  
  11079. if (_score > _bestScore) then {
  11080. _bestScore = _score;
  11081. _bestAllocation = _groupCount;
  11082. _bestPrediction = _prediction;
  11083. _bestEvaluation = _evaluation;
  11084. _bestGroups = _testGroups;
  11085. };
  11086. };
  11087. };
  11088.  
  11089. // Store objective with its best score and allocation
  11090. if (count _bestGroups > 0) then {
  11091. private _scoredObjective = createHashMap;
  11092. _scoredObjective set ["objective", _objective];
  11093. _scoredObjective set ["score", _bestScore];
  11094. _scoredObjective set ["recommendedGroupCount", _bestAllocation];
  11095. _scoredObjective set ["selectedGroups", _bestGroups];
  11096. _scoredObjective set ["prediction", _bestPrediction];
  11097. _scoredObjective set ["evaluation", _bestEvaluation];
  11098.  
  11099. _scoredObjectives pushBack _scoredObjective;
  11100. };
  11101. };
  11102. } forEach _objectives;
  11103.  
  11104. // Sort objectives by score (highest first)
  11105. _scoredObjectives = [_scoredObjectives, [], {_x get "score"}, "DESCEND"] call BIS_fnc_sortBy;
  11106.  
  11107. // Create final strategic plan
  11108. private _strategicPlan = createHashMap;
  11109. _strategicPlan set ["objectives", _scoredObjectives];
  11110. _strategicPlan set ["timestamp", time];
  11111.  
  11112. _strategicPlan
  11113. };
  11114.  
  11115. // Get strategic recommendation for group assignment
  11116. fnc_getStrategicRecommendation = {
  11117. params ["_side", "_availableGroups"];
  11118.  
  11119. if (count _availableGroups == 0) exitWith { createHashMap };
  11120.  
  11121. // Create or update strategic plan
  11122. private _strategicPlan = [_side, _availableGroups] call fnc_createStrategicPlan;
  11123.  
  11124. // Store plan for this side
  11125. if (_side == west) then {
  11126. BLUFOR_STRATEGIC_PLAN = _strategicPlan;
  11127. } else {
  11128. OPFOR_STRATEGIC_PLAN = _strategicPlan;
  11129. };
  11130.  
  11131. private _objectives = _strategicPlan get "objectives";
  11132.  
  11133. if (count _objectives == 0) exitWith { createHashMap };
  11134.  
  11135. // Get top priority objective
  11136. private _topObjective = _objectives select 0;
  11137. private _score = _topObjective get "score";
  11138.  
  11139. // Only recommend if score is positive
  11140. if (_score <= 0) exitWith { createHashMap };
  11141.  
  11142. private _objective = _topObjective get "objective";
  11143. private _objectiveType = _objective get "type";
  11144.  
  11145. // Special handling for high ground - track held positions
  11146. if (_objectiveType == "HIGH_GROUND") then {
  11147. private _heldPositions = if (_side == west) then {BLUFOR_HELD_POSITIONS} else {OPFOR_HELD_POSITIONS};
  11148. _heldPositions pushBackUnique (_objective get "position");
  11149.  
  11150. if (_side == west) then {
  11151. BLUFOR_HELD_POSITIONS = _heldPositions;
  11152. } else {
  11153. OPFOR_HELD_POSITIONS = _heldPositions;
  11154. };
  11155. };
  11156.  
  11157. private _recommendation = createHashMap;
  11158. _recommendation set ["objective", _objective];
  11159. _recommendation set ["groupCount", _topObjective get "recommendedGroupCount"];
  11160. _recommendation set ["selectedGroups", _topObjective get "selectedGroups"];
  11161. _recommendation set ["prediction", _topObjective get "prediction"];
  11162. _recommendation set ["evaluation", _topObjective get "evaluation"];
  11163. _recommendation set ["score", _score];
  11164.  
  11165. _recommendation
  11166. };
  11167.  
  11168. // Apply special tactics for objective types
  11169. fnc_applySpecialTactics = {
  11170. params ["_group", "_objective"];
  11171.  
  11172. private _objectiveType = _objective get "type";
  11173. private _objectivePos = _objective get "position";
  11174.  
  11175. switch (_objectiveType) do {
  11176. case "CONTAINMENT": {
  11177. // Lock group to containment position - critical for victory
  11178. _group setVariable ["HC_FORCED_LOCK", true, true];
  11179. _group setVariable ["HC_ROLE", "CONTAINMENT", true];
  11180. _group setVariable ["HC_HOLD_POSITION", true, true];
  11181.  
  11182. // Create defensive waypoints to hold the containment position
  11183. [_group, _objectivePos] call fnc_createDefenseWaypoints;
  11184.  
  11185. // Track this position as held by this side
  11186. private _side = side _group;
  11187. private _heldPositions = if (_side == west) then {BLUFOR_HELD_POSITIONS} else {OPFOR_HELD_POSITIONS};
  11188. _heldPositions pushBackUnique _objectivePos;
  11189.  
  11190. if (_side == west) then {
  11191. BLUFOR_HELD_POSITIONS = _heldPositions;
  11192. } else {
  11193. OPFOR_HELD_POSITIONS = _heldPositions;
  11194. };
  11195. };
  11196.  
  11197. case "HIGH_GROUND": {
  11198. // Set group to hold position on high ground
  11199. _group setVariable ["HC_HOLD_POSITION", true, true];
  11200. _group setVariable ["HC_ROLE", "HIGH_GROUND", true];
  11201.  
  11202. // Create defensive waypoints around the high ground
  11203. [_group, _objectivePos] call fnc_createDefenseWaypoints;
  11204. };
  11205.  
  11206. case "ENEMY_REAR": {
  11207. // Set up defensive perimeter for recon
  11208. _group setVariable ["HC_ROLE", "DEEP_RECON", true];
  11209. [_group, _objectivePos, 150] call fnc_createReconPerimeter;
  11210. };
  11211.  
  11212. case "BASE_DEFENSE": {
  11213. // Lock group to base defense
  11214. _group setVariable ["HC_FORCED_LOCK", true, true];
  11215. _group setVariable ["HC_ROLE", "BASE_DEFENSE", true];
  11216. [_group, _objectivePos] call fnc_createDefenseWaypoints;
  11217. };
  11218.  
  11219. case "DEFENSIVE_LINE": {
  11220. // Create defensive positions
  11221. _group setVariable ["HC_ROLE", "DEFENSIVE_LINE", true];
  11222. [_group, _objectivePos] call fnc_createDefenseWaypoints;
  11223. };
  11224. };
  11225. };
  11226.  
  11227. ==================== END OF: strategy.sqf ====================
  11228.  
  11229.  
Add Comment
Please, Sign In to add comment