Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ==================== START OF: AAF.sqf ====================
- // REWORKED: Handles dynamic AAF patrol spawning.
- // Also manages initial AAF garrisons at strategic locations.
- // AAF squads are now limited to only bandit compositions.
- AAF_SQUAD_COMPOSITIONS = [
- // Bandit squads
- ["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"],
- ["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"],
- ["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"]
- ];
- // New function to make AAF groups strictly defend the strongpoint
- fnc_setAAFDefend = {
- params ["_group", "_centerPos"];
- // Clear any existing waypoints
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Defense settings: Keep units strictly within the strongpoint area
- private _defendRadius = 120;
- // Set tactical mode for defense
- _group setBehaviour "COMBAT"; // Stay alert, use cover
- _group setCombatMode "RED"; // Engage enemies freely
- _group setSpeedMode "LIMITED"; // Walk/Crouch only, prevents running out of the zone
- // Create a defensive patrol pattern within the strongpoint
- for "_i" from 1 to 4 do {
- // Find a valid position within the tight defense radius
- private _defendPos = [_centerPos, 0, _defendRadius, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _defendPos == 0) then { _defendPos = _centerPos; };
- private _wp = _group addWaypoint [_defendPos, 0];
- _wp setWaypointType "SAD"; // Search and Destroy allows engaging nearby threats
- _wp setWaypointCompletionRadius 20;
- // Important: Make them wait at each point to simulate guarding/holding ground
- _wp setWaypointTimeout [20, 40, 60];
- };
- // Cycle back to the first waypoint to maintain the defense indefinitely
- private _cycleWp = _group addWaypoint [waypointPosition [_group, 1], 0];
- _cycleWp setWaypointType "CYCLE";
- };
- // This function now specifically handles the INITIAL AAF patrol at mission start.
- fnc_spawnAAFForces = {
- // IMPROVED: Wait for mission_centralPoint to exist AND be valid (not default fallback)
- waitUntil {
- !isNil "mission_centralPoint" &&
- {
- private _pos = missionNamespace getVariable ["mission_centralPoint", [0,0,0]];
- !(_pos isEqualTo [0,0,0]) &&
- !(_pos isEqualTo [worldSize/2, worldSize/2, 0]) &&
- (_pos select 0) > 0 &&
- (_pos select 1) > 0
- }
- };
- // IMPROVED: Additional delay to ensure base objects are fully initialized
- sleep 3; // Increased from 2 to 3 seconds
- private _aafTargetSize = AAF_START_SIZE;
- if (_aafTargetSize <= 0) exitWith {};
- private _centerPos = missionNamespace getVariable ["mission_centralPoint", [worldSize/2, worldSize/2, 0]];
- // IMPROVED: Comprehensive position validation with multiple checks
- private _positionValid = true;
- // Check 1: Not default fallback positions
- if (_centerPos isEqualTo [worldSize/2, worldSize/2, 0] || _centerPos isEqualTo [0,0,0]) then {
- _positionValid = false;
- diag_log format ["ERROR: AAF spawn position is default fallback: %1", _centerPos];
- };
- // Check 2: Coordinates must be positive and within world bounds
- if ((_centerPos select 0) <= 0 || (_centerPos select 1) <= 0) then {
- _positionValid = false;
- diag_log format ["ERROR: AAF spawn position has invalid coordinates: %1", _centerPos];
- };
- if ((_centerPos select 0) >= worldSize || (_centerPos select 1) >= worldSize) then {
- _positionValid = false;
- diag_log format ["ERROR: AAF spawn position exceeds world bounds: %1", _centerPos];
- };
- // Check 3: Position must not be in water
- if (surfaceIsWater _centerPos) then {
- _positionValid = false;
- diag_log format ["ERROR: AAF spawn position is in water: %1", _centerPos];
- };
- // IMPROVED: If position validation fails completely, abort and log
- if (!_positionValid) exitWith {
- diag_log format ["CRITICAL ERROR: AAF spawn aborted due to invalid mission_centralPoint: %1", _centerPos];
- systemChat "ERROR: AAF forces could not spawn - invalid stronghold position detected";
- };
- // IMPROVED: Log the validated position being used
- diag_log format ["AAF spawning at validated mission_centralPoint: %1", _centerPos];
- // Force Z to ground level for spawning (existing fix)
- _centerPos set [2, 0];
- private _allPatrolGroups = [];
- private _spawnedCount = 0;
- // Create a marker for the AAF Stronghold
- private _marker = createMarker ["aaf_stronghold_marker", _centerPos];
- _marker setMarkerType "mil_flag";
- _marker setMarkerColor "ColorGreen";
- _marker setMarkerText "AAF Stronghold";
- while {_spawnedCount < _aafTargetSize} do {
- private _group = createGroup independent;
- private _squadComp = selectRandom AAF_SQUAD_COMPOSITIONS;
- if ((_spawnedCount + count _squadComp) > _aafTargetSize) then {
- _squadComp = _squadComp select [0, _aafTargetSize - _spawnedCount];
- };
- if (count _squadComp == 0) then { break; };
- for "_i" from 0 to (count _squadComp - 1) do {
- private _unitType = _squadComp select _i;
- // Tighter spawn radius to keep units closer to the stronghold center.
- private _spawnPos = [_centerPos, 10, 75, 15, 0, 0.3, 0] call BIS_fnc_findSafePos;
- // NEW: Robust sanity check and multi-stage fallback for spawn position.
- // This prevents units spawning far away if findSafePos fails or returns an invalid location.
- if (count _spawnPos == 0 || _spawnPos isEqualTo [0,0,0] || _spawnPos distance2D _centerPos > 200) then {
- // STAGE 1 FALLBACK: Try again with a very tight radius.
- _spawnPos = [_centerPos, 0, 20, 5, 0, 0, 0] call BIS_fnc_findSafePos;
- diag_log format ["WARNING: findSafePos failed for AAF unit. Attempting fallback stage 1..."];
- // STAGE 2 FALLBACK: If the tight search also fails, use a simple random offset as a last resort.
- if (count _spawnPos == 0 || _spawnPos isEqualTo [0,0,0]) then {
- _spawnPos = [
- (_centerPos select 0) + (random 20 - 10),
- (_centerPos select 1) + (random 20 - 10),
- 0
- ];
- diag_log format ["WARNING: Fallback stage 1 failed. Using final offset position: %1", _spawnPos];
- };
- };
- private _unit = _group createUnit [_unitType, _spawnPos, [], 10, "NONE"];
- _unit setVariable ["isAAF", true, true];
- [_unit, 0.05, "REGULAR"] call fnc_enhanceIndividualAI;
- _spawnedCount = _spawnedCount + 1;
- };
- // Assign the new defensive logic to hold the stronghold
- [_group, _centerPos] call fnc_setAAFDefend;
- _allPatrolGroups pushBack _group;
- };
- independent setFriend [west, 0];
- independent setFriend [east, 0];
- west setFriend [independent, 0];
- east setFriend [independent, 0];
- missionNamespace setVariable ["AAF_Groups", _allPatrolGroups, true];
- // IMPROVED: Final confirmation log with unit count verification
- diag_log format ["AAF spawn complete: %1 units spawned at %2 in %3 groups", _spawnedCount, _centerPos, count _allPatrolGroups];
- // IMPROVED: Verify all groups have units
- {
- private _groupUnits = units _x select {alive _x};
- if (count _groupUnits == 0) then {
- diag_log format ["WARNING: AAF group %1 is empty after spawn", groupId _x];
- };
- } forEach _allPatrolGroups;
- };
- if (isServer) then {
- [] spawn {
- // FIXED: Wait for mission setup to complete first, then wait for player teams
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj" && !isNil "mission_centralPoint"};
- waitUntil {({side _x in [west, east]} count allPlayers > 0)};
- sleep 5;
- [] call fnc_spawnAAFForces;
- };
- };
- // ==================== CHANGE START ====================
- // NEW: Standalone server-side loop to apply foliage/vision checks to AAF units.
- // This gives them smarter engagement logic without adding all the complex behaviors from botai.sqf.
- if (isServer) then {
- [] spawn {
- // Wait for AAF forces to be initialized
- waitUntil {!isNil "AAF_Groups"};
- while {true} do {
- // Get all alive AAF units on the map
- private _allAAFUnits = allUnits select {alive _x && (_x getVariable ["isAAF", false])};
- {
- private _unit = _x;
- // AAF are hostile to both BLUFOR and OPFOR
- private _enemySides = [west, east];
- // Check for nearby targets
- private _nearTargets = _unit nearTargets 300; // Check targets within 300m
- {
- private _targetInfo = _x;
- private _target = _targetInfo select 4;
- // Check if the target is a valid enemy
- if (alive _target && (side _target in _enemySides)) then {
- // Use the global function from botai.sqf to check for obstruction
- private _obstructionLevel = [_unit, _target] call fnc_checkFoliageObstruction;
- // Apply obstruction-based reveal and fire control
- if (_obstructionLevel > 0.6) then {
- // Heavy obstruction - hold fire completely
- (group _unit) reveal [_target, 1.0];
- (group _unit) setVariable ["AICanFire", false];
- } else {
- if (_obstructionLevel > 0.3) then {
- // Moderate obstruction - limited engagement
- (group _unit) reveal [_target, 2.5];
- (group _unit) setVariable ["AICanFire", false];
- } else {
- // Clear shot - full engagement
- (group _unit) reveal [_target, 4];
- (group _unit) setVariable ["AICanFire", true];
- };
- };
- };
- } forEach _nearTargets;
- } forEach _allAAFUnits;
- // Check every 3-5 seconds to balance performance and responsiveness
- sleep (3 + random 2);
- };
- };
- };
- // ===================== CHANGE END =====================
- ==================== END OF: AAF.sqf ====================
- ==================== START OF: AIstuff.sqf ====================
- // AIstuff.sqf
- // AI Unit Pools
- BLUFOR_INFANTRY_POOL = [
- ["B_Soldier_TL_F", "B_Soldier_F", "B_Soldier_LAT_F", "B_Soldier_AA_F", "B_medic_F", "B_Soldier_AR_F"],
- ["B_Soldier_SL_F", "B_Soldier_F", "B_Soldier_AT_F", "B_Soldier_AA_F", "B_medic_F", "B_engineer_F"],
- ["B_Soldier_TL_F", "B_Soldier_F", "B_Soldier_LAT_F", "B_Soldier_AA_F", "B_HeavyGunner_F", "B_Soldier_A_F"],
- ["B_Soldier_TL_F", "B_Soldier_F", "B_Soldier_AT_F", "B_Soldier_AA_F", "B_Soldier_AR_F", "B_Soldier_A_F"],
- ["B_Soldier_SL_F", "B_Soldier_LAT_F", "B_Soldier_AA_F", "B_medic_F", "B_Soldier_AR_F", "B_engineer_F"]
- ];
- OPFOR_INFANTRY_POOL = [
- ["O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_LAT_F", "O_Soldier_AA_F", "O_medic_F", "O_Soldier_AR_F"],
- ["O_Soldier_SL_F", "O_Soldier_F", "O_Soldier_AT_F", "O_Soldier_AA_F", "O_medic_F", "O_engineer_F"],
- ["O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_LAT_F", "O_Soldier_AA_F", "O_HeavyGunner_F", "O_Soldier_A_F"],
- ["O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_AT_F", "O_Soldier_AA_F", "O_Soldier_AR_F", "O_Soldier_A_F"],
- ["O_Soldier_SL_F", "O_Soldier_LAT_F", "O_Soldier_AA_F", "O_medic_F", "O_Soldier_AR_F", "O_engineer_F"]
- ];
- BLUFOR_SPECOPS_POOL = [
- ["B_recon_TL_F", "B_recon_medic_F", "B_recon_LAT_F", "B_recon_exp_F", "B_recon_M_F", "B_recon_F"],
- ["B_recon_TL_F", "B_recon_medic_F", "B_recon_LAT_F", "B_recon_JTAC_F", "B_recon_F", "B_recon_F"]
- ];
- OPFOR_SPECOPS_POOL = [
- ["O_recon_TL_F", "O_recon_medic_F", "O_recon_LAT_F", "O_recon_exp_F", "O_recon_M_F", "O_recon_F"],
- ["O_recon_TL_F", "O_recon_medic_F", "O_recon_LAT_F", "O_recon_JTAC_F", "O_recon_F", "O_recon_F"]
- ];
- BLUFOR_ELITE_POOL = [
- ["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"]
- ];
- OPFOR_ELITE_POOL = [
- ["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"]
- ];
- BLUFOR_UPGRADED_POOL = [
- ["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"],
- ["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"]
- ];
- OPFOR_UPGRADED_POOL = [
- ["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"],
- ["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"]
- ];
- // Vehicle Pools - Light Vehicles (APCs)
- BLUFOR_LIGHT_VEHICLE_POOL = [
- "B_APC_Wheeled_01_cannon_F" // AMV-7 Marshall
- ];
- OPFOR_LIGHT_VEHICLE_POOL = [
- "O_APC_Wheeled_02_rcws_v2_F" // MSE-3 Marid
- ];
- // Vehicle Pools - Main Battle Tanks
- BLUFOR_TANK_POOL = [
- "B_MBT_01_TUSK_F" // M2A4 Slammer UP
- ];
- OPFOR_TANK_POOL = [
- "O_MBT_02_cannon_F" // T-100 Varsuk
- ];
- // Vehicle Pools - Attack Helicopters
- BLUFOR_ATTACK_HELI_POOL = [
- "B_Heli_Attack_01_F" // AH-99 Blackfoot
- ];
- OPFOR_ATTACK_HELI_POOL = [
- "O_Heli_Attack_02_F" // Mi-48 Kajman
- ];
- // Vehicle tracking (runtime state variables)
- if (isNil "BLUFOR_ACTIVE_LIGHT_VEHICLES") then { BLUFOR_ACTIVE_LIGHT_VEHICLES = []; };
- if (isNil "OPFOR_ACTIVE_LIGHT_VEHICLES") then { OPFOR_ACTIVE_LIGHT_VEHICLES = []; };
- if (isNil "BLUFOR_ACTIVE_TANKS") then { BLUFOR_ACTIVE_TANKS = []; };
- if (isNil "OPFOR_ACTIVE_TANKS") then { OPFOR_ACTIVE_TANKS = []; };
- if (isNil "BLUFOR_ACTIVE_ATTACK_HELIS") then { BLUFOR_ACTIVE_ATTACK_HELIS = []; };
- if (isNil "OPFOR_ACTIVE_ATTACK_HELIS") then { OPFOR_ACTIVE_ATTACK_HELIS = []; };
- // ==================== CHANGE START ====================
- // NEW: Finds a safe spawn position in a 600m radius around the base, avoiding enemies.
- fnc_getSafeSpawnPosition = {
- params ["_side"];
- private _baseObj = if (_side == west) then { bluforSpawnObj } else { opforSpawnObj };
- if (isNil "_baseObj") exitWith {[0,0,0]}; // Should not happen
- private _basePos = getPos _baseObj;
- private _enemySide = if (_side == west) then { east } else { west };
- private _spawnRadius = 500;
- private _enemyBuffer = 50; // Minimum distance from an enemy
- private _finalPos = [];
- // Try up to 20 times to find a suitable position
- for "_i" from 1 to 20 do {
- // Find a random, safe position within the base radius
- private _potentialPos = [_basePos, 10, _spawnRadius, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _potentialPos > 0) then {
- // Check for nearby enemies at the potential spawn point
- private _isNearEnemy = false;
- _nearby = _potentialPos nearEntities ["AllVehicles", _enemyBuffer];
- // Use findIf for efficiency; it stops once an enemy is found
- if ((_nearby findIf {side _x == _enemySide && alive _x}) != -1) then {
- _isNearEnemy = true;
- };
- // If no enemies are nearby, this position is valid
- if (!_isNearEnemy) then {
- _finalPos = _potentialPos;
- break; // Exit the loop
- };
- };
- };
- // If a valid position was found, return it. Otherwise, fallback to the base center.
- if (count _finalPos > 0) then {
- _finalPos
- } else {
- _basePos
- }
- };
- // ===================== CHANGE END =====================
- // AI Helper Functions
- fnc_updateStrengthTally = {
- params ["_entity", "_side"];
- if (!isServer) exitWith {};
- private _unitStrength = 1; // Default strength
- // Determine strength based on type
- if (_entity isKindOf "CAManBase") then {
- if (_entity getVariable ["isElite", false]) then { _unitStrength = 5; };
- if (_entity getVariable ["isSpecOps", false]) then { _unitStrength = 3; };
- if (_entity getVariable ["isSniper", false]) then { _unitStrength = 2; };
- } else {
- if (_entity isKindOf "Tank") then { _unitStrength = 15; };
- if (_entity isKindOf "APC") then { _unitStrength = 8; };
- if (_entity isKindOf "Air") then { _unitStrength = 20; };
- if (_entity isKindOf "Car" || _entity isKindOf "Ship") then { _unitStrength = 3; }; // For light vehicles etc.
- };
- // Store the calculated strength on the unit/vehicle for later use (on death)
- _entity setVariable ["strengthValue", _unitStrength, true];
- // Add the strength to the correct side's tally
- if (_side == west) then {
- BLUFOR_STRENGTH = BLUFOR_STRENGTH + _unitStrength;
- } else {
- OPFOR_STRENGTH = OPFOR_STRENGTH + _unitStrength;
- };
- };
- fnc_markAsImportant = {
- params ["_entity"];
- _entity enableDynamicSimulation false;
- _entity setVariable ["gcImportant", true, true];
- if (_entity isKindOf "AllVehicles" && !(_entity isKindOf "CAManBase")) then {
- {_x enableDynamicSimulation false; _x setVariable ["gcImportant", true, true];} forEach (crew _entity);
- };
- };
- fnc_getPatrolPosition = {
- params ["_centerPos", "_minRadius", "_maxRadius"];
- // Validate centerPos is an array with at least 2 elements
- if (typeName _centerPos != "ARRAY" || count _centerPos < 2) exitWith {
- [0, 0, 0]
- };
- private _searchRadius = _maxRadius;
- private _coverPositions = selectBestPlaces [_centerPos, _searchRadius, "forest + houses", 10, 5];
- private _landCoverPositions = _coverPositions select { !surfaceIsWater (_x select 0) };
- private _patrolPos = [];
- if (count _landCoverPositions > 0) then {
- _patrolPos = selectRandom _landCoverPositions select 0;
- } else {
- // Fallback to original method
- private _distance = _minRadius + (random (_maxRadius - _minRadius));
- private _direction = random 360;
- private _potentialPos = [(_centerPos select 0) + (_distance * cos _direction), (_centerPos select 1) + (_distance * sin _direction), 0];
- _patrolPos = [_potentialPos, 0, _maxRadius, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _patrolPos == 0) then { _patrolPos = _centerPos; };
- };
- _patrolPos
- };
- fnc_getBattleHotspot = {
- params ["_side"];
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
- private _targetPos = _enemyBase; // Default target is enemy base
- private _enemyPositions = [];
- {
- // Consider intel from the last 3 minutes (180s) to be relevant for a hotspot
- if (time - (_y get "time") < 180) then {
- _enemyPositions pushBack (_y get "position");
- };
- } forEach _intel;
- // If we have recent intel, find the largest concentration of enemies
- if (count _enemyPositions > 0) then {
- // Use the globally available clustering function from highcommand.sqf
- private _clusters = [_enemyPositions, 400] call fnc_clusterPositions;
- if (count _clusters > 0) then {
- // Find the largest cluster
- _clusters = [_clusters, [], {count _x}, "DESCEND"] call BIS_fnc_sortBy;
- private _largestCluster = _clusters select 0;
- // Calculate the center of the largest cluster to use as the target
- private _avgX = 0; private _avgY = 0;
- { _avgX = _avgX + (_x select 0); _avgY = _avgY + (_x select 1); } forEach _largestCluster;
- _targetPos = [_avgX / count _largestCluster, _avgY / count _largestCluster, 0];
- };
- };
- _targetPos // Return the calculated hotspot or the enemy base as a fallback
- };
- // ==================== OPTIMIZED FUNCTION ====================
- fnc_getRandomWaveType = {
- params ["_availablePoints", "_side"];
- private [
- "_waveHistory", "_affordableWaves", "_waveType", "_weight",
- "_canAfford", "_wasRecentlyUsed", "_totalWeight",
- "_random", "_currentWeight", "_selectedWave"
- ];
- _waveHistory = if (_side == west) then {BLUFOR_LAST_WAVES} else {OPFOR_LAST_WAVES};
- _affordableWaves = [];
- // --- OPTIMIZATION: Pre-calculate all vehicle counts once ---
- private _aliveLightVehicles = 0;
- private _aliveTanks = 0;
- private _aliveHelis = 0;
- if (_side == west) then {
- _aliveLightVehicles = count (BLUFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x && alive _x});
- _aliveTanks = count (BLUFOR_ACTIVE_TANKS select {!isNull _x && alive _x});
- _aliveHelis = count (BLUFOR_ACTIVE_ATTACK_HELIS select {!isNull _x && alive _x});
- } else {
- _aliveLightVehicles = count (OPFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x && alive _x});
- _aliveTanks = count (OPFOR_ACTIVE_TANKS select {!isNull _x && alive _x});
- _aliveHelis = count (OPFOR_ACTIVE_ATTACK_HELIS select {!isNull _x && alive _x});
- };
- // --- END OPTIMIZATION ---
- {
- _waveType = _x select 0;
- _weight = _x select 1;
- _canAfford = false;
- switch (_waveType) do {
- case "infantry": {if (!isNil "INFANTRY_VALUE") then {_canAfford = (_availablePoints >= (2 * 6 * INFANTRY_VALUE));};};
- case "upgraded": {if (!isNil "UPGRADED_VALUE") then {_canAfford = (_availablePoints >= (2 * 6 * UPGRADED_VALUE));};};
- case "specops": {if (!isNil "SPECOPS_VALUE") then {_canAfford = (_availablePoints >= (2 * 6 * SPECOPS_VALUE));};};
- case "sniper": {if (!isNil "SNIPER_VALUE") then {_canAfford = (_availablePoints >= (2 * 2 * SNIPER_VALUE));};};
- case "elite": {if (!isNil "ELITE_VALUE") then {_canAfford = (_availablePoints >= (2 * 5 * ELITE_VALUE));};};
- case "light_vehicle": {
- if (!isNil "VEHICLE_VALUE" && !isNil "maxLightVehiclesPerSide") then {
- _canAfford = (time > 600) && (_availablePoints >= (2 * VEHICLE_VALUE)) && (_aliveLightVehicles < maxLightVehiclesPerSide);
- };
- };
- case "tank": {
- if (!isNil "TANK_VALUE" && !isNil "maxTanksPerSide") then {
- _canAfford = (time > 1200) && (_availablePoints >= (2 * TANK_VALUE)) && (_aliveTanks < maxTanksPerSide);
- };
- };
- case "attack_heli": {
- if (!isNil "ATTACK_HELI_VALUE" && !isNil "maxAttackHelisPerSide") then {
- _canAfford = (time > 1200) && (_availablePoints >= (2 * ATTACK_HELI_VALUE)) && (_aliveHelis < maxAttackHelisPerSide);
- };
- };
- };
- if (_canAfford) then {
- _wasRecentlyUsed = _waveType in _waveHistory;
- if (_wasRecentlyUsed) then {
- _weight = _weight * 0.2;
- } else {
- _weight = _weight * 1.5;
- };
- _affordableWaves pushBack [_waveType, _weight];
- };
- } forEach WAVE_WEIGHTS;
- if (count _affordableWaves == 0) exitWith {nil};
- _totalWeight = 0;
- {_totalWeight = _totalWeight + (_x select 1);} forEach _affordableWaves;
- _random = random _totalWeight;
- _currentWeight = 0;
- _selectedWave = "infantry";
- {
- _currentWeight = _currentWeight + (_x select 1);
- if (_random <= _currentWeight) exitWith { _selectedWave = _x select 0; };
- } forEach _affordableWaves;
- _selectedWave
- };
- // AI Patrol Functions
- fnc_createPatrolWaypoints = {
- params ["_group", "_enemyBasePos", ["_behavior", "AWARE"], ["_formation", "LINE"]];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // First waypoint: Move closer to enemy base (reduced minimum distance from 500 to 200)
- private _patrolPos = [_enemyBasePos, 200, PATROL_RADIUS] call fnc_getPatrolPosition;
- private _wp = _group addWaypoint [_patrolPos, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointStatements ["true", "{_x setUnitPos 'UP';} forEach units group this;"];
- _wp setWaypointBehaviour _behavior;
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointFormation _formation;
- _wp setWaypointCombatMode "YELLOW";
- // Add enemy base position directly as a SAD waypoint
- _wp = _group addWaypoint [_enemyBasePos, 100];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "COMBAT";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointFormation _formation;
- _wp setWaypointCombatMode "RED";
- _wp setWaypointCompletionRadius 150;
- // Additional patrol points around enemy base (reduced minimum from 300 to 100)
- for "_i" from 1 to 2 do {
- _patrolPos = [_enemyBasePos, 100, PATROL_RADIUS] call fnc_getPatrolPosition;
- _wp = _group addWaypoint [_patrolPos, 75];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointFormation _formation;
- _wp setWaypointCombatMode "RED";
- _wp setWaypointCompletionRadius 100;
- };
- if (count (waypoints _group) > 1) then {
- _wp = _group addWaypoint [waypointPosition [_group, 1], 0];
- _wp setWaypointType "CYCLE";
- };
- _group setBehaviour _behavior;
- _group setCombatMode "RED";
- _group setSpeedMode "NORMAL";
- };
- fnc_createVehiclePatrolWaypoints = {
- params ["_group", "_enemyBasePos", "_ownBasePos"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _vehiclePatrolRadius = PATROL_RADIUS * 1.5;
- for "_i" from 0 to 3 do {
- private _patrolPos = [_enemyBasePos, 200, _vehiclePatrolRadius, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
- // MODIFIED: Robust fallback to prevent water waypoints.
- if (count _patrolPos == 0) then {
- // Try up to 10 times to find a random land position.
- for "_j" from 0 to 9 do {
- private _dir = random 360;
- private _dist = 200 + random (_vehiclePatrolRadius - 200);
- private _potentialPos = _enemyBasePos getPos [_dist, _dir];
- if (!surfaceIsWater _potentialPos) then {
- _patrolPos = _potentialPos;
- break;
- };
- };
- // If still no valid position, fallback to the base itself.
- if (count _patrolPos == 0) then {
- _patrolPos = _enemyBasePos;
- };
- };
- private _wp = _group addWaypoint [_patrolPos, 50];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointCombatMode "RED";
- _wp setWaypointCompletionRadius 150;
- };
- if (count (waypoints _group) > 0) then {
- private _wpCycle = _group addWaypoint [waypointPosition [_group, 0], 0];
- _wpCycle setWaypointType "CYCLE";
- };
- _group setBehaviour "AWARE";
- _group setCombatMode "YELLOW";
- _group setSpeedMode "NORMAL";
- };
- // Orders helicopters to patrol and attack targets from a safe altitude.
- fnc_createHeliPatrolWaypoints = {
- params ["_group", "_enemyBasePos"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _patrolRadius = PATROL_RADIUS * 3; // Larger radius for air patrols
- private _patrolAltitude = 150; // Fly 150m above ground
- for "_i" from 0 to 3 do {
- // Find a random position in the patrol area
- private _patrolPosGround = [_enemyBasePos, 500, _patrolRadius] call fnc_getPatrolPosition;
- private _patrolPosAir = _patrolPosGround vectorAdd [0,0,_patrolAltitude];
- private _wp = _group addWaypoint [_patrolPosAir, 50];
- _wp setWaypointType "SAD"; // Search and Destroy
- _wp setWaypointBehaviour "COMBAT"; // Immediately engage enemies
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointCombatMode "RED";
- _wp setWaypointCompletionRadius 300; // Larger radius to engage targets
- };
- // Cycle waypoints for continuous patrol
- if (count (waypoints _group) > 0) then {
- private _wpCycle = _group addWaypoint [waypointPosition [_group, 0], 0];
- _wpCycle setWaypointType "CYCLE";
- };
- // Set group combat parameters
- _group setBehaviour "COMBAT";
- _group setCombatMode "RED";
- _group setSpeedMode "NORMAL";
- };
- // Orders vehicles to a defensive staging area near their own base to await HC orders.
- fnc_createVehicleStagingWaypoints = {
- params ["_group", "_ownBasePos"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Staging area radius, around own base
- private _stagingRadius = 600;
- // Find a good defensive position near the base
- private _stagingPos = [_ownBasePos, 300, _stagingRadius, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
- // MODIFIED: Robust fallback to prevent water waypoints.
- if (count _stagingPos == 0) then {
- // Try up to 10 times to find a random land position.
- for "_j" from 0 to 9 do {
- private _dir = random 360;
- private _dist = 300 + random (_stagingRadius - 300);
- private _potentialPos = _ownBasePos getPos [_dist, _dir];
- if (!surfaceIsWater _potentialPos) then {
- _stagingPos = _potentialPos;
- break;
- };
- };
- // If still no valid position, fallback to the base itself.
- if (count _stagingPos == 0) then {
- _stagingPos = _ownBasePos;
- };
- };
- // Move to staging position
- private _wp = _group addWaypoint [_stagingPos, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointCombatMode "YELLOW"; // Be cautious
- _wp setWaypointCompletionRadius 150;
- // After arriving, patrol the staging area defensively
- private _wp2 = _group addWaypoint [_stagingPos, 50];
- _wp2 setWaypointType "SAD"; // Search and Destroy in the local area
- _wp2 setWaypointBehaviour "AWARE";
- _wp2 setWaypointSpeed "NORMAL";
- _wp2 setWaypointCombatMode "RED"; // Engage threats
- _wp2 setWaypointCompletionRadius 200; // Large radius for defensive scan
- // Create a small patrol loop in the staging area
- private _patrolPos2 = [_ownBasePos, 300, _stagingRadius, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _patrolPos2 == 0) then { _patrolPos2 = _stagingPos; };
- private _wp3 = _group addWaypoint [_patrolPos2, 50];
- _wp3 setWaypointType "SAD";
- _wp3 setWaypointBehaviour "AWARE";
- _wp3 setWaypointSpeed "NORMAL";
- _wp3 setWaypointCombatMode "RED";
- _wp3 setWaypointCompletionRadius 200;
- // Cycle between the two SAD waypoints
- if (count (waypoints _group) > 2) then {
- private _wpCycle = _group addWaypoint [waypointPosition [_group, 1], 0]; // Cycle back to the first SAD WP
- _wpCycle setWaypointType "CYCLE";
- };
- _group setBehaviour "AWARE";
- _group setCombatMode "YELLOW";
- _group setSpeedMode "NORMAL";
- };
- fnc_createStealthPatrol = {
- params ["_group", "_enemyBasePos"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _approachPos = [_enemyBasePos, PATROL_RADIUS, PATROL_RADIUS + 300, 10, 2, 0, 10] call BIS_fnc_findSafePos;
- private _wp = _group addWaypoint [_approachPos, 30];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- _wp setWaypointFormation "FILE";
- for "_i" from 1 to 3 do {
- private _patrolPos = [_enemyBasePos, 400, PATROL_RADIUS, 10, 2, 0, 10] call BIS_fnc_findSafePos;
- _wp = _group addWaypoint [_patrolPos, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- _wp setWaypointFormation "FILE";
- _wp setWaypointCombatMode "GREEN";
- };
- private _patrolPos = [_enemyBasePos, 200, 800, 10, 2, 0, 10] call BIS_fnc_findSafePos;
- _wp = _group addWaypoint [_patrolPos, 30];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "COMBAT";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointFormation "LINE";
- _wp setWaypointCombatMode "RED";
- if (count (waypoints _group) > 1) then {
- _wp = _group addWaypoint [waypointPosition [_group, 1], 0];
- _wp setWaypointType "CYCLE";
- };
- };
- // Wave Spawning Functions
- fnc_spawnBluforWave = {
- params ["_waveType", ["_isInitialWave", false], ["_flankIndex", 0]];
- if (isNil "bluforSpawnObj") exitWith {grpNull};
- private _group = grpNull;
- switch (_waveType) do {
- case "infantry": {
- if (BLUFOR_POINTS >= (2 * 6 * INFANTRY_VALUE)) then {
- _group = [_isInitialWave, _flankIndex] call fnc_spawnBluforSquad;
- };
- };
- case "upgraded": {
- if (BLUFOR_POINTS >= (2 * 6 * UPGRADED_VALUE)) then {
- _group = [] call fnc_spawnBluforUpgradedSquad;
- };
- };
- case "specops": {
- if (BLUFOR_POINTS >= (2 * 6 * SPECOPS_VALUE)) then {
- _group = [] call fnc_spawnBluforSpecOps;
- };
- };
- case "sniper": {
- if (BLUFOR_POINTS >= (2 * 2 * SNIPER_VALUE)) then {
- _group = [] call fnc_spawnBluforSnipers;
- };
- };
- case "elite": {
- if (BLUFOR_POINTS >= (2 * 5 * ELITE_VALUE)) then {
- _group = [] call fnc_spawnBluforElite;
- };
- };
- case "light_vehicle": {
- if (BLUFOR_POINTS >= (2 * VEHICLE_VALUE)) then {
- _group = [] call fnc_spawnBluforLightVehicle;
- };
- };
- case "tank": {
- if (BLUFOR_POINTS >= (2 * TANK_VALUE)) then {
- _group = [] call fnc_spawnBluforTank;
- };
- };
- case "attack_heli": {
- if (BLUFOR_POINTS >= (2 * ATTACK_HELI_VALUE)) then {
- _group = [] call fnc_spawnBluforAttackHeli;
- };
- };
- };
- if (!isNull _group) then {
- private _waveHistory = BLUFOR_LAST_WAVES;
- _waveHistory pushBack _waveType;
- if (count _waveHistory > WAVE_HISTORY_SIZE) then {_waveHistory deleteAt 0;};
- BLUFOR_LAST_WAVES = _waveHistory;
- };
- _group
- };
- fnc_spawnOpforWave = {
- params ["_waveType", ["_isInitialWave", false], ["_flankIndex", 0]];
- if (isNil "opforSpawnObj") exitWith {grpNull};
- private _group = grpNull;
- switch (_waveType) do {
- case "infantry": {
- if (OPFOR_POINTS >= (2 * 6 * INFANTRY_VALUE)) then {
- _group = [_isInitialWave, _flankIndex] call fnc_spawnOpforSquad;
- };
- };
- case "upgraded": {
- if (OPFOR_POINTS >= (2 * 6 * UPGRADED_VALUE)) then {
- _group = [] call fnc_spawnOpforUpgradedSquad;
- };
- };
- case "specops": {
- if (OPFOR_POINTS >= (2 * 6 * SPECOPS_VALUE)) then {
- _group = [] call fnc_spawnOpforSpecOps;
- };
- };
- case "sniper": {
- if (OPFOR_POINTS >= (2 * 2 * SNIPER_VALUE)) then {
- _group = [] call fnc_spawnOpforSnipers;
- };
- };
- case "elite": {
- if (OPFOR_POINTS >= (2 * 5 * ELITE_VALUE)) then {
- _group = [] call fnc_spawnOpforElite;
- };
- };
- case "light_vehicle": {
- if (OPFOR_POINTS >= (2 * VEHICLE_VALUE)) then {
- _group = [] call fnc_spawnOpforLightVehicle;
- };
- };
- case "tank": {
- if (OPFOR_POINTS >= (2 * TANK_VALUE)) then {
- _group = [] call fnc_spawnOpforTank;
- };
- };
- case "attack_heli": {
- if (OPFOR_POINTS >= (2 * ATTACK_HELI_VALUE)) then {
- _group = [] call fnc_spawnOpforAttackHeli;
- };
- };
- };
- if (!isNull _group) then {
- private _waveHistory = OPFOR_LAST_WAVES;
- _waveHistory pushBack _waveType;
- if (count _waveHistory > WAVE_HISTORY_SIZE) then {_waveHistory deleteAt 0;};
- OPFOR_LAST_WAVES = _waveHistory;
- };
- _group
- };
- // BLUFOR Spawn Functions
- fnc_spawnBluforSquad = {
- params ["_isInitialWave", "_flankIndex"];
- if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 6 * INFANTRY_VALUE)) exitWith {grpNull};
- BLUFOR_POINTS = BLUFOR_POINTS - (2 * 6 * INFANTRY_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [west] call fnc_getSafeSpawnPosition;
- _group = createGroup west;
- _units = selectRandom BLUFOR_INFANTRY_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.6, "REGULAR"] call fnc_enhanceIndividualAI;
- _unit setVariable ["unitValue", INFANTRY_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, west] call fnc_updateStrengthTally;
- } forEach _units;
- if (_isInitialWave) then {
- [_group, west, _flankIndex] call fnc_createInitialFlankWaypoints;
- } else {
- [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
- };
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnBluforUpgradedSquad = {
- if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 6 * UPGRADED_VALUE)) exitWith {grpNull};
- BLUFOR_POINTS = BLUFOR_POINTS - (2 * 6 * UPGRADED_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [west] call fnc_getSafeSpawnPosition;
- _group = createGroup west;
- _units = selectRandom BLUFOR_UPGRADED_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.68, "UPGRADED"] call fnc_enhanceIndividualAI;
- _unit setVariable ["unitValue", UPGRADED_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, west] call fnc_updateStrengthTally;
- } forEach _units;
- [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnBluforSpecOps = {
- if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 6 * SPECOPS_VALUE)) exitWith {grpNull};
- BLUFOR_POINTS = BLUFOR_POINTS - (2 * 6 * SPECOPS_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [west] call fnc_getSafeSpawnPosition;
- _group = createGroup west;
- _units = selectRandom BLUFOR_SPECOPS_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
- _unit setVariable ["isSpecOps", true, true];
- _unit setVariable ["unitValue", SPECOPS_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, west] call fnc_updateStrengthTally;
- } forEach _units;
- [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnBluforSnipers = {
- if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 2 * SNIPER_VALUE)) exitWith {grpNull};
- BLUFOR_POINTS = BLUFOR_POINTS - (2 * 2 * SNIPER_VALUE);
- private ["_spawnPos", "_group", "_sniper", "_spotter"];
- _spawnPos = [west] call fnc_getSafeSpawnPosition;
- _group = createGroup west;
- _sniper = _group createUnit ["B_sniper_F", _spawnPos, [], 20, "NONE"];
- [_sniper, 0.8, "SNIPER"] call fnc_enhanceIndividualAI;
- _spotter = _group createUnit ["B_spotter_F", _spawnPos, [], 20, "NONE"];
- [_spotter, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
- _sniper setVariable ["isSniper", true, true]; _sniper setVariable ["unitValue", SNIPER_VALUE, true];
- _spotter setVariable ["isSniper", true, true]; _spotter setVariable ["unitValue", SNIPER_VALUE, true];
- [_sniper] call fnc_markAsImportant; [_spotter] call fnc_markAsImportant;
- [_sniper, west] call fnc_updateStrengthTally;
- [_spotter, west] call fnc_updateStrengthTally;
- // CHANGED: Use standard patrol waypoints to increase movement speed and impact
- [_group, getPos opforSpawnObj] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnBluforElite = {
- if (isNil "bluforSpawnObj" || BLUFOR_POINTS < (2 * 5 * ELITE_VALUE)) exitWith {grpNull};
- BLUFOR_POINTS = BLUFOR_POINTS - (2 * 5 * ELITE_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [west] call fnc_getSafeSpawnPosition;
- _group = createGroup west;
- _units = selectRandom BLUFOR_ELITE_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.9, "ELITE"] call fnc_enhanceIndividualAI;
- _unit setVariable ["isElite", true, true];
- _unit setVariable ["unitValue", ELITE_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, west] call fnc_updateStrengthTally;
- } forEach _units;
- [_group, getPos opforSpawnObj, "COMBAT"] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- // ===================================================================================
- // ==================== OPTIMIZATION: GENERIC VEHICLE SPAWN LOGIC ====================
- // ===================================================================================
- // NEW: Generic vehicle spawning function to reduce code duplication and improve maintainability.
- fnc_spawnVehicleGeneric = {
- params [
- "_side",
- "_vehiclePool",
- "_activeVehicleArrayName",
- "_maxVehicleVarName",
- "_vehicleValue",
- "_crewSkill",
- "_crewSkillRole",
- "_spawnMode", // "NONE" for ground, "FLY" for air
- "_taskType", // "GROUND_PATROL", "HELI_PATROL"
- "_hcLockDuration"
- ];
- private _spawnObj = if (_side == west) then {bluforSpawnObj} else {opforSpawnObj};
- if (isNil "_spawnObj") exitWith {grpNull};
- // Dynamically get side-specific points and vehicle arrays
- private _pointsVarName = if (_side == west) then {"BLUFOR_POINTS"} else {"OPFOR_POINTS"};
- private _points = missionNamespace getVariable [_pointsVarName, 0];
- private _activeVehicles = missionNamespace getVariable [_activeVehicleArrayName, []];
- private _maxVehicleCount = missionNamespace getVariable [_maxVehicleVarName, 0];
- // Check if affordable
- if (_points < (2 * _vehicleValue)) exitWith {grpNull};
- // Clean the array of destroyed/null vehicles and check the cap
- _activeVehicles = _activeVehicles select {!isNull _x && alive _x};
- if (count _activeVehicles >= _maxVehicleCount) exitWith {grpNull};
- // Deduct points
- missionNamespace setVariable [_pointsVarName, _points - (2 * _vehicleValue)];
- private _basePos = getPos _spawnObj;
- private _spawnPos = [];
- if (_spawnMode == "FLY") then {
- _spawnPos = _basePos vectorAdd [((random 200) - 100), ((random 200) - 100), 100];
- } else {
- _spawnPos = [_basePos, 50, 150, 15, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _spawnPos == 0) then {
- _spawnPos = _basePos; // Fallback to base position
- };
- };
- // Create vehicle and crew
- private _vehicleClass = selectRandom _vehiclePool;
- private _vehicle = createVehicle [_vehicleClass, _spawnPos, [], 0, _spawnMode];
- _vehicle setVectorUp [0,0,1];
- if (_spawnMode == "FLY") then { _vehicle engineOn true; };
- private _group = createGroup _side;
- createVehicleCrew _vehicle;
- (crew _vehicle) joinSilent _group;
- // Setup crew
- {
- private _currentSkill = _crewSkill;
- // Special case for pilots
- if (_spawnMode == "FLY" && {_x == driver _vehicle}) then { _currentSkill = 0.95; };
- [_x, _currentSkill, _crewSkillRole] call fnc_enhanceIndividualAI;
- _x setVariable ["unitValue", _vehicleValue / (count (crew _vehicle)), true];
- [_x] call fnc_markAsImportant;
- } forEach (crew _vehicle);
- // Setup vehicle
- _vehicle setVariable ["unitValue", _vehicleValue, true];
- [_vehicle] call fnc_markAsImportant;
- // Update strength tally for the vehicle and its crew
- [_vehicle, _side] call fnc_updateStrengthTally;
- {
- [_x, _side] call fnc_updateStrengthTally;
- } forEach (crew _vehicle);
- _activeVehicles pushBack _vehicle;
- missionNamespace setVariable [_activeVehicleArrayName, _activeVehicles, true]; // Update the global array
- // Assign task
- switch (_taskType) do {
- case "GROUND_PATROL": {
- private _hotspot = [_side] call fnc_getBattleHotspot;
- [_group, _hotspot, _basePos] call fnc_createVehiclePatrolWaypoints;
- };
- case "HELI_PATROL": {
- private _hotspot = [_side] call fnc_getBattleHotspot;
- [_group, _hotspot] call fnc_createHeliPatrolWaypoints;
- };
- };
- // Set HC lock
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group, _hcLockDuration] spawn {
- params ["_grp", "_duration"];
- sleep _duration;
- if (!isNull _grp) then {
- _grp setVariable ["HC_FORCED_LOCK", false];
- };
- };
- _group
- };
- // ==================== REFACTORED VEHICLE SPAWN FUNCTIONS ====================
- fnc_spawnBluforLightVehicle = {
- [
- west,
- BLUFOR_LIGHT_VEHICLE_POOL,
- "BLUFOR_ACTIVE_LIGHT_VEHICLES",
- "maxLightVehiclesPerSide",
- VEHICLE_VALUE,
- 0.7,
- "UPGRADED_INF",
- "NONE",
- "GROUND_PATROL",
- 90
- ] call fnc_spawnVehicleGeneric;
- };
- fnc_spawnBluforTank = {
- [
- west,
- BLUFOR_TANK_POOL,
- "BLUFOR_ACTIVE_TANKS",
- "maxTanksPerSide",
- TANK_VALUE,
- 0.8,
- "ELITE",
- "NONE",
- "GROUND_PATROL",
- 90
- ] call fnc_spawnVehicleGeneric;
- };
- fnc_spawnBluforAttackHeli = {
- [
- west,
- BLUFOR_ATTACK_HELI_POOL,
- "BLUFOR_ACTIVE_ATTACK_HELIS",
- "maxAttackHelisPerSide",
- ATTACK_HELI_VALUE,
- 0.85,
- "ELITE",
- "FLY",
- "HELI_PATROL",
- 180
- ] call fnc_spawnVehicleGeneric;
- };
- // OPFOR Spawn Functions
- fnc_spawnOpforSquad = {
- params ["_isInitialWave", "_flankIndex"];
- if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 6 * INFANTRY_VALUE)) exitWith {grpNull};
- OPFOR_POINTS = OPFOR_POINTS - (2 * 6 * INFANTRY_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [east] call fnc_getSafeSpawnPosition;
- _group = createGroup east;
- _units = selectRandom OPFOR_INFANTRY_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.6, "REGULAR"] call fnc_enhanceIndividualAI;
- _unit setVariable ["unitValue", INFANTRY_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, east] call fnc_updateStrengthTally;
- } forEach _units;
- if (_isInitialWave) then {
- [_group, east, _flankIndex] call fnc_createInitialFlankWaypoints;
- } else {
- [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
- };
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnOpforUpgradedSquad = {
- if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 6 * UPGRADED_VALUE)) exitWith {grpNull};
- OPFOR_POINTS = OPFOR_POINTS - (2 * 6 * UPGRADED_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [east] call fnc_getSafeSpawnPosition;
- _group = createGroup east;
- _units = selectRandom OPFOR_UPGRADED_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.68, "UPGRADED"] call fnc_enhanceIndividualAI;
- _unit setVariable ["unitValue", UPGRADED_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, east] call fnc_updateStrengthTally;
- } forEach _units;
- [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnOpforSpecOps = {
- if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 6 * SPECOPS_VALUE)) exitWith {grpNull};
- OPFOR_POINTS = OPFOR_POINTS - (2 * 6 * SPECOPS_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [east] call fnc_getSafeSpawnPosition;
- _group = createGroup east;
- _units = selectRandom OPFOR_SPECOPS_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
- _unit setVariable ["isSpecOps", true, true];
- _unit setVariable ["unitValue", SPECOPS_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, east] call fnc_updateStrengthTally;
- } forEach _units;
- [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnOpforSnipers = {
- if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 2 * SNIPER_VALUE)) exitWith {grpNull};
- OPFOR_POINTS = OPFOR_POINTS - (2 * 2 * SNIPER_VALUE);
- private ["_spawnPos", "_group", "_sniper", "_spotter"];
- _spawnPos = [east] call fnc_getSafeSpawnPosition;
- _group = createGroup east;
- _sniper = _group createUnit ["O_sniper_F", _spawnPos, [], 20, "NONE"];
- [_sniper, 0.8, "SNIPER"] call fnc_enhanceIndividualAI;
- _spotter = _group createUnit ["O_spotter_F", _spawnPos, [], 20, "NONE"];
- [_spotter, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
- _sniper setVariable ["isSniper", true, true]; _sniper setVariable ["unitValue", SNIPER_VALUE, true];
- _spotter setVariable ["isSniper", true, true]; _spotter setVariable ["unitValue", SNIPER_VALUE, true];
- [_sniper] call fnc_markAsImportant; [_spotter] call fnc_markAsImportant;
- [_sniper, east] call fnc_updateStrengthTally;
- [_spotter, east] call fnc_updateStrengthTally;
- // CHANGED: Use standard patrol waypoints to increase movement speed and impact
- [_group, getPos bluforSpawnObj] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnOpforElite = {
- if (isNil "opforSpawnObj" || OPFOR_POINTS < (2 * 5 * ELITE_VALUE)) exitWith {grpNull};
- OPFOR_POINTS = OPFOR_POINTS - (2 * 5 * ELITE_VALUE);
- private ["_spawnPos", "_group", "_units"];
- _spawnPos = [east] call fnc_getSafeSpawnPosition;
- _group = createGroup east;
- _units = selectRandom OPFOR_ELITE_POOL;
- {
- private _unit = _group createUnit [_x, _spawnPos, [], 10, "NONE"];
- [_unit, 0.9, "ELITE"] call fnc_enhanceIndividualAI;
- _unit setVariable ["isElite", true, true];
- _unit setVariable ["unitValue", ELITE_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, east] call fnc_updateStrengthTally;
- } forEach _units;
- [_group, getPos bluforSpawnObj, "COMBAT"] call fnc_createPatrolWaypoints;
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn { params ["_grp"]; sleep 60; _grp setVariable ["HC_FORCED_LOCK", false]; };
- _group
- };
- fnc_spawnOpforLightVehicle = {
- [
- east,
- OPFOR_LIGHT_VEHICLE_POOL,
- "OPFOR_ACTIVE_LIGHT_VEHICLES",
- "maxLightVehiclesPerSide",
- VEHICLE_VALUE,
- 0.7,
- "UPGRADED_INF",
- "NONE",
- "GROUND_PATROL",
- 90
- ] call fnc_spawnVehicleGeneric;
- };
- fnc_spawnOpforTank = {
- [
- east,
- OPFOR_TANK_POOL,
- "OPFOR_ACTIVE_TANKS",
- "maxTanksPerSide",
- TANK_VALUE,
- 0.8,
- "ELITE",
- "NONE",
- "GROUND_PATROL",
- 90
- ] call fnc_spawnVehicleGeneric;
- };
- fnc_spawnOpforAttackHeli = {
- [
- east,
- OPFOR_ATTACK_HELI_POOL,
- "OPFOR_ACTIVE_ATTACK_HELIS",
- "maxAttackHelisPerSide",
- ATTACK_HELI_VALUE,
- 0.85,
- "ELITE",
- "FLY",
- "HELI_PATROL",
- 180
- ] call fnc_spawnVehicleGeneric;
- };
- fnc_createInitialFlankWaypoints = {
- params ["_group", "_side", "_flankIndex"];
- private _capturePos = (CAPTURE_LOCATIONS select 0) select 1;
- private _basePos = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _dirFromCaptureToBase = _capturePos getDir _basePos;
- private _frontLineDistance = 200; // Distance from capture zone towards base
- private _frontLinePos = _capturePos getPos [_frontLineDistance, _dirFromCaptureToBase];
- private _flankOffset = 100; // Offset for left and right flanks
- private _targetPos = _frontLinePos;
- switch (_flankIndex) do {
- case 0: { // Left flank
- _targetPos = _frontLinePos getPos [_flankOffset, _dirFromCaptureToBase - 90];
- };
- case 1: { // Center
- _targetPos = _frontLinePos;
- };
- case 2: { // Right flank
- _targetPos = _frontLinePos getPos [_flankOffset, _dirFromCaptureToBase + 90];
- };
- };
- private _safePos = [_targetPos, 0, 50, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _safePos == 0) then { _safePos = _targetPos; };
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _wp = _group addWaypoint [_safePos, 20];
- _wp setWaypointType "MOVE";
- _wp setWaypointStatements ["true", "{_x setUnitPos 'UP';} forEach units group this;"];
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointFormation "LINE";
- _wp setWaypointCombatMode "YELLOW";
- private _patrolWp1 = _group addWaypoint [_safePos, 75];
- _patrolWp1 setWaypointType "SAD";
- _patrolWp1 setWaypointBehaviour "COMBAT";
- _patrolWp1 setWaypointCombatMode "RED";
- _patrolWp1 setWaypointCompletionRadius 100;
- private _patrolPos2 = [_targetPos, 50, 150, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _patrolPos2 == 0) then { _patrolPos2 = _targetPos; };
- private _patrolWp2 = _group addWaypoint [_patrolPos2, 75];
- _patrolWp2 setWaypointType "SAD";
- _patrolWp2 setWaypointBehaviour "COMBAT";
- _patrolWp2 setWaypointCombatMode "RED";
- _patrolWp2 setWaypointCompletionRadius 100;
- if (count (waypoints _group) > 2) then {
- private _cycleWp = _group addWaypoint [waypointPosition [_group, 1], 0];
- _cycleWp setWaypointType "CYCLE";
- };
- _group setBehaviour "AWARE";
- _group setCombatMode "RED";
- _group setSpeedMode "NORMAL";
- };
- // Function to find closest suitable infantry group for merging
- fnc_findClosestInfantryGroup = {
- params ["_smallGroup"];
- private _smallGroupSide = side _smallGroup;
- private _leader = leader _smallGroup;
- if (isNull _leader) exitWith {grpNull};
- private _smallGroupPos = getPos _leader;
- private _closestGroup = grpNull;
- private _closestDistance = 99999;
- // ==================== CHANGE START: OPTIMIZED GROUP FINDING ====================
- // Instead of looping allGroups, we search a 500m radius for friendly units. This is much faster.
- private _nearbyFriendlies = _smallGroupPos nearEntities [["CAManBase"], 500];
- private _candidateGroups = [];
- {
- private _unit = _x;
- if (side _unit == _smallGroupSide && alive _unit) then {
- private _grp = group _unit;
- // Add the group to our candidates list, but only if it's not already there.
- _candidateGroups pushBackUnique _grp;
- };
- } forEach _nearbyFriendlies;
- // Now, we iterate the much smaller list of nearby candidate groups.
- {
- private _group = _x;
- // Filter out unsuitable groups
- if (
- _group != _smallGroup &&
- count (units _x select {alive _x}) > 2 &&
- !([_group] call fnc_isGroupLocked) &&
- ({isPlayer _x} count (units _group) == 0)
- ) then {
- private _grpLeader = leader _group;
- if (!isNull _grpLeader && alive _grpLeader) then {
- // Check if it's an infantry group (not in vehicles)
- private _isInfantryGroup = true;
- {
- if (vehicle _x != _x) then { _isInfantryGroup = false; };
- } forEach (units _group select {alive _x});
- if (_isInfantryGroup) then {
- private _distance = _smallGroupPos distance (getPos _grpLeader);
- if (_distance < _closestDistance) then {
- _closestDistance = _distance;
- _closestGroup = _group;
- };
- };
- };
- };
- } forEach _candidateGroups;
- // ===================== CHANGE END =====================
- _closestGroup
- };
- // Function to merge a small group into a larger one
- fnc_mergeSmallGroup = {
- params ["_smallGroup", "_targetGroup"];
- if (isNull _smallGroup || isNull _targetGroup) exitWith {false};
- if (_smallGroup == _targetGroup) exitWith {false};
- private _unitsToMove = units _smallGroup select {alive _x};
- if (count _unitsToMove == 0) exitWith {false};
- // Move all alive units from small group to target group
- {
- [_x] joinSilent _targetGroup;
- } forEach _unitsToMove;
- // Clean up the old group from HC tracking if it exists
- HC_Active_Groups deleteAt (groupId _smallGroup);
- // Delete the now-empty group
- deleteGroup _smallGroup;
- true
- };
- // Function to check and process small groups for merging
- fnc_checkAndMergeSmallGroups = {
- params ["_group"];
- if (isNull _group) exitWith {};
- private _aliveUnits = units _group select {alive _x};
- private _unitCount = count _aliveUnits;
- // Check if group qualifies for merging (1-2 alive members)
- if (_unitCount >= 1 && _unitCount <= 2) then {
- // Don't merge player groups
- if (({isPlayer _x} count _aliveUnits) > 0) exitWith {};
- // Don't merge sniper squads (they're supposed to be 2-man teams)
- if (({_x getVariable ["isSniper", false]} count _aliveUnits) > 0) exitWith {};
- // Don't merge vehicle crews
- private _isInfantryGroup = true;
- {
- if (vehicle _x != _x) then {
- _isInfantryGroup = false;
- };
- } forEach _aliveUnits;
- if (!_isInfantryGroup) exitWith {};
- // Don't merge if HC locked
- if ([_group] call fnc_isGroupLocked) exitWith {};
- // Find closest suitable group
- private _targetGroup = [_group] call fnc_findClosestInfantryGroup;
- if (!isNull _targetGroup) then {
- // Perform the merge
- private _success = [_group, _targetGroup] call fnc_mergeSmallGroup;
- if (_success) then {
- // Add a small delay before the merged units can get new HC orders
- [_targetGroup] spawn {
- params ["_grp"];
- sleep 5;
- if (!isNull _grp) then {
- _grp setVariable ["HC_FORCED_LOCK", false];
- };
- };
- };
- };
- };
- };
- ==================== END OF: AIstuff.sqf ====================
- ==================== START OF: arsenal.sqf ====================
- // arsenal.sqf
- // Purpose: Creates arsenal boxes for BLUFOR and OPFOR with purchase menu and clear effects actions.
- // REVISED: Added action to clear blurry post-processing effects.
- // REVISED: Fixed action code to use string to prevent race conditions.
- // REVISED: Removed player-purchased AI squad functionality.
- // REVISED: Fixed RadialBlur ppEffectAdjust to use 4 parameters to resolve scripting error.
- // Function to create an arsenal box at a given position for a specific side
- fnc_createBaseArsenal = {
- params ["_pos", "_side"];
- private _arsenalBox = createVehicle ["B_supplyCrate_F", _pos, [], 0, "CAN_COLLIDE"];
- _arsenalBox allowDamage false;
- _arsenalBox enableSimulationGlobal false;
- _arsenalBox enableDynamicSimulation false;
- _arsenalBox setVariable ["gcImportant", true, true];
- _arsenalBox setVariable ["arsenalSide", _side, true];
- // Clear all cargo
- clearWeaponCargoGlobal _arsenalBox;
- clearMagazineCargoGlobal _arsenalBox;
- clearItemCargoGlobal _arsenalBox;
- clearBackpackCargoGlobal _arsenalBox;
- [_arsenalBox, [], true] call BIS_fnc_removeVirtualItemCargo;
- // Load uniform restrictions if not defined
- if (isNil "BLUFOR_UNIFORMS") then {
- [] call compile preprocessFileLineNumbers "loadoutlimitations.sqf";
- };
- // Add virtual cargo
- [_arsenalBox, true, true] call BIS_fnc_addVirtualWeaponCargo;
- [_arsenalBox, true, true] call BIS_fnc_addVirtualMagazineCargo;
- [_arsenalBox, true, true] call BIS_fnc_addVirtualBackpackCargo;
- // Filter clothing items
- private _allItems = ("getNumber (_x >> 'scope') >= 2" configClasses (configFile >> "CfgWeapons")) apply {configName _x};
- private _allUniforms = _allItems select { isClass (configFile >> "CfgWeapons" >> _x >> "ItemInfo") && { getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "type") == 801 } };
- private _allVests = _allItems select { isClass (configFile >> "CfgWeapons" >> _x >> "ItemInfo") && { getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "type") == 701 } };
- private _allHeadgear = _allItems select { isClass (configFile >> "CfgWeapons" >> _x >> "ItemInfo") && { getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "type") == 605 } };
- private _allClothing = _allUniforms + _allVests + _allHeadgear;
- private _itemsWithoutClothing = _allItems - _allClothing;
- [_arsenalBox, _itemsWithoutClothing, true] call BIS_fnc_addVirtualItemCargo;
- // Set side-specific clothing and arsenal name
- private _allowedClothing = [];
- if (_side == west) then {
- _allowedClothing = BLUFOR_UNIFORMS + BLUFOR_VESTS + BLUFOR_HEADGEAR;
- _arsenalBox setVariable ["arsenalName", "BLUFOR Arsenal", true];
- } else {
- _allowedClothing = OPFOR_UNIFORMS + OPFOR_VESTS + OPFOR_HEADGEAR;
- _arsenalBox setVariable ["arsenalName", "OPFOR Arsenal", true];
- };
- [_arsenalBox, _allowedClothing, true] call BIS_fnc_addVirtualItemCargo;
- // Ensure box is grounded
- private _groundPos = _pos;
- _groundPos set [2, 0];
- _arsenalBox setPosATL _groundPos;
- _arsenalBox setVectorUp [0,0,1];
- _arsenalBox
- };
- // Function to add purchase menu action to arsenal box (client-side)
- fnc_client_addPurchaseAction = {
- params ["_arsenalBox"];
- if (!hasInterface) exitWith {};
- _arsenalBox addAction [
- "<t color='#FFD700'>[Open Purchase Menu]</t>",
- {
- [] call fnc_client_openPlayerMenu;
- },
- [],
- 6,
- true,
- true,
- "",
- "side _this == (_target getVariable ['arsenalSide', sideUnknown]) && alive _this",
- 10
- ];
- };
- // Function to add clear effects action to arsenal box (client-side)
- fnc_client_addClearEffectsAction = {
- params ["_arsenalBox"];
- if (!hasInterface) exitWith {};
- _arsenalBox addAction [
- "<t color='#00FF00'>[Clear Blurry Effects]</t>",
- {
- // Destroy all post-processing effects across all priority ranges
- for "_i" from 0 to 2000 do {
- ppEffectDestroy _i;
- };
- // Create and immediately disable clean effects to reset them
- "DynamicBlur" ppEffectEnable false;
- "RadialBlur" ppEffectEnable false;
- "chromAberration" ppEffectEnable false;
- "ColorCorrections" ppEffectEnable false;
- "FilmGrain" ppEffectEnable false;
- "WetDistortion" ppEffectEnable false;
- "ColorInversion" ppEffectEnable false;
- // Reset camera shake
- enableCamShake false;
- resetCamShake;
- // Reset any potential BIS effects
- BIS_fnc_feedback_allowPP = true;
- BIS_fnc_feedback_allowDeathScreen = true;
- hint "All visual effects cleared.";
- },
- [],
- 5,
- true,
- true,
- "",
- "side _this == (_target getVariable ['arsenalSide', sideUnknown]) && alive _this",
- 10
- ];
- };
- // ==================== CHANGE START ====================
- // NEW: Function to add settings menu action to arsenal box (client-side)
- fnc_client_addSettingsAction = {
- params ["_arsenalBox"];
- if (!hasInterface) exitWith {};
- _arsenalBox addAction [
- "<t color='#ADD8E6'>[Open Settings Menu]</t>",
- {
- createDialog "AdminSettingsMenu";
- },
- [],
- 4, // Priority below purchase and clear effects
- true,
- true,
- "",
- // Condition: Show if in singleplayer, or if the player is the server (host) in multiplayer.
- "(!isMultiplayer) || (isServer)"
- ];
- };
- // ===================== CHANGE END =====================
- // Server-side execution to create arsenals and broadcast actions
- if (isServer) then {
- [] spawn {
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- sleep 1;
- private _bluforBasePos = getPosATL bluforSpawnObj;
- _bluforBasePos set [2, 0];
- private _bluforArsenal = [_bluforBasePos, west] call fnc_createBaseArsenal;
- private _opforBasePos = getPosATL opforSpawnObj;
- _opforBasePos set [2, 0];
- private _opforArsenal = [_opforBasePos, east] call fnc_createBaseArsenal;
- missionNamespace setVariable ["BLUFOR_Arsenal", _bluforArsenal, true];
- missionNamespace setVariable ["OPFOR_Arsenal", _opforArsenal, true];
- // Broadcast purchase menu action
- [_bluforArsenal] remoteExecCall ["fnc_client_addPurchaseAction", 0, true];
- [_opforArsenal] remoteExecCall ["fnc_client_addPurchaseAction", 0, true];
- // Broadcast clear effects action
- [_bluforArsenal] remoteExecCall ["fnc_client_addClearEffectsAction", 0, true];
- [_opforArsenal] remoteExecCall ["fnc_client_addClearEffectsAction", 0, true];
- // ==================== CHANGE START ====================
- // NEW: Broadcast settings menu action
- [_bluforArsenal] remoteExecCall ["fnc_client_addSettingsAction", 0, true];
- [_opforArsenal] remoteExecCall ["fnc_client_addSettingsAction", 0, true];
- // ===================== CHANGE END =====================
- };
- };
- ==================== END OF: arsenal.sqf ====================
- ==================== START OF: botai.sqf ====================
- // botai.sqf
- // Centralized AI manager for improved performance and tactical behaviors
- // ==================== PERFORMANCE MONITORING SYSTEM ====================
- // Global performance state - accessed by all systems
- if (isNil "PERF_STATE") then {
- PERF_STATE = "EXCELLENT"; // EXCELLENT, GOOD, MODERATE, POOR, CRITICAL, EMERGENCY
- };
- if (isNil "PERF_MULTIPLIER") then {
- PERF_MULTIPLIER = 1.0; // Multiplier for sleep intervals (1.0 = normal, up to 8.0 = extreme slowdown)
- };
- // Monitor server performance and adjust intervals dynamically
- fnc_monitorPerformance = {
- private _deltaHistory = [];
- private _execTimeHistory = [];
- private _historySize = 10;
- while {true} do {
- // Measure scheduler frame delta time (works in SP and MP)
- private _schedulerDelta = diag_deltaTime;
- // Get current script execution time from main AI loop
- private _lastExecTime = missionNamespace getVariable ["BOTAI_lastExecTime", 0];
- // Add to histories
- _deltaHistory pushBack _schedulerDelta;
- _execTimeHistory pushBack _lastExecTime;
- if (count _deltaHistory > _historySize) then {
- _deltaHistory deleteAt 0;
- };
- if (count _execTimeHistory > _historySize) then {
- _execTimeHistory deleteAt 0;
- };
- // Calculate averages
- private _avgDelta = 0;
- {_avgDelta = _avgDelta + _x;} forEach _deltaHistory;
- _avgDelta = _avgDelta / count _deltaHistory;
- private _avgExecTime = 0;
- {_avgExecTime = _avgExecTime + _x;} forEach _execTimeHistory;
- _avgExecTime = _avgExecTime / count _execTimeHistory;
- // Determine performance state based on scheduler delta
- // Excellent: <0.025s (40+ scheduler fps)
- // Good: 0.025-0.040s (25-40 scheduler fps) - LOWERED from 0.035
- // Moderate: 0.040-0.060s (16-25 scheduler fps) - LOWERED from 0.050
- // Poor: 0.060-0.090s (11-16 scheduler fps) - LOWERED from 0.075
- // Critical: 0.090-0.120s (8-11 scheduler fps) - LOWERED from 0.100
- // Emergency: >0.120s (<8 scheduler fps) - LOWERED from 0.100
- private _deltaState = "EXCELLENT";
- private _deltaMultiplier = 1.0;
- if (_avgDelta < 0.025) then {
- _deltaState = "EXCELLENT";
- _deltaMultiplier = 1.0;
- } else {
- if (_avgDelta < 0.040) then {
- _deltaState = "GOOD";
- _deltaMultiplier = 1.5;
- } else {
- if (_avgDelta < 0.060) then {
- _deltaState = "MODERATE";
- _deltaMultiplier = 2.5;
- } else {
- if (_avgDelta < 0.090) then {
- _deltaState = "POOR";
- _deltaMultiplier = 4.0;
- } else {
- if (_avgDelta < 0.120) then {
- _deltaState = "CRITICAL";
- _deltaMultiplier = 6.0;
- } else {
- _deltaState = "EMERGENCY";
- _deltaMultiplier = 10.0;
- };
- };
- };
- };
- };
- // Check script execution time (secondary metric)
- // If AI loop takes >50ms per cycle, we're in trouble
- private _execState = "EXCELLENT";
- private _execMultiplier = 1.0;
- if (_avgExecTime < 0.020) then {
- _execState = "EXCELLENT";
- _execMultiplier = 1.0;
- } else {
- if (_avgExecTime < 0.040) then {
- _execState = "GOOD";
- _execMultiplier = 1.5;
- } else {
- if (_avgExecTime < 0.060) then {
- _execState = "MODERATE";
- _execMultiplier = 2.5;
- } else {
- if (_avgExecTime < 0.090) then {
- _execState = "POOR";
- _execMultiplier = 4.0;
- } else {
- if (_avgExecTime < 0.120) then {
- _execState = "CRITICAL";
- _execMultiplier = 6.0;
- } else {
- _execState = "EMERGENCY";
- _execMultiplier = 10.0;
- };
- };
- };
- };
- };
- // Use the WORST of the two metrics
- private _oldState = PERF_STATE;
- private _oldMultiplier = PERF_MULTIPLIER;
- private _worstMultiplier = _deltaMultiplier max _execMultiplier;
- if (_worstMultiplier == _deltaMultiplier) then {
- PERF_STATE = _deltaState;
- PERF_MULTIPLIER = _deltaMultiplier;
- } else {
- PERF_STATE = _execState;
- PERF_MULTIPLIER = _execMultiplier;
- };
- // Set skip flags for non-critical operations
- PERF_SKIP_NONCRITICAL = (PERF_STATE in ["CRITICAL", "EMERGENCY"]);
- PERF_SKIP_MODERATE = (PERF_STATE == "EMERGENCY");
- // Log state changes
- if (_oldState != PERF_STATE) then {
- diag_log format ["[PERF] State: %1 -> %2 (x%3) | Delta: %4ms (%5) | Exec: %6ms (%7)",
- _oldState, PERF_STATE, PERF_MULTIPLIER,
- round (_avgDelta * 1000), _deltaState,
- round (_avgExecTime * 1000), _execState];
- };
- // Adaptive monitoring interval
- if (PERF_STATE == "EXCELLENT") then {
- sleep 3;
- } else {
- if (PERF_STATE in ["CRITICAL", "EMERGENCY"]) then {
- sleep 0.5; // Monitor very frequently when critical
- } else {
- sleep 1.5;
- };
- };
- };
- };
- // Helper function to get adaptive sleep time
- fnc_getAdaptiveSleep = {
- params ["_baseSleep"];
- (_baseSleep * PERF_MULTIPLIER)
- };
- // ==================== END PERFORMANCE MONITORING ====================
- // Set AI skills based on level and role
- fnc_botSetSkills = {
- params ["_unit", "_skillLevel", "_unitRole"];
- _unit setSkill ["aimingAccuracy", (_skillLevel + 0.5) min 1];
- _unit setSkill ["aimingSpeed", (_skillLevel + 0.2) min 1];
- _unit setSkill ["aimingShake", 0];
- _unit setSkill ["courage", 1.0];
- _unit setSkill ["commanding", 1.0];
- _unit setSkill ["reloadSpeed", 1.0];
- _unit setSkill ["spotDistance", 1.0];
- _unit setSkill ["spotTime", 1.0];
- _unit setSkill ["endurance", 1.0];
- };
- // Force AI to throw smoke grenade using correct muzzle
- fnc_botUseSmoke = {
- params ["_unit", "_purpose"];
- if ("SmokeShell" in (magazines _unit)) then {
- _unit forceWeaponFire ["SmokeShellMuzzle", "SmokeShellMuzzle"];
- };
- };
- // Enhance individual AI unit during spawning
- fnc_enhanceIndividualAI = {
- params ["_unit", "_skillLevel", "_unitRole"];
- if (isNil "_unit" || {isNull _unit} || {!alive _unit}) exitWith {
- diag_log "Error: _unit is invalid in fnc_enhanceIndividualAI";
- };
- if (_unit getVariable ["BOTAI_isEnhanced", false]) exitWith {};
- _unit setVariable ["BOTAI_isEnhanced", true, true];
- // Check if unit is a helicopter pilot/gunner
- private _vehicle = vehicle _unit;
- if (_vehicle != _unit && {_vehicle isKindOf "Air"}) then {
- // Enhanced skills for helicopter crews
- _unit setSkill ["aimingAccuracy", 0.85];
- _unit setSkill ["aimingSpeed", 0.9];
- _unit setSkill ["aimingShake", 0.1];
- _unit setSkill ["courage", 1.0];
- _unit setSkill ["commanding", 1.0];
- _unit setSkill ["reloadSpeed", 1.0];
- _unit setSkill ["spotDistance", 1.0];
- _unit setSkill ["spotTime", 0.9];
- _unit setSkill ["endurance", 1.0];
- // Add flares to helicopters if not present
- if (driver _vehicle == _unit) then {
- private _hasFlares = (magazinesAllTurrets _vehicle) findIf {(_x select 0) find "CMFlare" >= 0} != -1;
- if (!_hasFlares) then {
- _vehicle addMagazineTurret ["120Rnd_CMFlare_Chaff_Magazine", [-1]];
- };
- };
- } else {
- // Original ground unit skills
- [_unit, _skillLevel, _unitRole] call fnc_botSetSkills;
- };
- // Ensure unit has a smoke grenade for tactical use
- private _hasSmoke = false;
- { if (_x find "SmokeShell" >= 0) exitWith { _hasSmoke = true; }; } forEach (magazines _unit);
- if (!_hasSmoke && vehicle _unit == _unit) then {
- _unit addMagazine "SmokeShell";
- };
- };
- fnc_checkFoliageObstruction = {
- params ["_unit", "_target"];
- private _cacheKey = format ["%1_%2", netId _unit, netId _target];
- private _cachedResult = _unit getVariable ["foliageCache_" + _cacheKey, []];
- if (count _cachedResult > 0 && {time - (_cachedResult select 1) < 2}) exitWith {
- _cachedResult select 0
- };
- private _unitEyePos = eyePos _unit;
- private _targetEyePos = eyePos _target;
- // --- Phase 1: Fast Engine Checks ---
- private _visibility = [_unit, "VIEW", _target] checkVisibility [_unitEyePos, _targetEyePos];
- if (_visibility < 0.2) exitWith {
- _unit setVariable ["foliageCache_" + _cacheKey, [1, time]];
- 1
- };
- private _terrainBlocked = terrainIntersectASL [_unitEyePos, _targetEyePos];
- if (_terrainBlocked) exitWith {
- _unit setVariable ["foliageCache_" + _cacheKey, [1, time]];
- 1
- };
- // --- Phase 2: Detailed (Perf-Scaled) ---
- private _intersectionsCount = 0;
- private _foliageHits = 0;
- private _dist = _unit distance2D _target;
- private _perfScale = 1.0;
- if (!isNil "PERF_STATE") then {
- if (typeName PERF_STATE == "SCALAR") then {
- _perfScale = (PERF_STATE max 1 min 10) / 10;
- };
- };
- private _maxCheckDistance = 150 + 850 * _perfScale;
- if (_dist < _maxCheckDistance) then {
- // OPTIMIZATION: Reduced from 4 points to 2 (Head and Torso/Center)
- // This provides sufficient data for AI decision making while saving performance
- private _targetPositions = [
- _targetEyePos,
- (getPosASL _target) vectorAdd [0, 0, 0.8]
- ];
- // Surfaces: true=all LODs, 3=terrain(1)+objects(2)
- {
- if (count (lineIntersectsSurfaces [_unitEyePos, _x, _unit, _target, true, 3]) > 0) then {
- _intersectionsCount = _intersectionsCount + 1;
- };
- } forEach _targetPositions;
- // Foliage proxy: Reduced from 3 to 2 raymarch samples
- private _dirVec = _targetEyePos vectorDiff _unitEyePos;
- private _stepVec = _dirVec vectorMultiply (1 / 3);
- private _samplePos = _unitEyePos vectorAdd _stepVec;
- for "_s" from 1 to 2 do {
- private _nearFoliage = nearestTerrainObjects [_samplePos, ["BUSH", "TREE", "SMALL TREE"], 2.2, false, true];
- if (count _nearFoliage > 0) then { _foliageHits = _foliageHits + 1; };
- _samplePos = _samplePos vectorAdd _stepVec;
- };
- };
- // --- Phase 3: Score ---
- // Adjusted scoring for reduced sample count
- private _surfacePenalty = switch (_intersectionsCount) do {
- case 0: {0};
- case 1: {0.5}; // Adjusted from 0.4
- default {1.0};
- };
- private _foliagePenalty = if (_foliageHits > 0) then {0.75} else {0};
- private _totalObstruction = (_surfacePenalty max _foliagePenalty) max (1 - _visibility);
- _totalObstruction = _totalObstruction min 1 max 0;
- _unit setVariable ["foliageCache_" + _cacheKey, [_totalObstruction, time]];
- _totalObstruction
- };
- // Predicts enemy future pos using last known velocity (for ambushes/flanks)
- fnc_predictEnemyMove = {
- params ["_unit", "_enemyID"];
- private _sideIntel = if (side _unit == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _report = _sideIntel get _enemyID;
- if (isNil "_report") exitWith {[0,0,0,0]};
- private _lastPos = _report get "position";
- private _vel = _report get "velocity";
- private _lastTime = _report getOrDefault ["lastUpdate", 0];
- private _deltaT = (time - _lastTime) min 15; // Cap at 15s prediction
- private _predictedPos = _lastPos vectorAdd (_vel vectorMultiply _deltaT);
- _predictedPos // [x,y,z]
- };
- // Leader-only: If recent enemy lost LOS, predict move & flank to intercept/cover
- fnc_attemptPredictionAmbush = {
- params ["_unit"];
- private _group = group _unit;
- private _lastAmbushCheck = _group getVariable ["lastAmbushCheck", 0];
- if (time - _lastAmbushCheck < (20 * PERF_MULTIPLIER)) exitWith {}; // Perf cooldown
- _group setVariable ["lastAmbushCheck", time];
- private _sideIntel = if (side _unit == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _recentEnemies = [];
- // Scan recent intel (last 30s) for "lost" enemies (knowsAbout low but fresh)
- {
- private _report = _y;
- private _age = time - (_report get "time");
- private _enemyID = _x;
- if (_age < 30 && {(_group knowsAbout (objectFromNetId _enemyID)) < 1.5}) then {
- _recentEnemies pushBack _enemyID;
- };
- } forEach _sideIntel;
- if (count _recentEnemies == 0) exitWith {};
- private _targetID = selectRandom _recentEnemies;
- private _predictedPos = [_unit, _targetID] call fnc_predictEnemyMove;
- if (count _predictedPos < 3) exitWith {};
- // Find flank intercept: 45deg offset + cover
- private _unitPos = getPosATL _unit;
- private _dirToPred = _unitPos getDir _predictedPos;
- private _flank1 = _predictedPos getPos [50, _dirToPred - 45];
- private _flank2 = _predictedPos getPos [50, _dirToPred + 45];
- private _interceptPos = [_flank1, _flank2] select {
- private _coverScore = ([_x] call fnc_evaluateTerrainCover) get "score";
- _coverScore > 25 // Reuse existing cover eval
- } select 0;
- if (isNil "_interceptPos") then { _interceptPos = _flank1; };
- // Quick safe pos + waypoint (overrides if CQB/evade not active)
- if !(_group getVariable ["isDoingCQB", false]) then {
- _interceptPos = [_interceptPos, 0, 20, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
- while {count (waypoints _group) > 1} do { deleteWaypoint ((waypoints _group) select 0); };
- private _wp = _group addWaypoint [_interceptPos, 30];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- diag_log format ["[BOTAI] %1 predicts & ambushes at %2", groupId _group, _interceptPos];
- };
- };
- // Dynamic morale: Losses → retreat to cover. Recovers after safe time.
- fnc_manageDynamicMorale = {
- params ["_group"];
- private _lastMoraleCheck = _group getVariable ["lastMoraleCheck", 0];
- if (time - _lastMoraleCheck < (25 * PERF_MULTIPLIER)) exitWith {}; // Cooldown
- _group setVariable ["lastMoraleCheck", time];
- private _initialUnits = _group getVariable ["initialGroupStrength", -1];
- if (_initialUnits == -1) then {
- _initialUnits = {alive _x} count units _group;
- _group setVariable ["initialGroupStrength", _initialUnits];
- };
- private _currentUnits = {alive _x && vehicle _x == _x} count units _group;
- if (_initialUnits <= 0) then { _initialUnits = 1; }; // Prevent division by zero
- private _moralePct = (_currentUnits / _initialUnits) max 0;
- // Recover initial if full strength >30s (no recent losses)
- if (_moralePct > 0.7 && time - (_group getVariable ["lastLossTime", 0]) > 30) then {
- _group setVariable ["initialGroupStrength", _currentUnits];
- _group setVariable ["moraleLow", false];
- _group setVariable ["retreatActive", false];
- };
- if (_moralePct < 0.3 && !(_group getVariable ["moraleLow", false])) then {
- _group setVariable ["moraleLow", true];
- _group setVariable ["lastLossTime", time];
- private _leader = leader _group;
- if (isNull _leader) exitWith {}; // Safety check
- // Retreat: Hard cover 100-200m back
- private _retreatPos = [_leader] call fnc_botFindHardCover;
- // Fallback if hard cover not found or invalid
- if (count _retreatPos < 2) then {
- private _retreatDir = (direction _leader) + 180;
- // Calculate offset position
- _retreatPos = (getPosATL _leader) getPos [150 + random 50, _retreatDir];
- // Ensure we have a Z coordinate (2D -> 3D) for consistency
- if (count _retreatPos == 2) then { _retreatPos pushBack 0; };
- };
- // Final sanity check
- if (count _retreatPos < 2) then { _retreatPos = getPosATL _leader; };
- // Execute retreat only if we have a valid position
- if (count _retreatPos >= 2) then {
- while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
- private _wp = _group addWaypoint [_retreatPos, 40];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "FULL";
- _group setCombatMode "GREEN"; // Hold fire while running
- // Self-clear after 60s or safe
- [_group] spawn {
- sleep 60;
- params ["_grp"];
- if (!isNull _grp && {alive leader _grp} && !(_grp getVariable ["moraleLow", false])) then {
- _grp setVariable ["retreatActive", false];
- _grp setCombatMode "YELLOW";
- };
- };
- diag_log format ["[BOTAI] %1 morale broken (%2% left) - RETREAT!", groupId _group, round(_moralePct*100)];
- };
- };
- };
- // NEW: Manages group-level CQB behavior when a close threat is known.
- fnc_manageCQB = {
- params ["_group"];
- // Cooldown check for performance, runs every 3-5 seconds per group.
- if (time < (_group getVariable ["lastCQBCheck", 0]) + (3 + random 2)) exitWith {};
- _group setVariable ["lastCQBCheck", time];
- // Do not run if the group is already executing a temporary CQB task.
- if (_group getVariable ["isDoingCQB", false]) exitWith {};
- private _leader = leader _group;
- if (isNull _leader || !alive _leader) exitWith {};
- // Find the closest enemy that the group already knows about within 100m.
- private _closestEnemy = objNull;
- private _minDist = 101;
- private _nearbyEntities = _leader nearEntities [["CAManBase", "LandVehicle"], 100];
- {
- // Check that the unit is an enemy AND that the group is aware of it.
- if (side _x != side _leader && (_group knowsAbout _x) > 1.5) then {
- private _dist = _leader distance2D _x;
- if (_dist < _minDist) then {
- _minDist = _dist;
- _closestEnemy = _x;
- };
- };
- } forEach _nearbyEntities;
- // If no valid close-quarters threat is found, exit.
- if (isNull _closestEnemy) exitWith {};
- // --- CQB THREAT CONFIRMED ---
- // Immediately set aggressive behavior.
- _group setBehaviour "COMBAT";
- _group setCombatMode "RED";
- // Calculate flanking positions 60m to the left and right of the enemy's position, relative to the group leader.
- private _enemyPos = getPos _closestEnemy;
- private _dirToEnemy = _leader getDir _enemyPos;
- private _flankLeftPos = _enemyPos getPos [60, _dirToEnemy - 90];
- private _flankRightPos = _enemyPos getPos [60, _dirToEnemy + 90];
- // ==================== CHANGE START: BUG FIX ====================
- // The engine expects 5 arguments for selectBestPlaces, not 7.
- // To achieve the same goal, we search for cover around each flanking point separately and combine the results.
- private _leftCover = selectBestPlaces [_flankLeftPos, 40, "trees+houses", 10, 1];
- private _rightCover = selectBestPlaces [_flankRightPos, 40, "trees+houses", 10, 1];
- private _coverPositions = _leftCover + _rightCover;
- // ===================== CHANGE END =====================
- private _targetPos = _enemyPos; // Default to the enemy's position if no good cover is found.
- if (count _coverPositions > 0) then {
- // Select the best found position.
- _targetPos = (_coverPositions select 0) select 0;
- };
- // Immediately override any existing waypoints with the new CQB task.
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Add a single "Search and Destroy" waypoint to the calculated flanking position.
- private _wp = _group addWaypoint [_targetPos, 5];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "COMBAT";
- _wp setWaypointSpeed "FULL"; // Move with urgency.
- _wp setWaypointCompletionRadius 30; // A small radius for a precise maneuver.
- // Set a flag that this group is performing a CQB action. This prevents other logic from overriding it.
- _group setVariable ["isDoingCQB", true];
- // Spawn a script to automatically clear the flag after 45 seconds, allowing the AI commander to issue new orders.
- [_group] spawn {
- params ["_grp"];
- sleep 45;
- if (!isNull _grp) then {
- _grp setVariable ["isDoingCQB", false];
- };
- };
- };
- // Check if a group has anti-tank capability
- fnc_groupHasAT = {
- params ["_group"];
- private _hasAT = false;
- private _units = units _group select {alive _x};
- {
- private _secondaryWeapon = secondaryWeapon _x;
- if (_secondaryWeapon != "") then {
- _hasAT = true;
- break;
- };
- } forEach _units;
- _hasAT
- };
- // Make AI infantry evade from enemy vehicles if they lack AT weapons
- fnc_evadeEnemyVehicles = {
- params ["_unit"];
- if (vehicle _unit != _unit) exitWith {}; // Skip if in vehicle
- // Only process for group leaders to manage group behavior
- private _group = group _unit;
- if (_unit != leader _group) exitWith {};
- // Use a single cooldown for this entire logic block to prevent spamming actions.
- private _lastVehicleResponseCheck = _group getVariable ["lastVehicleResponseCheck", 0];
- if (time < _lastVehicleResponseCheck + (5 + random 3)) exitWith {};
- _group setVariable ["lastVehicleResponseCheck", time];
- // Find the closest enemy vehicle the group knows about within the 100m radius.
- private _closestThreat = objNull;
- private _minDist = 101;
- private _nearbyVehicles = (position _unit) nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F", "Car"], 100];
- private _enemySide = if (side _unit == west) then {east} else {west};
- {
- if (alive _x && side _x == _enemySide && (_group knowsAbout _x) > 1.5) then {
- private _dist = _unit distance2D _x;
- if (_dist < _minDist) then {
- _minDist = _dist;
- _closestThreat = _x;
- };
- };
- } forEach _nearbyVehicles;
- // If no close vehicle threat is found, exit the function.
- if (isNull _closestThreat) exitWith {};
- // If the group already has living AT soldiers, they can handle the threat. Exit.
- if ([_group] call fnc_groupHasAT) exitWith {};
- // --- GROUP HAS NO AT - BEGIN SCAVENGE/EVADE LOGIC ---
- // 1. Attempt to scavenge an AT weapon. This function has its own internal checks.
- [_group] call fnc_bot_scavengeATWeapon;
- // 2. If scavenging was not initiated (no suitable bodies found), then evade.
- // The scavenge function sets "isScavengingAT" to true if it starts a task.
- if !(_group getVariable ["isScavengingAT", false]) then {
- // Do not issue a new evade order if the group is already evading.
- if (_group getVariable ["evadingVehicle", false]) exitWith {};
- // Mark group as evading to prevent this logic from running again immediately.
- _group setVariable ["evadingVehicle", true];
- // Find a safe position away from the vehicle threat.
- private _unitPos = getPosATL _unit;
- private _dirAwayFromVehicle = _closestThreat getDir _unit;
- private _coverPos = [];
- // First, try to find "hard" cover like buildings or walls.
- _coverPos = [_unit] call fnc_botFindHardCover;
- // If no hard cover, find general cover (trees, rocks, terrain).
- if (count _coverPos == 0) then {
- _coverPos = [_unit, 150] call fnc_findNearestCover;
- };
- // As a last resort, just pick a point far away from the vehicle.
- if (count _coverPos == 0) then {
- _coverPos = _unitPos getPos [150, _dirAwayFromVehicle];
- };
- if (count _coverPos > 0 && !surfaceIsWater _coverPos) then {
- // Clear any existing waypoints to prioritize this evasion move.
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Order the group to move to the safe position with urgency.
- private _wp = _group addWaypoint [_coverPos, 20];
- _wp setWaypointType "MOVE";
- _wp setWaypointCompletionRadius 30;
- _wp setWaypointSpeed "FULL"; // Move fast
- _wp setWaypointBehaviour "AWARE";
- // Order all units to go prone to hide upon arrival.
- {_x setUnitPos "DOWN";} forEach (units _group);
- // Spawn a monitor to clear the "evadingVehicle" flag once the group is safe.
- [_group, _coverPos, _closestThreat] spawn {
- params ["_evadingGroup", "_targetCover", "_threatVehicle"];
- private _timeout = time + 45; // Action times out after 45 seconds.
- waitUntil {
- sleep 1;
- // Exit condition: group is gone, reached cover, threat is gone, or timeout.
- isNull _evadingGroup || (leader _evadingGroup distance2D _targetCover < 40) || (!alive _threatVehicle) || (leader _evadingGroup distance2D _threatVehicle > 200) || time > _timeout
- };
- // Once finished, clear the flag and allow AI to return to normal behavior.
- if (!isNull _evadingGroup) then {
- _evadingGroup setVariable ["evadingVehicle", false];
- {_x setUnitPos "AUTO";} forEach (units _evadingGroup);
- };
- };
- };
- };
- };
- // Find nearest cover position for AI unit - OPTIMIZED
- fnc_findNearestCover = {
- params ["_unit", ["_maxDistance", 50]];
- private _unitPos = getPosATL _unit;
- // OPTIMIZATION: Cache cover position for much longer
- private _cachedCover = _unit getVariable ["cachedCoverPos", []];
- private _cachedCoverTime = _unit getVariable ["cachedCoverTime", 0];
- // Use cached position if less than 45 seconds old
- if (time - _cachedCoverTime < 45 && count _cachedCover > 0) exitWith {
- _cachedCover
- };
- private _bestCoverPos = [];
- private _bestCoverScore = -1;
- // OPTIMIZATION: Smaller search radius for terrain objects
- private _searchRadius = 25; // Reduced from 30
- private _terrainObjects = nearestTerrainObjects [_unitPos, ["TREE", "ROCK", "WALL"], _searchRadius, false, true];
- // Only check first 5 objects
- private _checkCount = (count _terrainObjects) min 5;
- for "_i" from 0 to (_checkCount - 1) do {
- private _object = _terrainObjects select _i;
- private _objectPos = getPosATL _object;
- private _distanceToObject = _unitPos distance2D _objectPos;
- if (_distanceToObject > 3 && _distanceToObject < _searchRadius) then {
- // Simplified scoring
- private _coverScore = 10 / _distanceToObject;
- if (_coverScore > _bestCoverScore) then {
- private _dirToObject = [_unitPos, _objectPos] call BIS_fnc_dirTo;
- private _coverPos = _objectPos getPos [2, _dirToObject + 180];
- if (!surfaceIsWater _coverPos) then {
- _bestCoverScore = _coverScore;
- _bestCoverPos = _coverPos;
- };
- };
- };
- };
- // OPTIMIZATION: Simplified building check - only check very close buildings
- if (_bestCoverScore < 3) then {
- private _nearBuildings = _unitPos nearObjects ["House", 20]; // Reduced from 25
- // Only check first 3 buildings
- private _buildingCount = (count _nearBuildings) min 3;
- for "_i" from 0 to (_buildingCount - 1) do {
- private _building = _nearBuildings select _i;
- private _buildingPos = getPosATL _building;
- private _distanceToBuilding = _unitPos distance2D _buildingPos;
- if (_distanceToBuilding > 5) then {
- private _coverScore = 15 / _distanceToBuilding;
- if (_coverScore > _bestCoverScore) then {
- _bestCoverScore = _coverScore;
- _bestCoverPos = _buildingPos;
- };
- };
- };
- };
- // OPTIMIZATION: Simplified terrain check - only 4 directions
- if (count _bestCoverPos == 0) then {
- for "_angle" from 0 to 270 step 90 do {
- private _testPos = _unitPos getPos [15, _angle];
- private _heightDiff = (getTerrainHeightASL _unitPos) - (getTerrainHeightASL _testPos);
- if (_heightDiff > 1 && !surfaceIsWater _testPos) then {
- if (_heightDiff > _bestCoverScore) then {
- _bestCoverScore = _heightDiff;
- _bestCoverPos = _testPos;
- };
- };
- };
- };
- // Cache result for longer
- _unit setVariable ["cachedCoverPos", _bestCoverPos];
- _unit setVariable ["cachedCoverTime", time];
- _bestCoverPos
- };
- // Move AI unit to tactical position when under fire - OPTIMIZED
- fnc_moveToTacticalPosition = {
- params ["_unit"];
- if (!alive _unit || vehicle _unit != _unit) exitWith {};
- private _lastRepositionTime = _unit getVariable ["lastRepositionTime", 0];
- // ==================== CHANGE START ====================
- // OPTIMIZATION: Increased minimum interval and tied to performance scaling
- if (time - _lastRepositionTime < (15 * PERF_COOLDOWN_MULTIPLIER)) exitWith {};
- // ===================== CHANGE END =====================
- private _suppression = getSuppression _unit;
- private _damage = damage _unit;
- private _isUnderFire = (time - (_unit getVariable ["lastShotAtUnit_self", 0])) < 5;
- // Check if unit needs to reposition
- private _needsReposition = _suppression > 0.3 || _damage > 0.2 || _isUnderFire;
- if (_needsReposition) then {
- private _cachedCover = _unit getVariable ["cachedCoverPos", []];
- private _cachedCoverTime = _unit getVariable ["cachedCoverTime", 0];
- private _coverPos = [];
- // Use cached position if less than 30 seconds old
- if (time - _cachedCoverTime < 30 && count _cachedCover > 0) then {
- _coverPos = _cachedCover;
- } else {
- _coverPos = [_unit, 40] call fnc_findNearestCover;
- _unit setVariable ["cachedCoverPos", _coverPos];
- _unit setVariable ["cachedCoverTime", time];
- };
- if (count _coverPos > 0) then {
- _unit doMove _coverPos;
- _unit setSpeedMode "FULL";
- if (_suppression > 0.6 || _damage > 0.5) then {
- _unit setUnitPos "DOWN";
- } else {
- _unit setUnitPos "MIDDLE";
- };
- _unit setVariable ["lastRepositionTime", time];
- _unit setVariable ["movingToCover", true];
- _unit setVariable ["coverPosition", _coverPos];
- [_unit, _coverPos] spawn {
- params ["_unit", "_coverPos"];
- waitUntil {sleep 1; !alive _unit || _unit distance2D _coverPos < 5 || time > ((_unit getVariable ["lastRepositionTime", 0]) + 15)};
- if (alive _unit) then {
- _unit setVariable ["movingToCover", false];
- sleep (5 + random 5);
- if (getSuppression _unit < 0.2 && damage _unit < 0.3) then {
- _unit setUnitPos "AUTO";
- };
- };
- };
- };
- };
- };
- // Manage AI combat stance based on situation
- fnc_manageCombatStance = {
- params ["_unit"];
- if (!alive _unit || vehicle _unit != _unit) exitWith {};
- private _suppression = getSuppression _unit;
- private _damage = damage _unit;
- // ==================== CHANGE START ====================
- private _nearEnemies = _unit nearEntities [["CAManBase", "LandVehicle"], (100 * PERF_SEARCH_RADIUS_MULTIPLIER)];
- // ===================== CHANGE END =====================
- private _enemyCount = {side _x != side _unit && alive _x} count _nearEnemies;
- private _isMovingToCover = _unit getVariable ["movingToCover", false];
- if (_isMovingToCover) exitWith {};
- if (_suppression > 0.7 || _damage > 0.6) then {
- _unit setUnitPos "DOWN";
- } else {
- if (_suppression > 0.3 || _enemyCount > 3) then {
- _unit setUnitPos "MIDDLE";
- } else {
- if (_enemyCount > 0 && _enemyCount <= 3) then {
- if (random 1 > 0.3) then {
- _unit setUnitPos "MIDDLE";
- } else {
- _unit setUnitPos "AUTO";
- };
- } else {
- _unit setUnitPos "AUTO";
- };
- };
- };
- if (_enemyCount > 0) then {
- if (_suppression > 0.5) then {
- _unit setBehaviour "SAFE";
- _unit setSpeedMode "FULL";
- } else {
- _unit setBehaviour "COMBAT";
- _unit setSpeedMode "NORMAL";
- };
- } else {
- if (behaviour _unit != "STEALTH") then {
- _unit setBehaviour "AWARE";
- };
- };
- };
- // Assign flank watch sectors to group members
- fnc_assignFlankWatchSectors = {
- params ["_group"];
- if (isNull _group || count (units _group) == 0) exitWith {};
- private _leader = leader _group;
- if (isNull _leader || !alive _leader) exitWith {};
- private _groupUnits = units _group select {alive _x && vehicle _x == _x};
- private _unitCount = count _groupUnits;
- if (_unitCount == 0) exitWith {};
- private _leaderDir = direction _leader;
- private _currentWP = currentWaypoint _group;
- if (_currentWP > 0) then {
- private _wpPos = waypointPosition [_group, _currentWP];
- if (count _wpPos > 0 && !(_wpPos isEqualTo [0,0,0])) then {
- _leaderDir = _leader getDir _wpPos;
- };
- };
- if (_unitCount == 1) then {
- _groupUnits select 0 setVariable ["watchSector", _leaderDir];
- _groupUnits select 0 setVariable ["sectorWidth", 180];
- } else {
- if (_unitCount == 2) then {
- _groupUnits select 0 setVariable ["watchSector", _leaderDir];
- _groupUnits select 0 setVariable ["sectorWidth", 180];
- _groupUnits select 1 setVariable ["watchSector", _leaderDir + 180];
- _groupUnits select 1 setVariable ["sectorWidth", 180];
- } else {
- if (_unitCount == 3) then {
- _groupUnits select 0 setVariable ["watchSector", _leaderDir];
- _groupUnits select 0 setVariable ["sectorWidth", 120];
- _groupUnits select 1 setVariable ["watchSector", _leaderDir - 90];
- _groupUnits select 1 setVariable ["sectorWidth", 120];
- _groupUnits select 2 setVariable ["watchSector", _leaderDir + 90];
- _groupUnits select 2 setVariable ["sectorWidth", 120];
- } else {
- private _sectorSize = 360 / _unitCount;
- for "_i" from 0 to (_unitCount - 1) do {
- private _unit = _groupUnits select _i;
- private _sectorDir = _leaderDir + (_i * _sectorSize);
- _unit setVariable ["watchSector", _sectorDir];
- _unit setVariable ["sectorWidth", _sectorSize + 20];
- };
- };
- };
- };
- _group setVariable ["sectorsAssigned", true];
- _group setVariable ["sectorAssignmentTime", time];
- };
- // Make AI unit scan their assigned sector
- fnc_scanAssignedSector = {
- params ["_unit"];
- if (!alive _unit || vehicle _unit != _unit) exitWith {};
- if (behaviour _unit == "COMBAT") exitWith {};
- private _assignedSector = _unit getVariable ["watchSector", -1];
- private _sectorWidth = _unit getVariable ["sectorWidth", 90];
- if (_assignedSector < 0) exitWith {};
- private _lastScanTime = _unit getVariable ["lastSectorScan", 0];
- if (time - _lastScanTime > (3 + random 3)) then {
- private _scanDir = _assignedSector + (random _sectorWidth - (_sectorWidth / 2));
- private _unitPos = getPosATL _unit;
- private _lookAtPos = _unitPos getPos [50, _scanDir];
- _unit doWatch _lookAtPos;
- _unit setVariable ["lastSectorScan", time];
- [_unit] spawn {
- params ["_unit"];
- sleep (2 + random 2);
- if (alive _unit && behaviour _unit != "COMBAT") then {
- _unit doWatch objNull;
- };
- };
- };
- };
- // Maintain tactical spacing between group members - OPTIMIZED
- fnc_maintainTacticalSpacing = {
- params ["_group"];
- if (isNull _group || count (units _group) == 0) exitWith {};
- private _leader = leader _group;
- if (isNull _leader || !alive _leader) exitWith {};
- private _groupUnits = units _group select {alive _x && vehicle _x == _x && _x != _leader};
- if (count _groupUnits == 0) exitWith {};
- private _leaderPos = getPosATL _leader;
- private _optimalSpacing = 5;
- private _maxSpacing = 15;
- {
- private _unit = _x;
- private _unitPos = getPosATL _unit;
- private _distToLeader = _unitPos distance2D _leaderPos;
- // Early exit if already well-positioned
- if (_distToLeader > _optimalSpacing && _distToLeader < _maxSpacing) then {
- continue;
- };
- // ==================== CHANGE START ====================
- // OPTIMIZATION: Replaced nested loop with a single, fast nearEntities check.
- private _tooClose = count (_unit nearEntities ["CAManBase", _optimalSpacing]) > 1; // >1 to not count self
- if (_tooClose && !(_unit getVariable ["movingToCover", false])) then {
- // ===================== CHANGE END =====================
- private _lastSpacingAdjust = _unit getVariable ["lastSpacingAdjust", 0];
- if (time - _lastSpacingAdjust > 8) then {
- private _repositionDir = _leaderPos getDir _unitPos;
- private _repositionPos = _leaderPos getPos [_optimalSpacing + random 3, _repositionDir + (random 40 - 20)];
- _repositionPos = [_repositionPos, 0, 5, 2, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _repositionPos > 0 && !surfaceIsWater _repositionPos) then {
- _unit doMove _repositionPos;
- _unit setVariable ["lastSpacingAdjust", time];
- };
- };
- };
- if (_distToLeader > _maxSpacing && behaviour _unit != "COMBAT") then {
- private _lastSpacingAdjust = _unit getVariable ["lastSpacingAdjust", 0];
- if (time - _lastSpacingAdjust > 10) then {
- _unit doFollow _leader;
- _unit setVariable ["lastSpacingAdjust", time];
- };
- };
- } forEach _groupUnits;
- };
- // NEW: Finds the nearest "hard" cover, prioritizing buildings and walls.
- fnc_botFindHardCover = {
- params ["_unit"];
- private _unitPos = getPosATL _unit;
- private _bestCoverPos = [];
- private _maxScanRadius = 75;
- // Search for nearest buildings first
- private _nearBuildings = nearestObjects [_unitPos, ["House"], _maxScanRadius];
- if (count _nearBuildings > 0) then {
- private _closestBuilding = objNull;
- private _minDist = 9999;
- {
- private _dist = _unit distance _x;
- // FIXED: nearObjects [_x, 1] -> distance2D _x > 3 (avoids current building)
- if (_dist < _minDist && (_unit distance2D _x > 3)) then {
- _minDist = _dist;
- _closestBuilding = _x;
- };
- } forEach _nearBuildings;
- if (!isNull _closestBuilding) then {
- // FIXED: buildingPos -1 returns an array of ALL positions. We must select ONE.
- private _buildingPositions = _closestBuilding buildingPos -1;
- if (count _buildingPositions > 0) then {
- _bestCoverPos = selectRandom _buildingPositions;
- } else {
- // If building has no indexed positions, use its center
- _bestCoverPos = getPosATL _closestBuilding;
- };
- };
- };
- // If no building, look for walls
- if (count _bestCoverPos == 0) then {
- private _nearWalls = nearestTerrainObjects [_unitPos, ["WALL"], _maxScanRadius / 2];
- if (count _nearWalls > 0) then {
- private _closestWall = _nearWalls select 0;
- private _wallPos = getPosATL _closestWall;
- private _dirToWall = [_unitPos, _wallPos] call BIS_fnc_dirTo;
- _bestCoverPos = _wallPos getPos [2, _dirToWall + 180];
- };
- };
- _bestCoverPos
- };
- // NEW: Make infantry units seek cover and heal when damaged
- fnc_botSeekCoverAndHeal = {
- params ["_unit"];
- // Only for infantry units
- if (vehicle _unit != _unit) exitWith {};
- // Check damage threshold (20%)
- private _damage = damage _unit;
- if (_damage <= 0.2) exitWith {};
- // Check if unit has a FirstAidKit
- if (!("FirstAidKit" in items _unit)) exitWith {};
- // Check cooldown to prevent constant healing attempts
- private _lastHealAttempt = _unit getVariable ["lastHealAttempt", 0];
- if (time - _lastHealAttempt < 30) exitWith {}; // 30 second cooldown
- // Check if already healing
- if (_unit getVariable ["isHealing", false]) exitWith {};
- _unit setVariable ["lastHealAttempt", time];
- _unit setVariable ["isHealing", true];
- private _unitPos = getPosATL _unit;
- private _coverFound = false;
- private _coverPos = [];
- // Search for hard cover within 20 meters
- private _nearBuildings = nearestObjects [_unitPos, ["House", "Building"], 20];
- private _nearWalls = nearestTerrainObjects [_unitPos, ["WALL"], 20];
- // Try to find building cover first
- if (count _nearBuildings > 0) then {
- // Find closest building that's not the one we're already in
- {
- private _building = _x;
- private _distance = _unit distance _building;
- if (_distance > 2 && _distance <= 20) then {
- // Try to get a building position
- private _buildingPos = _building buildingPos 0;
- if (count _buildingPos > 0) then {
- _coverPos = _buildingPos;
- _coverFound = true;
- } else {
- // If no interior position, use position behind building
- _coverPos = getPosATL _building;
- _coverFound = true;
- };
- };
- if (_coverFound) exitWith {};
- } forEach _nearBuildings;
- };
- // If no building, try walls
- if (!_coverFound && count _nearWalls > 0) then {
- private _closestWall = _nearWalls select 0;
- if (_unit distance _closestWall <= 20) then {
- private _wallPos = getPosATL _closestWall;
- // Position behind wall relative to unit's current position
- private _dirToWall = _unitPos getDir _wallPos;
- _coverPos = _wallPos getPos [2, _dirToWall + 180];
- _coverFound = true;
- };
- };
- // Execute healing behavior
- if (_coverFound) then {
- // Move to cover and heal
- [_unit, _coverPos] spawn {
- params ["_unit", "_coverPos"];
- _unit doMove _coverPos;
- _unit setSpeedMode "FULL";
- // Wait until unit reaches cover or times out
- private _timeout = time + 15;
- waitUntil {
- sleep 0.5;
- !alive _unit ||
- (_unit distance2D _coverPos) < 3 ||
- time > _timeout
- };
- if (alive _unit && "FirstAidKit" in items _unit) then {
- // Go prone and heal
- _unit setUnitPos "DOWN";
- sleep 1;
- _unit action ["HealSoldierSelf", _unit];
- _unit removeItem "FirstAidKit";
- // Stay in cover for a few seconds
- sleep 5;
- _unit setUnitPos "AUTO";
- };
- _unit setVariable ["isHealing", false];
- };
- } else {
- // No cover found - go prone and heal at current position
- [_unit] spawn {
- params ["_unit"];
- if (alive _unit && "FirstAidKit" in items _unit) then {
- _unit setUnitPos "DOWN";
- sleep 1;
- _unit action ["HealSoldierSelf", _unit];
- _unit removeItem "FirstAidKit";
- // Stay prone for a few seconds
- sleep 5;
- _unit setUnitPos "AUTO";
- };
- _unit setVariable ["isHealing", false];
- };
- };
- };
- // NEW: Prevents AI units from clipping/sticking inside each other by applying a gentle push.
- fnc_resolveClipping = {
- params ["_unit"];
- if (vehicle _unit != _unit) exitWith {}; // Only for infantry
- // Check for other infantry units extremely close (0.6m)
- private _nearby = _unit nearEntities ["CAManBase", 0.6];
- if (count _nearby > 1) then {
- // Find a neighbor that isn't self
- private _neighborIndex = _nearby findIf {_x != _unit};
- if (_neighborIndex != -1) then {
- private _neighbor = _nearby select _neighborIndex;
- // Calculate direction away from the neighbor
- private _dir = _neighbor getDir _unit;
- // Apply a small velocity push away from the neighbor
- // This uses the physics engine to separate them smoothly rather than teleporting
- private _pushSpeed = 1.5;
- private _currentVel = velocity _unit;
- _unit setVelocity [
- (_currentVel select 0) + (sin _dir * _pushSpeed),
- (_currentVel select 1) + (cos _dir * _pushSpeed),
- _currentVel select 2
- ];
- };
- };
- };
- // NEW: Makes AI squads attempt to scavenge an AT launcher from a fallen teammate if a vehicle threat is present.
- fnc_bot_scavengeATWeapon = {
- params ["_group"];
- // Cooldown: Only check every 10-15 seconds per group
- if (time < (_group getVariable ["lastATScavengeCheck", 0]) + (10 + random 5)) exitWith {};
- _group setVariable ["lastATScavengeCheck", time];
- // Exit if another scavenger is already tasked for this group
- if (_group getVariable ["isScavengingAT", false]) exitWith {};
- private _leader = leader _group;
- if (isNull _leader || !alive _leader) exitWith {};
- // 1. Find the primary vehicle threat
- private _enemySide = if (side _group == west) then {east} else {west};
- private _threats = _leader nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F", "Car"], 400];
- private _targetVehicle = objNull;
- {
- if (side _x == _enemySide && alive _x && count (crew _x) > 0) exitWith {
- _targetVehicle = _x;
- };
- } forEach _threats;
- // If no vehicle threat, exit
- if (isNull _targetVehicle) exitWith {};
- // 2. Check for living AT soldiers in the group
- private _livingUnits = units _group select {alive _x && vehicle _x == _x};
- private _hasLivingAT = false;
- {
- if (secondaryWeapon _x != "") exitWith {
- _hasLivingAT = true;
- };
- } forEach _livingUnits;
- if (_hasLivingAT) exitWith {}; // Group already has AT capability
- // 3. Search for nearby dead bodies with AT weapons
- // FIXED: Used 'allDeadMen' instead of 'nearEntities' because nearEntities only finds alive units.
- private _leaderPos = getPos _leader;
- private _nearbyBodies = allDeadMen select {
- (_x distance _leaderPos < 100) &&
- (secondaryWeapon _x != "")
- };
- if (count _nearbyBodies == 0) exitWith {};
- // Find the closest body with a launcher and ammo
- private _deadBody = objNull;
- private _launcherClass = "";
- private _closestDist = 9999;
- {
- private _launcher = secondaryWeapon _x;
- if (_launcher != "") then {
- // Check if body has launcher ammo
- private _hasAmmo = false;
- private _launcherMags = getArray (configFile >> "CfgWeapons" >> _launcher >> "magazines");
- {
- if (_x in _launcherMags) exitWith {
- _hasAmmo = true;
- };
- } forEach (magazines _x);
- if (_hasAmmo) then {
- private _dist = _leader distance2D _x;
- if (_dist < _closestDist) then {
- _closestDist = _dist;
- _deadBody = _x;
- _launcherClass = _launcher;
- };
- };
- };
- } forEach _nearbyBodies;
- // If no suitable body found, exit
- if (isNull _deadBody || _launcherClass == "") exitWith {};
- // 4. Find the closest living non-AT soldier to be the scavenger
- private _scavenger = objNull;
- private _minDist = 9999;
- {
- if (secondaryWeapon _x == "" && !(_x getVariable ["isScavenger", false])) then {
- private _dist = _x distance2D _deadBody;
- if (_dist < _minDist) then {
- _minDist = _dist;
- _scavenger = _x;
- };
- };
- } forEach _livingUnits;
- // If we found a valid scavenger, task them
- if (!isNull _scavenger) then {
- _group setVariable ["isScavengingAT", true, true];
- _scavenger setVariable ["isScavenger", true, true];
- // Spawn the scavenge process
- [_scavenger, _deadBody, _launcherClass, _targetVehicle, _group] spawn {
- params ["_scavenger", "_deadBody", "_launcherClass", "_targetVehicle", "_group"];
- // Disable auto combat to prevent AI getting distracted
- _scavenger disableAI "AUTOCOMBAT";
- _scavenger setSpeedMode "FULL";
- _scavenger doMove (getPos _deadBody);
- // Wait until close to body or timeout
- private _timeout = time + 30;
- waitUntil {
- sleep 0.5;
- !alive _scavenger ||
- isNull _deadBody ||
- (_scavenger distance2D _deadBody < 3) ||
- time > _timeout
- };
- if (alive _scavenger && !isNull _deadBody && (_scavenger distance2D _deadBody < 3)) then {
- // Play grab animation
- _scavenger playActionNow "PutDown";
- sleep 1;
- // Get compatible magazines from body
- private _launcherMags = getArray (configFile >> "CfgWeapons" >> _launcherClass >> "magazines");
- private _ammoToTake = magazines _deadBody select {_x in _launcherMags};
- // Transfer weapon and ammo
- _scavenger addWeapon _launcherClass;
- {
- _scavenger addMagazine _x;
- } forEach _ammoToTake;
- // Re-enable combat and engage
- _scavenger enableAI "AUTOCOMBAT";
- sleep 0.5;
- if (alive _scavenger && !isNull _targetVehicle) then {
- _scavenger selectWeapon (secondaryWeapon _scavenger);
- _scavenger setBehaviour "COMBAT";
- _scavenger setUnitPos "MIDDLE"; // Kneel to shoot
- (group _scavenger) reveal [_targetVehicle, 4];
- _scavenger doTarget _targetVehicle;
- _scavenger doFire _targetVehicle;
- };
- };
- // Clean up flags
- if (alive _scavenger) then {
- _scavenger enableAI "AUTOCOMBAT";
- _scavenger setVariable ["isScavenger", false, true];
- };
- if (!isNull _group) then {
- _group setVariable ["isScavengingAT", false, true];
- };
- };
- };
- };
- // NEW: Core function to make units react to suppression in the open and to friendly casualties.
- fnc_botReactToThreats = {
- params ["_unit"];
- if (vehicle _unit != _unit) exitWith {}; // Exit if in vehicle.
- // --- Suppression Response ---
- private _isUnderFire = (getSuppression _unit > 0.4) || (time - (_unit getVariable ["lastShotAtUnit_self", 0])) < 3;
- private _isInCombat = behaviour _unit == "COMBAT";
- private _isTakingCover = time - (_unit getVariable ["BOTAI_takingCoverTime", 0]) < 20; // 20 second cooldown
- if (_isUnderFire && !_isInCombat && !_isTakingCover) then {
- // Check if unit is in the open (more than 15m from any cover object).
- private _coverObjects = nearestTerrainObjects [getPosATL _unit, ["TREE", "ROCK", "WALL", "BUSH"], 15];
- private _nearBuildings = nearestObjects [getPosATL _unit, ["House"], 15];
- if (count _coverObjects == 0 && count _nearBuildings == 0) then {
- // Unit is in the open, find hard cover.
- private _coverPos = [_unit] call fnc_botFindHardCover;
- if (count _coverPos > 0) then {
- _unit setVariable ["BOTAI_takingCoverTime", time]; // Set cooldown.
- _unit setSpeedMode "FULL"; // Sprint!
- _unit doMove _coverPos;
- // Once in cover, hold position and wait for leader.
- [_unit, _coverPos] spawn {
- params ["_unit", "_pos"];
- waitUntil {sleep 1; !alive _unit || _unit distance _pos < 5};
- if (alive _unit) then {
- // Clear waypoints to make the unit hold. The main HC loop will re-task them eventually.
- while {count (waypoints (group _unit)) > 0} do {
- deleteWaypoint ((waypoints (group _unit)) select 0);
- };
- _unit setSpeedMode "NORMAL";
- };
- };
- };
- };
- };
- // --- Casualty Awareness ---
- private _lastBodyCheck = _unit getVariable ["BOTAI_lastBodyCheck", 0];
- if (time - _lastBodyCheck > 10) then { // Check every 10 seconds.
- _unit setVariable ["BOTAI_lastBodyCheck", time];
- private _friendlyBodies = (getPosATL _unit) nearEntities ["CAManBase", 40] select {
- side _x == side _unit && !alive _x
- };
- private _bodyCount = count _friendlyBodies;
- if (_bodyCount > 0) then {
- // If any friendly body is nearby, go to max alert.
- if (behaviour _unit != "COMBAT") then { _unit setBehaviour "COMBAT"; };
- if (combatMode _unit != "RED") then { _unit setCombatMode "RED"; };
- // If more than one body is found, try to move away from the cluster.
- if (_bodyCount > 1) then {
- private _isAvoiding = time - (_unit getVariable ["BOTAI_avoidingBodiesTime", 0]) < 60;
- if (!_isAvoiding) then {
- _unit setVariable ["BOTAI_avoidingBodiesTime", time]; // Set cooldown.
- // Find center of the bodies.
- private _avgX = 0; private _avgY = 0;
- { _avgX = _avgX + ((getPos _x) select 0); _avgY = _avgY + ((getPos _x) select 1); } forEach _friendlyBodies;
- private _bodyCenter = [_avgX / _bodyCount, _avgY / _bodyCount, 0];
- // Find a new position 50-75m away from the danger zone.
- private _dirAway = [_bodyCenter, getPosATL _unit] call BIS_fnc_dirTo;
- private _newPos = (getPosATL _unit) getPos [50 + (random 25), _dirAway];
- // Order a move to the safer position.
- _unit doMove _newPos;
- };
- };
- };
- };
- };
- // Main server-side AI loop - REWORKED WITH NEW PERFORMANCE SCALING
- if (isServer) then {
- [] spawn {
- waitUntil { time > 20 };
- private _unitIndex = 0;
- private _allAiUnits = [];
- private _lastUnitRefresh = 0;
- while {true} do {
- private _cycleStartTime = diag_tickTime;
- // Refresh unit list less frequently
- if (time > _lastUnitRefresh + 45 || count _allAiUnits == 0) then {
- _allAiUnits = allUnits select {
- !isNull _x && alive _x && !isPlayer _x && side _x in [west, east]
- };
- _lastUnitRefresh = time;
- };
- // This now uses the definitive batch size from performance.sqf
- private _batchSize = (PERF_AI_BATCH_SIZE min (count _allAiUnits));
- if (_batchSize == 0) then { _batchSize = 1; };
- private _endIndex = (_unitIndex + _batchSize) min (count _allAiUnits);
- // This frame-time budget prevents a single AI cycle from causing a major lag spike.
- private _frameTimeBudget = 0.030; // 30ms max per cycle
- for "_i" from _unitIndex to (_endIndex - 1) do {
- if ((diag_tickTime - _cycleStartTime) > _frameTimeBudget) exitWith {
- diag_log format ["[BOTAI] Frame budget exceeded at unit %1/%2", _i - _unitIndex, _batchSize];
- };
- private _unit = _allAiUnits select _i;
- if (!isNull _unit && alive _unit) then {
- private _vehicle = vehicle _unit;
- private _isInfantry = (_vehicle == _unit);
- private _enemySide = if (side _unit == west) then {east} else {west};
- private _unitPos = getPos _unit;
- if (_isInfantry) then {
- // NEW: Resolve clipping for infantry units
- [_unit] call fnc_resolveClipping;
- private _group = group _unit;
- // REWORKED BINOCULAR FIX: Prevents AI from being stuck with binoculars, but allows AT soldiers to aim their launchers.
- if (currentWeapon _unit isKindOf "Binocular" && behaviour _unit == "COMBAT") then {
- private _isPreparingToFireLauncher = false;
- // Check if the AI has a launcher and a valid vehicle target nearby.
- if (secondaryWeapon _unit != "") then {
- // Use findIf for efficiency; it stops once a target is found.
- private _foundTarget = (_unit nearEntities [["Tank", "Armored"], (500 * PERF_SEARCH_RADIUS_MULTIPLIER)]) findIf {
- side _x == _enemySide && alive _x && _unit knowsAbout _x > 1.5
- };
- // If a valid target was found (findIf returns index >= 0), then the AI is likely aiming its launcher.
- if (_foundTarget != -1) then {
- _isPreparingToFireLauncher = true;
- };
- };
- // If the AI is NOT preparing to fire a launcher, THEN it's safe to force a switch to the primary rifle.
- if (!_isPreparingToFireLauncher) then {
- _unit selectWeapon (primaryWeapon _unit);
- };
- };
- // NEW: CQB Management. Call only for the leader to manage the whole group's tactical response.
- if (_unit == leader _group) then {
- [_group] call fnc_manageCQB;
- };
- // If the group is busy with a CQB maneuver, skip other movement/behavior logic for this unit.
- if (_group getVariable ["isDoingCQB", false]) then { continue; };
- // NEW: High-priority reactions to immediate threats.
- [_unit] call fnc_botReactToThreats;
- // Do not process other behaviors if the unit is busy taking cover.
- if (time - (_unit getVariable ["BOTAI_takingCoverTime", 0]) < 20) then { continue; };
- // NEW POLISH: Morale & Prediction (leaders only, perf-safe)
- if (_unit == leader _group) then {
- [_group] call fnc_manageDynamicMorale;
- [_unit] call fnc_attemptPredictionAmbush;
- };
- // NEW: Vehicle evasion for infantry without AT (scales with performance)
- [_unit] call fnc_evadeEnemyVehicles;
- private _unitBehaviour = behaviour _unit;
- // CRITICAL: Combat behaviors are always processed.
- if (_unitBehaviour == "COMBAT") then {
- [_unit] call fnc_moveToTacticalPosition;
- [_unit] call fnc_manageCombatStance;
- };
- // NEW: Check if the group needs to scavenge an AT weapon.
- if (_unit == leader _group) then {
- [_group] call fnc_bot_scavengeATWeapon;
- };
- // HIGH PRIORITY: Check if unit needs to heal
- if (damage _unit > 0.2 && !(_unit getVariable ["isHealing", false])) then {
- [_unit] call fnc_botSeekCoverAndHeal;
- };
- if (!isNull _group && _unitBehaviour in ["AWARE", "SAFE"]) then
- {
- // Sector assignment is infrequent (every 60s)
- if (time > ((_group getVariable ["sectorAssignmentTime", 0]) + (60 * PERF_COOLDOWN_MULTIPLIER))) then {
- [_group] call fnc_assignFlankWatchSectors;
- };
- // Scanning is staggered between units
- if (_i % 2 == 0) then {
- [_unit] call fnc_scanAssignedSector;
- };
- // Spacing is infrequent and only for leaders
- if (_unit == leader _group && (time - (_group getVariable ["lastSpacingCheck", 0])) > 20) then {
- _group setVariable ["lastSpacingCheck", time];
- [_group] call fnc_maintainTacticalSpacing;
- };
- };
- private _lastVehicleAvoidCheck = _unit getVariable ["lastVehicleAvoidCheck", 0];
- if (time > _lastVehicleAvoidCheck + (8 * PERF_COOLDOWN_MULTIPLIER)) then {
- _unit setVariable ["lastVehicleAvoidCheck", time];
- private _nearbyVehicles = _unit nearEntities ["LandVehicle", 10];
- if (count _nearbyVehicles > 0) then {
- private _friendlyVehicle = _nearbyVehicles findIf {side _x == side _unit};
- if (_friendlyVehicle != -1) then {
- private _closestVehicle = _nearbyVehicles select _friendlyVehicle;
- private _dirFromVehicle = _closestVehicle getDir _unit;
- _unit doMove (_unit getPos [5, _dirFromVehicle]);
- };
- };
- };
- // ==================== AT ENGAGEMENT START ====================
- if (secondaryWeapon _unit != "") then {
- private _lastATCheck = _unit getVariable ["lastATCheck", 0];
- if (time > _lastATCheck + (2 * PERF_COOLDOWN_MULTIPLIER)) then {
- _unit setVariable ["lastATCheck", time];
- // Search for ALL vehicle types, not just Land/Air
- private _nearVehicles = _unit nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F", "Car", "Helicopter"], (500 * PERF_SEARCH_RADIUS_MULTIPLIER)];
- // Find closest enemy vehicle
- private _closestVehicle = objNull;
- private _closestDist = 9999;
- {
- if (side _x == _enemySide && alive _x && count (crew _x) > 0) then {
- private _dist = _unit distance2D _x;
- if (_dist < _closestDist) then {
- _closestDist = _dist;
- _closestVehicle = _x;
- };
- };
- } forEach _nearVehicles;
- if (!isNull _closestVehicle) then {
- // Force immediate reveal
- (group _unit) reveal [_closestVehicle, 4];
- // Check obstruction
- private _obstructionLevel = [_unit, _closestVehicle] call fnc_checkFoliageObstruction;
- // Only engage if obstruction is low enough
- if (_obstructionLevel < 0.5) then {
- // Clear shot - aggressive engagement
- _unit setBehaviour "COMBAT";
- _unit setUnitPos "MIDDLE";
- _unit selectWeapon (secondaryWeapon _unit);
- _unit doTarget _closestVehicle;
- _unit doFire _closestVehicle;
- (group _unit) setVariable ["AICanFire", true];
- // Keep launcher ready flag
- _unit setVariable ["keepLauncherReady", time];
- } else {
- // Too much obstruction - hold fire and try to find better position
- (group _unit) setVariable ["AICanFire", false];
- _unit doWatch _closestVehicle;
- private _betterPos = [_unit, 30] call fnc_findNearestCover;
- if (count _betterPos > 0) then {
- _unit doMove _betterPos;
- _unit setSpeedMode "FULL";
- };
- (group _unit) reveal [_closestVehicle, 2];
- };
- } else {
- // No vehicle threat - can switch back to primary if no recent engagement
- private _keepReady = _unit getVariable ["keepLauncherReady", 0];
- if (time - _keepReady > 10 && currentWeapon _unit == secondaryWeapon _unit) then {
- _unit selectWeapon (primaryWeapon _unit);
- };
- };
- };
- };
- // ===================== AT ENGAGEMENT END =====================
- private _lastCombinedCheck = _unit getVariable ["lastCombinedCheck", 0];
- if (time > _lastCombinedCheck + (6 * PERF_COOLDOWN_MULTIPLIER)) then {
- _unit setVariable ["lastCombinedCheck", time];
- private _nearTargets = _unit nearTargets (120 * PERF_SEARCH_RADIUS_MULTIPLIER);
- private _targetCount = (count _nearTargets) min 5;
- for "_t" from 0 to (_targetCount - 1) do {
- private _targetData = _nearTargets select _t;
- private _target = _targetData select 4;
- if (alive _target && side _target == _enemySide && _target isKindOf "CAManBase") then {
- private _distance = _unit distance _target;
- if (_distance < (120 * PERF_SEARCH_RADIUS_MULTIPLIER) && _unit knowsAbout _target > 0) then {
- private _obstructionLevel = [_unit, _target] call fnc_checkFoliageObstruction;
- private _isBeingShotAt = (time - (_target getVariable ["lastShotAtUnit_" + str _unit, 0])) < 10;
- if (_obstructionLevel > 0.6) then {
- // Heavy obstruction - very low reveal, hold fire
- (group _unit) reveal [_target, 1.0];
- (group _unit) setVariable ["AICanFire", false];
- _unit doWatch objNull;
- } else {
- if (_obstructionLevel > 0.3) then {
- // Moderate obstruction
- if (_isBeingShotAt) then {
- // Under fire - engage anyway with caution
- (group _unit) reveal [_target, 3.5];
- (group _unit) setVariable ["AICanFire", true];
- } else {
- // Not under fire - hold position and watch
- (group _unit) reveal [_target, 1.5];
- (group _unit) setVariable ["AICanFire", false];
- _unit doWatch (getPos _target);
- };
- } else {
- // Clear shot - full engagement
- (group _unit) reveal [_target, 4];
- (group _unit) setVariable ["AICanFire", true];
- };
- };
- };
- };
- };
- private _lastDroneCheck = _unit getVariable ["lastDroneCheck", 0];
- if (time > _lastDroneCheck + (10 * PERF_COOLDOWN_MULTIPLIER)) then {
- _unit setVariable ["lastDroneCheck", time];
- private _nearbyAir = _unit nearEntities ["Air", (350 * PERF_SEARCH_RADIUS_MULTIPLIER)];
- if (count _nearbyAir > 0) then {
- private _enemyDrone = _nearbyAir findIf {
- alive _x && side _x == _enemySide &&
- (_x isKindOf "UAV_01_base_F" || _x isKindOf "UAV_02_base_F" || _x isKindOf "UAV")
- };
- if (_enemyDrone != -1) then {
- private _drone = _nearbyAir select _enemyDrone;
- if (_unit distance _drone < (350 * PERF_SEARCH_RADIUS_MULTIPLIER)) then {
- private _visibility = [_unit, "VIEW", _drone] checkVisibility [eyePos _unit, getPosASL _drone];
- if (_visibility > 0.3) then {
- (group _unit) reveal [_drone, 4];
- (group _unit) setVariable ["AICanFire", true];
- _unit doWatch _drone;
- _unit doTarget _drone;
- _unit doFire _drone;
- };
- };
- };
- };
- };
- };
- private _damage = damage _unit;
- if (_damage > 0.3) then {
- private _lastSmokeTime = _unit getVariable ["lastSmokeTime", 0];
- if (time > _lastSmokeTime + (90 * PERF_COOLDOWN_MULTIPLIER) && getSuppression _unit > 0.5) then {
- [_unit, "cover"] call fnc_botUseSmoke;
- _unit setVariable ["lastSmokeTime", time];
- };
- if (_damage > 0.5) then {
- private _isRetreating = _unit getVariable ["isRetreating", false];
- if (!_isRetreating) then {
- private _retreatPos = _unitPos getPos [30, (direction _unit) + 180];
- _unit doMove _retreatPos;
- _unit setBehaviour "AWARE";
- _unit setVariable ["isRetreating", true];
- _unit setVariable ["retreatPos", _retreatPos];
- } else {
- private _retreatPos = _unit getVariable ["retreatPos", [0,0,0]];
- if (_unit distance _retreatPos < 10) then {
- if ("FirstAidKit" in items _unit) then {
- _unit action ["HealSoldierSelf", _unit];
- _unit removeItem "FirstAidKit";
- };
- _unit setVariable ["isRetreating", false];
- };
- };
- };
- };
- if (currentWeapon _unit != "") then {
- private _currentAmmo = _unit ammo (currentWeapon _unit);
- private _lastKnownAmmo = _unit getVariable ["lastKnownAmmo", _currentAmmo];
- if (_currentAmmo < _lastKnownAmmo) then {
- _unit setVariable ["lastFiredTime", time];
- };
- _unit setVariable ["lastKnownAmmo", _currentAmmo];
- };
- } else {
- // VEHICLE CREW LOGIC
- private _lastVehicleCheck = _vehicle getVariable ["lastVehicleCheck", 0];
- if (time > _lastVehicleCheck + 6) then {
- _vehicle setVariable ["lastVehicleCheck", time];
- private _nearTargets = _unit nearTargets (250 * PERF_SEARCH_RADIUS_MULTIPLIER);
- private _targetCount = (count _nearTargets) min 5;
- for "_t" from 0 to (_targetCount - 1) do {
- private _targetData = _nearTargets select _t;
- private _target = _targetData select 4;
- if (alive _target && side _target == _enemySide && (_target isKindOf "CAManBase" || _target isKindOf "LandVehicle")) then {
- private _distance = _unit distance _target;
- if (_distance < (250 * PERF_SEARCH_RADIUS_MULTIPLIER) && _unit knowsAbout _target > 0) then {
- private _obstructionLevel = [_unit, _target] call fnc_checkFoliageObstruction;
- if (_obstructionLevel > 0.6) then {
- (group _unit) reveal [_target, 1.5];
- } else {
- (group _unit) reveal [_target, 3];
- };
- };
- };
- };
- };
- // HELICOPTER LOGIC
- if (_vehicle isKindOf "Air" && driver _vehicle == _unit) then {
- private _lastHeliCheck = _vehicle getVariable ["lastHeliCheck", 0];
- if (time > _lastHeliCheck + 4) then {
- _vehicle setVariable ["lastHeliCheck", time];
- private _nearMissiles = _vehicle nearObjects ["MissileBase", 800];
- private _lastFlareTime = _vehicle getVariable ["lastFlareTime", 0];
- if (count _nearMissiles > 0 && time > _lastFlareTime + 4) then {
- _vehicle action ["useWeapon", _vehicle, driver _vehicle, 0];
- _vehicle setVariable ["lastFlareTime", time];
- private _evadeDir = random 360;
- _vehicle doMove (_vehicle getPos [300, _evadeDir]);
- _vehicle flyInHeight (50 + random 150);
- };
- if (isNull (assignedTarget _unit)) then {
- private _potentialTargets = _vehicle nearEntities [["CAManBase", "LandVehicle"], 1000];
- private _enemies = _potentialTargets select {
- side _x == _enemySide && alive _x && _vehicle knowsAbout _x > 0.5
- };
- if (count _enemies > 0) then {
- (gunner _vehicle) doTarget (_enemies select 0);
- (gunner _vehicle) doFire (_enemies select 0);
- };
- };
- };
- };
- };
- private _lastIntelTime = _unit getVariable ["BOTAI_lastIntelCheck", 0];
- if (time > _lastIntelTime + (15 * PERF_COOLDOWN_MULTIPLIER)) then {
- _unit setVariable ["BOTAI_lastIntelCheck", time];
- [_unit, side _unit] call HC_fnc_gatherIntelligence;
- [_unit, side _unit] call HC_fnc_investigateNearbyIntel;
- };
- };
- };
- // Move to next batch
- _unitIndex = _unitIndex + _batchSize;
- if (_unitIndex >= count _allAiUnits) then {
- _unitIndex = 0;
- };
- // Measure cycle execution time for the performance monitor
- private _cycleEndTime = diag_tickTime;
- private _cycleExecTime = _cycleEndTime - _cycleStartTime;
- missionNamespace setVariable ["BOTAI_lastExecTime", _cycleExecTime];
- // Log if a single cycle takes too long
- if (_cycleExecTime > 0.050) then {
- diag_log format ["[BOTAI] SLOW CYCLE: %1ms (batch: %2, state: %3)",
- round (_cycleExecTime * 1000), _batchSize, PERF_STATE];
- };
- // Adaptive sleep time based on server performance
- sleep (1.0 * PERF_MAIN_LOOP_MULTIPLIER);
- };
- };
- };
- ==================== END OF: botai.sqf ====================
- ==================== START OF: cleanup.sqf ====================
- // cleanup.sqf
- if (isNil "CLEANUP_BODY_LIMIT") then { CLEANUP_BODY_LIMIT = 20; };
- if (isNil "CLEANUP_DELAY") then { CLEANUP_DELAY = 30; };
- if (isNil "CLEANUP_BODY_TIMER") then { CLEANUP_BODY_TIMER = 120; };
- if (isNil "CLEANUP_WEAPON_TIMER") then { CLEANUP_WEAPON_TIMER = 300; };
- if (isNil "CLEANUP_VEHICLE_TIMER") then { CLEANUP_VEHICLE_TIMER = 120; };
- if (isNil "CLEANUP_MIN_DISTANCE") then { CLEANUP_MIN_DISTANCE = 300; };
- if (isNil "CLEANUP_BODY_ARRAY") then { CLEANUP_BODY_ARRAY = []; };
- fnc_performCleanup = {
- private _startTime = diag_tickTime;
- private _deleted = 0;
- private _currentTime = time;
- // Apply dynamic multiplier from performance settings
- // PERF_CLEANUP_MULTIPLIER is defined in performance.sqf (default 1.0)
- // Lower multiplier means faster cleanup (shorter timers)
- private _timerMult = missionNamespace getVariable ["PERF_CLEANUP_MULTIPLIER", 1.0];
- private _bodyTimer = CLEANUP_BODY_TIMER * _timerMult;
- private _weaponTimer = CLEANUP_WEAPON_TIMER * _timerMult;
- private _vehicleTimer = CLEANUP_VEHICLE_TIMER * _timerMult;
- // --- Add new dead bodies to the tracking array ---
- private _deadUnits = allDead select {_x isKindOf "CAManBase"};
- {
- private _body = _x;
- if ((CLEANUP_BODY_ARRAY findIf {(_x select 0) == _body}) == -1) then {
- CLEANUP_BODY_ARRAY pushBack [_body, _currentTime];
- };
- } forEach _deadUnits;
- // --- Process and delete old bodies ---
- for "_i" from (count CLEANUP_BODY_ARRAY - 1) to 0 step -1 do {
- private _entry = CLEANUP_BODY_ARRAY select _i;
- private _body = _entry select 0;
- private _deathTime = _entry select 1;
- if (isNull _body || (_currentTime - _deathTime > _bodyTimer)) then {
- if (!isNull _body) then {
- deleteVehicle _body;
- _deleted = _deleted + 1;
- };
- CLEANUP_BODY_ARRAY deleteAt _i;
- };
- };
- // --- Process Ground Weapon Holders ---
- {
- private _holder = _x;
- if ((count (weaponCargo _holder) == 0) && (count (magazineCargo _holder) == 0) && (count (itemCargo _holder) == 0)) then {
- deleteVehicle _holder;
- _deleted = _deleted + 1;
- } else {
- private _age = _currentTime - (_holder getVariable ["cleanup_time", _currentTime]);
- if (isNil {_holder getVariable "cleanup_time"}) then {
- _holder setVariable ["cleanup_time", _currentTime];
- };
- if (_age > _weaponTimer) then {
- deleteVehicle _holder;
- _deleted = _deleted + 1;
- };
- };
- } forEach (allMissionObjects "GroundWeaponHolder");
- // ==================== CHANGE START: HIGHLY OPTIMIZED VEHICLE CLEANUP ====================
- private _cleanupTimer = _vehicleTimer; // Use dynamic timer
- // Helper function to process a vehicle for cleanup
- private _fnc_processVehicle = {
- params ["_vehicle", "_timerValue"]; // Accept timer as argument
- if (isNull _vehicle || {_vehicle isKindOf "CAManBase"}) exitWith {};
- // Aggressive cleanup for unoccupied quad bikes
- if (_vehicle isKindOf "Quadbike_01_base_F") then {
- if (count crew _vehicle == 0) then {
- private _startTime = _vehicle getVariable ["cleanup_unoccupied_startTime", -1];
- if (_startTime == -1) then {
- _vehicle setVariable ["cleanup_unoccupied_startTime", time, true];
- } else {
- if (time - _startTime > 300) then { // 5 minute timer for abandoned quads
- deleteVehicle _vehicle;
- _deleted = _deleted + 1;
- continue;
- };
- };
- } else {
- if !(isNil {_vehicle getVariable "cleanup_unoccupied_startTime"}) then {
- _vehicle setVariable ["cleanup_unoccupied_startTime", nil, true];
- };
- };
- };
- private _isDerelict = false;
- if (!alive _vehicle) then {
- _isDerelict = true; // Is destroyed
- } else {
- if (damage _vehicle > 0.9) then {
- _isDerelict = true; // Is heavily damaged
- } else {
- if (count crew _vehicle == 0) then {
- // Is abandoned (only check if it was an AI vehicle)
- if ((_vehicle getVariable ["isAIVehicle", false]) || (_vehicle getVariable ["unitValue", 0] > 0)) then {
- _isDerelict = true;
- };
- };
- };
- };
- // Don't clean protected vehicles unless they are derelict
- if ((_vehicle getVariable ["gcImportant", false]) && !_isDerelict) then {
- if !(isNil {_vehicle getVariable "cleanup_startTime"}) then {
- _vehicle setVariable ["cleanup_startTime", nil, true];
- };
- continue;
- };
- if (_isDerelict) then {
- private _startTime = _vehicle getVariable ["cleanup_startTime", -1];
- if (_startTime == -1) then {
- _vehicle setVariable ["cleanup_startTime", time, true];
- } else {
- if (time - _startTime > _timerValue) then {
- deleteVehicle _vehicle;
- _deleted = _deleted + 1;
- };
- };
- } else {
- if !(isNil {_vehicle getVariable "cleanup_startTime"}) then {
- _vehicle setVariable ["cleanup_startTime", nil, true];
- };
- };
- };
- // 1. Process all recently destroyed vehicles (fast)
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach (allDead select {!(_x isKindOf "CAManBase")});
- // 2. Process tracked AI vehicles that might be abandoned (fast)
- BLUFOR_ACTIVE_LIGHT_VEHICLES = BLUFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x};
- OPFOR_ACTIVE_LIGHT_VEHICLES = OPFOR_ACTIVE_LIGHT_VEHICLES select {!isNull _x};
- BLUFOR_ACTIVE_TANKS = BLUFOR_ACTIVE_TANKS select {!isNull _x};
- OPFOR_ACTIVE_TANKS = OPFOR_ACTIVE_TANKS select {!isNull _x};
- BLUFOR_ACTIVE_ATTACK_HELIS = BLUFOR_ACTIVE_ATTACK_HELIS select {!isNull _x};
- OPFOR_ACTIVE_ATTACK_HELIS = OPFOR_ACTIVE_ATTACK_HELIS select {!isNull _x};
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach BLUFOR_ACTIVE_LIGHT_VEHICLES;
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach OPFOR_ACTIVE_LIGHT_VEHICLES;
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach BLUFOR_ACTIVE_TANKS;
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach OPFOR_ACTIVE_TANKS;
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach BLUFOR_ACTIVE_ATTACK_HELIS;
- { [_x, _cleanupTimer] call _fnc_processVehicle; } forEach OPFOR_ACTIVE_ATTACK_HELIS;
- // ===================== CHANGE END =====================
- // --- Empty Group Cleanup ---
- {
- if (count (units _x) == 0) then {
- deleteGroup _x;
- };
- } forEach allGroups;
- // --- Crater and Smoke Cleanup ---
- { deleteVehicle _x; _deleted = _deleted + 1; } forEach (allMissionObjects "CraterLong");
- { deleteVehicle _x; _deleted = _deleted + 1; } forEach (allMissionObjects "CraterLong_small");
- {
- private _obj = _x;
- private _age = time - (_obj getVariable ["cleanup_time", time]);
- if (_age > 300) then {
- deleteVehicle _obj;
- _deleted = _deleted + 1;
- } else {
- if (isNil {_obj getVariable "cleanup_time"}) then {
- _obj setVariable ["cleanup_time", time];
- };
- };
- } forEach (allMissionObjects "SmokeShell");
- private _endTime = diag_tickTime;
- private _executionTime = (_endTime - _startTime) * 1000;
- };
- if (isServer) then {
- [] spawn {
- waitUntil {time > 10};
- while {true} do {
- // Adaptive cleanup delay
- private _baseDelay = CLEANUP_DELAY;
- private _adaptiveDelay = [_baseDelay] call fnc_getAdaptiveSleep;
- sleep _adaptiveDelay;
- if (count allPlayers > 0) then {
- [] call fnc_performCleanup;
- };
- };
- };
- addMissionEventHandler ["EntityKilled", {
- params ["_victim"];
- if (_victim isKindOf "AllVehicles" && !(_victim isKindOf "CAManBase")) then {
- _victim setVariable ["cleanup_destroyed_time", time];
- };
- }];
- };
- ==================== END OF: cleanup.sqf ====================
- ==================== START OF: description.ext ====================
- class Header
- {
- gameType = CTI;
- minPlayers = 1;
- maxPlayers = 50;
- };
- respawn = "BASE"; // Enables respawn at the defined respawn positions (from BIS_fnc_addRespawnPosition in init.sqf)
- respawnDelay = 5; // Default respawn delay in seconds. Will be dynamically adjusted by playerinit.sqf
- respawnDialog = 0; // Hides the respawn screen, making respawn automatic after the delay
- // ======================= BASE UI CLASSES =======================
- class RscText_Base {
- type = 0; style = 0;
- font = "RobotoCondensed"; sizeEx = 0.035;
- colorText[] = {1,1,1,1};
- colorBackground[] = {0,0,0,0}; // Transparent background
- text = "";
- };
- class RscButton_Base {
- type = 1; style = 2;
- font = "RobotoCondensed"; sizeEx = 0.035;
- colorText[] = {1,1,1,1};
- colorBackground[] = {0,0,0,0.8};
- colorBackgroundActive[] = {1,1,1,0.3};
- colorFocused[] = {0,0,0,1};
- colorDisabled[] = {1,1,1,0.3};
- colorBackgroundDisabled[] = {0,0,0,0.3};
- colorBorder[] = {0,0,0,1};
- colorShadow[] = {0,0,0,0.5};
- soundEnter[] = {"\A3\ui_f\data\sound\RscButton\soundEnter", 0.09, 1};
- soundPush[] = {"\A3\ui_f\data\sound\RscButton\soundPush", 0.09, 1};
- soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick", 0.09, 1};
- soundEscape[] = {"\A3\ui_f\data\sound\RscButton\soundEscape", 0.09, 1};
- borderSize = 0; offsetX = 0; offsetY = 0; offsetPressedX = 0; offsetPressedY = 0;
- };
- // ======================= MISSION BRIEFING DIALOG =======================
- class MissionBriefingDialog {
- idd = 9300;
- movingEnable = false;
- onLoad = "uiNamespace setVariable ['MissionBriefingDisplay', _this select 0];";
- onUnload = "uiNamespace setVariable ['MissionBriefingDisplay', displayNull];";
- class Controls {
- // --- BACKGROUND ---
- class Background {
- type = 0; style = 0; idc = 93000;
- x = 0.1; y = 0.05; w = 0.8; h = 0.9; // Increased height, moved up
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0.8};
- colorText[] = {1,1,1,1};
- text = "";
- };
- // --- TITLE ---
- class Title {
- type = 0; style = 2; idc = 93001;
- text = "Mission Briefing";
- x = 0.1; y = 0.07; w = 0.8; h = 0.04; // Moved up
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0};
- colorText[] = {1,1,1,1};
- };
- // --- TEXT AREA (SCROLLABLE) ---
- class BriefingText {
- type = 0; style = 16; // ST_MULTI for scrollable text
- idc = 93002;
- 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.";
- x = 0.12; y = 0.13; w = 0.76; h = 0.74; // Increased height, moved up
- font = "RobotoCondensed"; sizeEx = 0.032; // Reduced font size
- colorBackground[] = {0,0,0,0};
- colorText[] = {1,1,1,1};
- lineSpacing = 1;
- };
- // --- CLOSE BUTTON ---
- class CloseButton: RscButton_Base {
- idc = 93003;
- text = "Close";
- x = 0.475; y = 0.88; w = 0.15; h = 0.04; // Adjusted position for larger dialog
- action = "closeDialog 0;";
- };
- };
- };
- // ======================= MAIN PURCHASE MENU =======================
- class PlayerPurchaseMenu {
- idd = 9000;
- movingEnable = false;
- onLoad = "((_this select 0) displayCtrl 9002) ctrlSetText format['Points: %1', player getVariable ['playerPoints', 0]];";
- class Controls {
- // --- BACKGROUND AND TITLES ---
- class Background {
- type = 0; style = 0; idc = 90000;
- x = 0.3; y = 0.28; w = 0.4; h = 0.48;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0.7};
- colorText[] = {1,1,1,1};
- text = "";
- };
- class Title {
- type = 0; style = 2; idc = 9001;
- text = "Purchase Menu";
- x = 0.3; y = 0.3; w = 0.4; h = 0.04;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0};
- colorText[] = {1,1,1,1};
- };
- class PointsDisplay {
- type = 0; style = 2; idc = 9002;
- text = "Points: 100";
- x = 0.3; y = 0.34; w = 0.4; h = 0.04;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0};
- colorText[] = {1,1,0,1};
- };
- // --- BUTTONS ---
- class QuadButton: RscButton_Base {
- idc = 9003;
- text = "Quad Bike (1 pt)";
- x = 0.32; y = 0.40; w = 0.36; h = 0.05;
- 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;";
- };
- class LightVicButton: RscButton_Base {
- idc = 9004;
- text = "Light Vehicles (10 pts)";
- x = 0.32; y = 0.46; w = 0.36; h = 0.05;
- action = "closeDialog 0; createDialog 'LightVehicleMenu';";
- };
- class HeavyVicButton: RscButton_Base {
- idc = 9006;
- text = "Heavy Vehicles (20-30 pts)";
- x = 0.32; y = 0.52; w = 0.36; h = 0.05;
- action = "closeDialog 0; createDialog 'HeavyVehicleMenu';";
- };
- class ParadropButton: RscButton_Base {
- idc = 9007;
- text = "Paradrop Near Stronghold (2 pts)";
- x = 0.32; y = 0.58; w = 0.36; h = 0.05;
- action = "[player] remoteExecCall ['fnc_server_paradropPlayer', 2]; closeDialog 0;";
- };
- // ==================== CHANGE START ====================
- class AISquadButton: RscButton_Base {
- idc = 9008;
- text = "Buy 3 Man AI Squad (15 pts)";
- x = 0.32; y = 0.64; w = 0.36; h = 0.05;
- action = "[player] remoteExecCall ['fnc_server_purchaseAISquad', 2]; closeDialog 0;";
- };
- // ===================== CHANGE END =====================
- class CloseButton: RscButton_Base {
- idc = 9005;
- text = "Close";
- x = 0.425; y = 0.70; w = 0.15; h = 0.04;
- action = "closeDialog 0;";
- };
- };
- };
- // ======================= LIGHT VEHICLE SUB-MENU (Self-Contained) =======================
- class LightVehicleMenu {
- idd = 9100;
- movingEnable = false;
- 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); ";
- class Controls {
- class Background {
- type = 0; style = 0; idc = 91000;
- x = 0.3; y = 0.28; w = 0.4; h = 0.35;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0.7};
- colorText[] = {1,1,1,1}; text = "";
- };
- class Title {
- type = 0; style = 2; idc = 91001;
- text = "Light Vehicles (10 pts)";
- x = 0.3; y = 0.3; w = 0.4; h = 0.04;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0}; colorText[] = {1,1,1,1};
- };
- class PointsDisplay {
- type = 0; style = 2; idc = 9100;
- text = "Points: 100";
- x = 0.3; y = 0.34; w = 0.4; h = 0.04;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0}; colorText[] = {1,1,0,1};
- };
- // --- BLUFOR BUTTONS ---
- class HunterButton: RscButton_Base {
- idc = 9101;
- text = "Hunter HMG";
- x = 0.32; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'B_MRAP_01_hmg_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- class PawneeButton: RscButton_Base {
- idc = 9102;
- text = "Pawnee";
- x = 0.51; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'B_Heli_Light_01_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- // --- OPFOR BUTTONS ---
- class IfritButton: RscButton_Base {
- idc = 9103;
- text = "Ifrit HMG";
- x = 0.32; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'O_MRAP_02_hmg_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- class OrcaButton: RscButton_Base {
- idc = 9104;
- text = "Orca";
- x = 0.51; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'O_Heli_Light_02_unarmed_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- // --- UTILITY BUTTONS ---
- class BackButton: RscButton_Base {
- idc = 9198;
- text = "Back";
- x = 0.32; y = 0.58; w = 0.15; h = 0.04;
- action = "closeDialog 0; createDialog 'PlayerPurchaseMenu';";
- };
- class CloseButton: RscButton_Base {
- idc = 9199;
- text = "Close";
- x = 0.53; y = 0.58; w = 0.15; h = 0.04;
- action = "closeDialog 0;";
- };
- };
- };
- // ======================= HEAVY VEHICLE SUB-MENU (Self-Contained) =======================
- class HeavyVehicleMenu {
- idd = 9200;
- movingEnable = false;
- 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); ";
- class Controls {
- class Background {
- type = 0; style = 0; idc = 92000;
- x = 0.3; y = 0.28; w = 0.4; h = 0.35;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0.7};
- colorText[] = {1,1,1,1}; text = "";
- };
- class Title {
- type = 0; style = 2; idc = 92001;
- text = "Heavy Vehicles";
- x = 0.3; y = 0.3; w = 0.4; h = 0.04;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0}; colorText[] = {1,1,1,1};
- };
- class PointsDisplay {
- type = 0; style = 2; idc = 9200;
- text = "Points: 100";
- x = 0.3; y = 0.34; w = 0.4; h = 0.04;
- font = "RobotoCondensed"; sizeEx = 0.04;
- colorBackground[] = {0,0,0,0}; colorText[] = {1,1,0,1};
- };
- // --- BLUFOR BUTTONS ---
- class MarshallButton: RscButton_Base {
- idc = 9201;
- text = "Marshall APC (20 pts)";
- x = 0.32; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'B_APC_Wheeled_01_cannon_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- class SlammerButton: RscButton_Base {
- idc = 9202;
- text = "Slammer MBT (30 pts)";
- x = 0.51; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'B_MBT_01_cannon_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- // --- OPFOR BUTTONS ---
- class MaridButton: RscButton_Base {
- idc = 9203;
- text = "Marid APC (20 pts)";
- x = 0.32; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'O_APC_Wheeled_02_rcws_v2_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- class VarsukButton: RscButton_Base {
- idc = 9204;
- text = "Varsuk MBT (30 pts)";
- x = 0.51; y = 0.40; w = 0.17; h = 0.05;
- action = "[player, 'O_MBT_02_cannon_F'] remoteExecCall ['fnc_server_purchaseVehicle', 2]; closeDialog 0;";
- };
- // --- UTILITY BUTTONS ---
- class BackButton: RscButton_Base {
- idc = 9298;
- text = "Back";
- x = 0.32; y = 0.58; w = 0.15; h = 0.04;
- action = "closeDialog 0; createDialog 'PlayerPurchaseMenu';";
- };
- class CloseButton: RscButton_Base {
- idc = 9299;
- text = "Close";
- x = 0.53; y = 0.58; w = 0.15; h = 0.04;
- action = "closeDialog 0;";
- };
- };
- };
- // ======================= ADMIN/SP SETTINGS MENU =======================
- class AdminSettingsMenu {
- idd = 9400;
- movingEnable = false;
- onLoad = " \
- private _display = _this select 0; \
- private _isSP = !isMultiplayer; \
- _display displayCtrl 9411 ctrlShow _isSP; \
- _display displayCtrl 9412 ctrlShow _isSP; \
- _display displayCtrl 9413 ctrlShow _isSP; \
- _display displayCtrl 9414 ctrlShow _isSP; \
- _display displayCtrl 9415 ctrlShow _isSP; \
- if (!_isSP) then { \
- (_display displayCtrl 9410) ctrlSetText 'Team Points (SP Only - Disabled)'; \
- (_display displayCtrl 9416) ctrlSetText 'Personal Points (SP Only - Disabled)'; \
- }; \
- _display displayCtrl 9420 ctrlSetText (if (_isSP) then {'Singleplayer Settings'} else {'Host Settings'}); \
- private _currentMaxAI = missionNamespace getVariable ['maxAI', 120]; \
- (_display displayCtrl 9421) ctrlSetText format ['Current: %1', _currentMaxAI]; \
- ";
- class Controls {
- // --- BACKGROUND AND TITLES ---
- class Background: RscText_Base {
- idc = 94000;
- x = 0.25; y = 0.1; w = 0.5; h = 0.8;
- colorBackground[] = {0,0,0,0.7};
- };
- class Title: RscText_Base {
- idc = 9420; style = 2; // Centered
- text = "Settings";
- x = 0.25; y = 0.12; w = 0.5; h = 0.04;
- sizeEx = 0.04;
- };
- // --- MAX AI SETTINGS ---
- class MaxAITitle: RscText_Base {
- idc = -1;
- text = "Max AI Count:";
- x = 0.26; y = 0.18; w = 0.2; h = 0.04;
- sizeEx = 0.03;
- };
- class MaxAIDisplay: RscText_Base {
- idc = 9421;
- text = "Current: 120";
- x = 0.55; y = 0.18; w = 0.2; h = 0.04;
- style = 1; // Right aligned
- sizeEx = 0.03;
- colorText[] = {1,1,0,1};
- };
- // --- ROW 1 ---
- class MaxAIButton80: RscButton_Base {
- idc = -1; text = "80";
- x = 0.26; y = 0.22; w = 0.08; h = 0.04;
- action = "[80] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 80';";
- };
- class MaxAIButton90: RscButton_Base {
- idc = -1; text = "90";
- x = 0.36; y = 0.22; w = 0.08; h = 0.04;
- action = "[90] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 90';";
- };
- class MaxAIButton100: RscButton_Base {
- idc = -1; text = "100";
- x = 0.46; y = 0.22; w = 0.08; h = 0.04;
- action = "[100] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 100';";
- };
- class MaxAIButton110: RscButton_Base {
- idc = -1; text = "110";
- x = 0.56; y = 0.22; w = 0.08; h = 0.04;
- action = "[110] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 110';";
- };
- class MaxAIButton120: RscButton_Base {
- idc = -1; text = "120";
- x = 0.66; y = 0.22; w = 0.08; h = 0.04;
- action = "[120] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 120';";
- };
- // --- ROW 2 ---
- class MaxAIButton130: RscButton_Base {
- idc = -1; text = "130";
- x = 0.26; y = 0.27; w = 0.08; h = 0.04;
- action = "[130] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 130';";
- };
- class MaxAIButton140: RscButton_Base {
- idc = -1; text = "140";
- x = 0.36; y = 0.27; w = 0.08; h = 0.04;
- action = "[140] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 140';";
- };
- class MaxAIButton150: RscButton_Base {
- idc = -1; text = "150";
- x = 0.46; y = 0.27; w = 0.08; h = 0.04;
- action = "[150] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 150';";
- };
- class MaxAIButton160: RscButton_Base {
- idc = -1; text = "160";
- x = 0.56; y = 0.27; w = 0.08; h = 0.04;
- action = "[160] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 160';";
- };
- class MaxAIButton170: RscButton_Base {
- idc = -1; text = "170";
- x = 0.66; y = 0.27; w = 0.08; h = 0.04;
- action = "[170] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 170';";
- };
- // --- ROW 3 ---
- class MaxAIButton180: RscButton_Base {
- idc = -1; text = "180";
- x = 0.26; y = 0.32; w = 0.08; h = 0.04;
- action = "[180] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 180';";
- };
- class MaxAIButton190: RscButton_Base {
- idc = -1; text = "190";
- x = 0.36; y = 0.32; w = 0.08; h = 0.04;
- action = "[190] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 190';";
- };
- class MaxAIButton200: RscButton_Base {
- idc = -1; text = "200";
- x = 0.46; y = 0.32; w = 0.08; h = 0.04;
- action = "[200] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 200';";
- };
- class MaxAIButton210: RscButton_Base {
- idc = -1; text = "210";
- x = 0.56; y = 0.32; w = 0.08; h = 0.04;
- action = "[210] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 210';";
- };
- class MaxAIButton220: RscButton_Base {
- idc = -1; text = "220";
- x = 0.66; y = 0.32; w = 0.08; h = 0.04;
- action = "[220] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 220';";
- };
- // --- ROW 4 ---
- class MaxAIButton230: RscButton_Base {
- idc = -1; text = "230";
- x = 0.26; y = 0.37; w = 0.08; h = 0.04;
- action = "[230] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 230';";
- };
- class MaxAIButton240: RscButton_Base {
- idc = -1; text = "240";
- x = 0.36; y = 0.37; w = 0.08; h = 0.04;
- action = "[240] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 240';";
- };
- class MaxAIButton250: RscButton_Base {
- idc = -1; text = "250";
- x = 0.46; y = 0.37; w = 0.08; h = 0.04;
- action = "[250] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 250';";
- };
- class MaxAIButton260: RscButton_Base {
- idc = -1; text = "260";
- x = 0.56; y = 0.37; w = 0.08; h = 0.04;
- action = "[260] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 260';";
- };
- class MaxAIButton270: RscButton_Base {
- idc = -1; text = "270";
- x = 0.66; y = 0.37; w = 0.08; h = 0.04;
- action = "[270] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 270';";
- };
- // --- ROW 5 ---
- class MaxAIButton280: RscButton_Base {
- idc = -1; text = "280";
- x = 0.26; y = 0.42; w = 0.08; h = 0.04;
- action = "[280] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 280';";
- };
- class MaxAIButton290: RscButton_Base {
- idc = -1; text = "290";
- x = 0.36; y = 0.42; w = 0.08; h = 0.04;
- action = "[290] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 290';";
- };
- class MaxAIButton300: RscButton_Base {
- idc = -1; text = "300";
- x = 0.46; y = 0.42; w = 0.08; h = 0.04;
- action = "[300] remoteExecCall ['fnc_server_setMaxAI', 2]; ((findDisplay 9400) displayCtrl 9421) ctrlSetText 'Current: 300';";
- };
- // --- TIMESCALE SETTINGS ---
- class TimescaleTitle: RscText_Base {
- idc = -1;
- text = "Timescale:";
- x = 0.26; y = 0.48; w = 0.2; h = 0.04;
- sizeEx = 0.03;
- };
- class TimescaleButton1: RscButton_Base {
- idc = -1; text = "Realtime";
- x = 0.26; y = 0.52; w = 0.15; h = 0.04;
- action = "[1] remoteExecCall ['fnc_server_setTimescale', 2];";
- };
- class TimescaleButton2: RscButton_Base {
- idc = -1; text = "2h = 1 Day";
- x = 0.42; y = 0.52; w = 0.15; h = 0.04;
- action = "[12] remoteExecCall ['fnc_server_setTimescale', 2];";
- };
- class TimescaleButton3: RscButton_Base {
- idc = -1; text = "1h = 1 Day";
- x = 0.58; y = 0.52; w = 0.15; h = 0.04;
- action = "[24] remoteExecCall ['fnc_server_setTimescale', 2];";
- };
- // --- TEAM POINTS (SP ONLY) ---
- class TeamPointsTitle: RscText_Base {
- idc = 9410;
- text = "Team Points (SP Only):";
- x = 0.26; y = 0.58; w = 0.3; h = 0.04;
- sizeEx = 0.03;
- };
- class AddBluforPoints: RscButton_Base {
- idc = 9411; text = "+100 BLUFOR";
- x = 0.26; y = 0.62; w = 0.23; h = 0.04;
- action = "['BLUFOR', 100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
- };
- class RemBluforPoints: RscButton_Base {
- idc = 9412; text = "-100 BLUFOR";
- x = 0.26; y = 0.67; w = 0.23; h = 0.04;
- action = "['BLUFOR', -100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
- };
- class AddOpforPoints: RscButton_Base {
- idc = 9413; text = "+100 OPFOR";
- x = 0.51; y = 0.62; w = 0.23; h = 0.04;
- action = "['OPFOR', 100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
- };
- class RemOpforPoints: RscButton_Base {
- idc = 9414; text = "-100 OPFOR";
- x = 0.51; y = 0.67; w = 0.23; h = 0.04;
- action = "['OPFOR', -100] remoteExecCall ['fnc_server_adjustTeamPoints', 2];";
- };
- // --- PERSONAL POINTS (SP ONLY) ---
- class PersonalPointsTitle: RscText_Base {
- idc = 9416;
- text = "Personal Points (SP Only):";
- x = 0.26; y = 0.73; w = 0.3; h = 0.04;
- sizeEx = 0.03;
- };
- class AddPersonalPoints: RscButton_Base {
- idc = 9415;
- text = "+100 Personal Points";
- x = 0.26; y = 0.77; w = 0.48; h = 0.04;
- action = "[player, 100] remoteExecCall ['fnc_server_addPersonalPoints', 2];";
- };
- // --- CLOSE BUTTON ---
- class CloseButton: RscButton_Base {
- idc = -1;
- text = "Close";
- x = 0.4; y = 0.83; w = 0.2; h = 0.04;
- action = "closeDialog 0;";
- };
- };
- };
- ==================== END OF: description.ext ====================
- ==================== START OF: highcommand.sqf ====================
- // highcommand.sqf
- // High Command AI logic for Arma 3 mission
- // Initialize global variables
- if (isNil "BLUFOR_STRATEGY") then { BLUFOR_STRATEGY = "SEARCH"; };
- if (isNil "OPFOR_STRATEGY") then { OPFOR_STRATEGY = "SEARCH"; };
- if (isNil "BLUFOR_LAST_DECISION") then { BLUFOR_LAST_DECISION = time; };
- if (isNil "OPFOR_LAST_DECISION") then { OPFOR_LAST_DECISION = time; };
- if (isNil "BLUFOR_LAST_FLARE") then { BLUFOR_LAST_FLARE = time - 3600; };
- if (isNil "OPFOR_LAST_FLARE") then { OPFOR_LAST_FLARE = time - 3600; };
- if (isNil "BLUFOR_LAST_SMOKE") then { BLUFOR_LAST_SMOKE = time - 3600; };
- if (isNil "OPFOR_LAST_SMOKE") then { OPFOR_LAST_SMOKE = time - 3600; };
- if (isNil "BLUFOR_LAST_DRONE") then { BLUFOR_LAST_DRONE = time - 3600; };
- if (isNil "OPFOR_LAST_DRONE") then { OPFOR_LAST_DRONE = time - 3600; };
- if (isNil "BLUFOR_LAST_ARTILLERY") then { BLUFOR_LAST_ARTILLERY = time - 3600; };
- if (isNil "OPFOR_LAST_ARTILLERY") then { OPFOR_LAST_ARTILLERY = time - 3600; };
- HC_SUPPORT_COST = 0;
- if (isNil "BLUFOR_COMMANDER_PERSONALITY") then {
- BLUFOR_COMMANDER_PERSONALITY = selectRandom HC_COMMANDER_PERSONALITIES;
- };
- if (isNil "OPFOR_COMMANDER_PERSONALITY") then {
- OPFOR_COMMANDER_PERSONALITY = selectRandom HC_COMMANDER_PERSONALITIES;
- };
- if (isNil "BLUFOR_INTEL") then { BLUFOR_INTEL = createHashMap; };
- if (isNil "OPFOR_INTEL") then { OPFOR_INTEL = createHashMap; };
- if (isNil "BLUFOR_SEARCH_AREAS") then { BLUFOR_SEARCH_AREAS = []; };
- if (isNil "OPFOR_SEARCH_AREAS") then { OPFOR_SEARCH_AREAS = []; };
- if (isNil "BLUFOR_TACTICAL_STATE") then { BLUFOR_TACTICAL_STATE = createHashMap; };
- if (isNil "OPFOR_TACTICAL_STATE") then { OPFOR_TACTICAL_STATE = createHashMap; };
- if (isNil "BLUFOR_LAST_SUPPORT_REQUEST") then { BLUFOR_LAST_SUPPORT_REQUEST = 0; };
- if (isNil "OPFOR_LAST_SUPPORT_REQUEST") then { OPFOR_LAST_SUPPORT_REQUEST = 0; };
- if (isNil "HC_Active_Groups") then { HC_Active_Groups = createHashMap; };
- // ==================== INTEL OPTIMIZATION START ====================
- // NEW: Lightweight queues for recent tactical reports to avoid scanning the entire intel database.
- if (isNil "BLUFOR_TACTICAL_REPORTS") then { BLUFOR_TACTICAL_REPORTS = []; };
- if (isNil "OPFOR_TACTICAL_REPORTS") then { OPFOR_TACTICAL_REPORTS = []; };
- HC_REPORTS_MAX_SIZE = 100; // Max size for the tactical report queue.
- // ===================== INTEL OPTIMIZATION END =====================
- // REVISED FUNCTION: Creates a temporary map marker that fades out over 60 seconds.
- fnc_HC_createSupportMarker = {
- params ["_side", "_pos", "_type", "_text"];
- if (!isServer) exitWith {};
- private _markerName = format ["support_marker_%1_%2", round(random 99999), time];
- private _markerColor = if (_side == west) then { "ColorBlue" } else { "ColorRed" };
- // Create marker only for the appropriate side's players
- [_markerName, _pos, _type, _markerColor, _text] remoteExec ["fnc_createLocalSupportMarker", _side, true];
- // Server-side cleanup spawn
- [_markerName, _side] spawn {
- params ["_marker", "_side"];
- sleep 60;
- [_marker] remoteExec ["deleteMarkerLocal", _side, true];
- };
- };
- fnc_HC_assignGroupWithSpacing = {
- params ["_groups", "_role", "_centerPos", "_ownBase", "_enemyBase"];
- {
- private _group = _x;
- private _targetPos = _centerPos;
- // Apply aggressive spacing for the first 15 minutes to ensure varied deployment
- // After 15 minutes, gradually reduce spacing over the next 10 minutes
- if (time < 900) then {
- private _spacingMultiplier = 1.0;
- // Full spacing for first 15 minutes
- if (time < 900) then {
- _spacingMultiplier = 1.0;
- };
- // Gradually reduce spacing between 15-25 minutes
- if (time >= 900 && time < 1500) then {
- _spacingMultiplier = 1.0 - ((time - 900) / 600); // Gradually reduce from 1.0 to 0
- };
- // Apply the spacing with current multiplier
- if (_spacingMultiplier > 0.1) then {
- // Much larger spread - 100-500m radius with full randomization
- private _spreadDistance = (100 + (random 400)) * _spacingMultiplier;
- private _spreadAngle = random 360;
- _targetPos = _centerPos getPos [_spreadDistance, _spreadAngle];
- };
- };
- // Execute the role with the (potentially offset) target position.
- [_group, _role, _targetPos, _ownBase, _enemyBase] call fnc_executeGroupRole;
- } forEach _groups;
- };
- fnc_createLocalSupportMarker = {
- params ["_markerName", "_pos", "_type", "_markerColor", "_text"];
- if (!hasInterface) exitWith {};
- createMarkerLocal [_markerName, _pos];
- _markerName setMarkerShapeLocal "ICON";
- _markerName setMarkerTypeLocal _type;
- _markerName setMarkerColorLocal _markerColor;
- _markerName setMarkerTextLocal _text;
- _markerName setMarkerSizeLocal [0.8, 0.8];
- // Local fade effect
- [_markerName] spawn {
- params ["_marker"];
- private _lifetime = 60;
- for "_i" from 0 to (_lifetime - 1) do {
- private _alpha = 1 - (_i / _lifetime);
- _marker setMarkerAlphaLocal _alpha;
- sleep 1;
- };
- deleteMarkerLocal _marker;
- };
- };
- // Check if support is available
- fnc_isSupportAvailable = {
- params ["_side", "_supportType"];
- private _lastSupport = 0;
- private _cooldown = 9999;
- switch (_supportType) do {
- case "FLARE": {
- _lastSupport = if (_side == west) then {BLUFOR_LAST_FLARE} else {OPFOR_LAST_FLARE};
- _cooldown = HC_FLARE_COOLDOWN;
- };
- case "SMOKE": {
- _lastSupport = if (_side == west) then {BLUFOR_LAST_SMOKE} else {OPFOR_LAST_SMOKE};
- _cooldown = HC_SMOKE_COOLDOWN;
- };
- case "DRONE": {
- _lastSupport = if (_side == west) then {BLUFOR_LAST_DRONE} else {OPFOR_LAST_DRONE};
- _cooldown = HC_DRONE_COOLDOWN;
- };
- case "ARTILLERY": {
- _lastSupport = if (_side == west) then {BLUFOR_LAST_ARTILLERY} else {OPFOR_LAST_ARTILLERY};
- _cooldown = HC_ARTILLERY_COOLDOWN;
- };
- };
- (time - _lastSupport >= _cooldown)
- };
- // Check if support can be afforded
- fnc_canAffordSupport = {
- params ["_side"];
- private _points = if (_side == west) then {missionNamespace getVariable ["BLUFOR_POINTS", 0]} else {missionNamespace getVariable ["OPFOR_POINTS", 0]};
- (_points >= HC_SUPPORT_COST)
- };
- // Deduct support cost
- fnc_deductSupportCost = {
- params ["_side"];
- if (_side == west) then {
- BLUFOR_POINTS = (missionNamespace getVariable ["BLUFOR_POINTS", 0]) - HC_SUPPORT_COST;
- publicVariable "BLUFOR_POINTS";
- } else {
- OPFOR_POINTS = (missionNamespace getVariable ["OPFOR_POINTS", 0]) - HC_SUPPORT_COST;
- publicVariable "OPFOR_POINTS";
- };
- };
- // Call flare support
- fnc_callFlareSupport = {
- params ["_side", "_targetPos"];
- if (!([_side, "FLARE"] call fnc_isSupportAvailable)) exitWith { false };
- if (!([_side] call fnc_canAffordSupport)) exitWith { false };
- [_side] call fnc_deductSupportCost;
- private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
- systemChat format ["GLOBAL ALERT: %1 has called for Illumination support.", _sideName];
- [
- _side,
- "Support Command",
- format["Illumination mission requested over grid %1.", mapGridPosition _targetPos]
- ] remoteExecCall ["sideChat", _side];
- if (_side == west) then {
- BLUFOR_LAST_FLARE = time;
- } else {
- OPFOR_LAST_FLARE = time;
- };
- [_side, _targetPos, "mil_dot", "Flare Illumination"] call fnc_HC_createSupportMarker;
- [_targetPos] spawn {
- params ["_pos"];
- sleep 3; // Small delay for realism
- private _flarePos = [(_pos select 0), (_pos select 1), 250];
- private _flareShell = "F_40mm_White" createVehicle _flarePos;
- _flareShell setPosASL _flarePos;
- _flareShell setVelocity [0, 0, -5];
- };
- true
- };
- // Call smoke support
- fnc_callSmokeSupport = {
- params ["_side", "_targetPos"];
- if (!([_side, "SMOKE"] call fnc_isSupportAvailable)) exitWith { false };
- if (!([_side] call fnc_canAffordSupport)) exitWith { false };
- [_side] call fnc_deductSupportCost;
- private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
- systemChat format ["GLOBAL ALERT: %1 has called for Smoke Screen support.", _sideName];
- [
- _side,
- "Support Command",
- format["Smoke cover requested for friendlies at grid %1.", mapGridPosition _targetPos]
- ] remoteExecCall ["sideChat", _side];
- if (_side == west) then {
- BLUFOR_LAST_SMOKE = time;
- } else {
- OPFOR_LAST_SMOKE = time;
- };
- [_side, _targetPos, "mil_dot", "Smoke Cover"] call fnc_HC_createSupportMarker;
- [_targetPos, _side] spawn {
- params ["_pos", "_side"];
- private _smokeRadius = 40;
- [
- _side,
- "Support Command",
- "Smoke cover inbound. ETA 10 seconds."
- ] remoteExecCall ["sideChat", _side];
- sleep 10;
- [
- _side,
- "Support Command",
- "SPLASH! Smoke rounds impacting."
- ] remoteExecCall ["sideChat", _side];
- for "_i" from 1 to HC_SMOKE_ROUNDS do {
- private _roundPos = _pos vectorAdd [
- (random _smokeRadius) - (_smokeRadius / 2),
- (random _smokeRadius) - (_smokeRadius / 2),
- 0
- ];
- private _smoke = createVehicle ["SmokeShellArty", _roundPos, [], 0, "CAN_COLLIDE"];
- _smoke setPos _roundPos;
- sleep (random 2);
- };
- [
- _side,
- "Support Command",
- "Smoke screen deployed for friendly cover."
- ] remoteExecCall ["sideChat", _side];
- };
- true
- };
- // Call recon drone support
- fnc_callReconDroneSupport = {
- params ["_side", "_targetPos"];
- if (time < 600) exitWith { false };
- if (!([_side, "DRONE"] call fnc_isSupportAvailable)) exitWith { false };
- if (!([_side] call fnc_canAffordSupport)) exitWith { false };
- // Check if target position is inside the play area
- private _playAreaCenter = missionNamespace getVariable ["PLAY_AREA_CENTER", [0,0,0]];
- private _playAreaRadius = missionNamespace getVariable ["PLAY_AREA_RADIUS", 0];
- if (_playAreaRadius > 0 && (_targetPos distance2D _playAreaCenter > _playAreaRadius)) exitWith { false }; // Don't call drones outside the battle zone
- [_side] call fnc_deductSupportCost;
- private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
- systemChat format ["GLOBAL ALERT: %1 has deployed Recon Drone support.", _sideName];
- [
- _side,
- "Support Command",
- format["Recon Drone en route to grid %1.", mapGridPosition _targetPos]
- ] remoteExecCall ["sideChat", _side];
- if (_side == west) then {
- BLUFOR_LAST_DRONE = time;
- } else {
- OPFOR_LAST_DRONE = time;
- };
- private _basePos = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _droneClass = if (_side == west) then {"B_UAV_01_F"} else {"O_UAV_01_F"};
- private _enemySide = if (_side == west) then {east} else {west};
- private _droneSpawnPos = _basePos vectorAdd [0, 0, 500];
- private _drone = createVehicle [_droneClass, _droneSpawnPos, [], 0, "FLY"];
- createVehicleCrew _drone;
- _drone flyInHeight 300;
- private _droneGroup = group _drone;
- private _wpToTarget = _droneGroup addWaypoint [_targetPos, 0];
- _wpToTarget setWaypointType "MOVE";
- _wpToTarget setWaypointBehaviour "SAFE";
- _wpToTarget setWaypointSpeed "NORMAL";
- private _loiterWp = _droneGroup addWaypoint [_targetPos, 0];
- _loiterWp setWaypointType "LOITER";
- _loiterWp setWaypointLoiterType "CIRCLE_L";
- _loiterWp setWaypointLoiterRadius 500;
- _loiterWp setWaypointBehaviour "SAFE";
- _loiterWp setWaypointSpeed "NORMAL";
- // MODIFIED: The drone will now loiter and spot indefinitely until destroyed.
- [_drone, _targetPos, _enemySide] spawn {
- params ["_drone", "_targetPos", "_enemySide"];
- while {!isNull _drone && alive _drone} do {
- private _nearEnemies = _targetPos nearEntities [["CAManBase", "LandVehicle", "Air"], 1000] select {side _x == _enemySide && alive _x};
- { _drone reveal [_x, 4]; } forEach _nearEnemies;
- sleep 5; // Check for enemies every 5 seconds
- };
- };
- true
- };
- // Call artillery support
- fnc_callArtillerySupport = {
- params ["_side", "_targetPos"];
- if (!([_side, "ARTILLERY"] call fnc_isSupportAvailable)) exitWith { false };
- if (!([_side] call fnc_canAffordSupport)) exitWith { false };
- // Check if target is inside enemy base area
- private _enemyBaseMarker = if (_side == west) then {"opfor_base_area"} else {"blufor_base_area"};
- private _enemyBasePos = markerPos _enemyBaseMarker;
- private _enemyBaseSize = (markerSize _enemyBaseMarker) select 0;
- if (_targetPos distance2D _enemyBasePos < _enemyBaseSize) exitWith {
- false // Don't allow mortars inside enemy base area
- };
- // Check for friendlies in target area
- private _nearbyFriendlies = _targetPos nearEntities [["CAManBase", "LandVehicle"], 50];
- private _friendliesInArea = _nearbyFriendlies select {side _x == _side && alive _x};
- if (count _friendliesInArea > 0) exitWith {
- false // Don't allow mortars on friendlies
- };
- [_side] call fnc_deductSupportCost;
- private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
- systemChat format ["GLOBAL ALERT: %1 has called for Mortar support.", _sideName];
- [
- _side,
- "Support Command",
- format["Mortar fire mission requested on grid %1.", mapGridPosition _targetPos]
- ] remoteExecCall ["sideChat", _side];
- if (_side == west) then {
- BLUFOR_LAST_ARTILLERY = time;
- } else {
- OPFOR_LAST_ARTILLERY = time;
- };
- [_side, _targetPos, "mil_destroy", "Mortar Strike"] call fnc_HC_createSupportMarker;
- [_targetPos, _side] spawn {
- params ["_pos", "_side"];
- // Spawn red smoke first
- [
- _side,
- "Support Command",
- "Marking target with red smoke. Mortars inbound."
- ] remoteExecCall ["sideChat", _side];
- private _smoke = createVehicle ["SmokeShellRed", _pos, [], 0, "CAN_COLLIDE"];
- _smoke setPos _pos;
- // Wait 10 seconds after smoke
- sleep 10;
- [
- _side,
- "Support Command",
- "SPLASH! Mortar rounds impacting."
- ] remoteExecCall ["sideChat", _side];
- // Fire 10 mortar rounds over 30 seconds in a 20-40 meter area
- for "_i" from 1 to 10 do {
- private _impactRadius = 20 + (random 20); // 20-40 meter radius
- private _roundPos = _pos vectorAdd [
- (random (_impactRadius * 2)) - _impactRadius,
- (random (_impactRadius * 2)) - _impactRadius,
- 0
- ];
- // Create mortar shell
- private _shell = "Sh_82mm_AMOS" createVehicle [_roundPos select 0, _roundPos select 1, 200];
- _shell setVectorDir [0, 0, -1];
- _shell setVelocity [0, 0, -100];
- sleep 3; // 30 seconds / 10 rounds = 3 seconds between rounds
- };
- [
- _side,
- "Support Command",
- "Mortar mission complete. BDA to follow."
- ] remoteExecCall ["sideChat", _side];
- };
- true
- };
- // ==================== INTEL OPTIMIZATION START ====================
- // MODIFIED: This function now reads from the new, lightweight tactical report queue.
- fnc_evaluateSupportNeed = {
- params ["_side"];
- private _supportToCall = "";
- if (!([_side] call fnc_canAffordSupport)) exitWith {_supportToCall};
- // OPTIMIZATION: Read from the fast report queue instead of the state machine.
- private _recentReports = if (_side == west) then {BLUFOR_TACTICAL_REPORTS} else {OPFOR_TACTICAL_REPORTS};
- // Clean out stale reports from the queue (older than 5 minutes)
- for "_i" from (count _recentReports - 1) to 0 step -1 do {
- private _report = _recentReports select _i;
- if (time - (_report select 0) > 300) then {
- _recentReports deleteAt _i;
- };
- };
- private _heavyContactReports = count _recentReports;
- private _recentCombatAreas = [];
- { _recentCombatAreas pushBackUnique (_x select 2); } forEach _recentReports;
- // Check for friendly casualties to call smoke
- private _friendlyGroups = allGroups select {side _x == _side && count (units _x) > 0};
- private _casualtyAreas = [];
- {
- private _group = _x;
- private _casualties = {damage _x > 0.3} count (units _group);
- if (_casualties >= 2 && behaviour leader _group == "COMBAT") then {
- _casualtyAreas pushBack (getPos leader _group);
- };
- } forEach _friendlyGroups;
- // Priority 1: Drone for reconnaissance
- if ([_side, "DRONE"] call fnc_isSupportAvailable) then {
- _supportToCall = "DRONE";
- };
- // Priority 2: Artillery on any enemy concentration
- if (_supportToCall == "" && _heavyContactReports > 0) then {
- if ([_side, "ARTILLERY"] call fnc_isSupportAvailable) then {
- _supportToCall = "ARTILLERY";
- };
- };
- // Priority 3: Smoke for friendly casualties
- if (_supportToCall == "") then {
- if (count _casualtyAreas > 0 && ([_side, "SMOKE"] call fnc_isSupportAvailable)) then {
- _supportToCall = "SMOKE";
- };
- };
- // Priority 4: Flares for night operations
- if (_supportToCall == "") then {
- if (sunOrMoon < 0.1 && ([_side, "FLARE"] call fnc_isSupportAvailable)) then {
- if (count _recentCombatAreas > 0) then {
- _supportToCall = "FLARE";
- };
- };
- };
- _supportToCall
- };
- // ===================== INTEL OPTIMIZATION END =====================
- // Cluster positions for analysis
- fnc_clusterPositions = {
- params ["_positions", "_clusterRadius"];
- private _clusters = [];
- private _unclustered = +_positions;
- while {count _unclustered > 0} do {
- private _seed = _unclustered select 0;
- private _cluster = [_seed];
- _unclustered = _unclustered - [_seed];
- private _toCheck = +_cluster;
- while {count _toCheck > 0} do {
- private _current = _toCheck select 0;
- _toCheck = _toCheck - [_current];
- private _nearby = _unclustered select {_x distance _current < _clusterRadius};
- _cluster append _nearby;
- _unclustered = _unclustered - _nearby;
- _toCheck append _nearby;
- };
- _clusters pushBack _cluster;
- };
- _clusters
- };
- // Evaluate terrain cover quality at a position
- fnc_evaluateTerrainCover = {
- params ["_pos"];
- private _coverScore = 0;
- private _coverType = "NONE";
- // Check for buildings (best hard cover)
- private _nearBuildings = nearestObjects [_pos, ["House", "Building"], 30];
- if (count _nearBuildings > 0) then {
- _coverScore = _coverScore + 50;
- _coverType = "BUILDING";
- };
- // Check for walls and fortifications
- private _nearWalls = nearestTerrainObjects [_pos, ["WALL", "FENCE"], 20];
- if (count _nearWalls > 0) then {
- _coverScore = _coverScore + 30;
- if (_coverType == "NONE") then { _coverType = "WALL"; };
- };
- // Check for natural cover (trees, rocks, bushes)
- private _nearTrees = nearestTerrainObjects [_pos, ["TREE", "SMALL TREE"], 15];
- private _nearRocks = nearestTerrainObjects [_pos, ["ROCK", "ROCKS"], 15];
- private _nearBushes = nearestTerrainObjects [_pos, ["BUSH"], 10];
- private _naturalCoverCount = (count _nearTrees) + (count _nearRocks) + (count _nearBushes);
- if (_naturalCoverCount > 0) then {
- _coverScore = _coverScore + (_naturalCoverCount * 5);
- if (_coverType == "NONE") then { _coverType = "FOREST"; };
- };
- // Check for defilade (terrain masking)
- private _terrainHeight = getTerrainHeightASL _pos;
- private _surroundingHeights = [];
- for "_angle" from 0 to 315 step 45 do {
- private _checkPos = _pos getPos [30, _angle];
- _surroundingHeights pushBack (getTerrainHeightASL _checkPos);
- };
- private _avgSurroundingHeight = 0;
- {_avgSurroundingHeight = _avgSurroundingHeight + _x;} forEach _surroundingHeights;
- _avgSurroundingHeight = _avgSurroundingHeight / count _surroundingHeights;
- // If position is lower than surroundings (in defilade)
- if (_terrainHeight < (_avgSurroundingHeight - 2)) then {
- _coverScore = _coverScore + 25;
- if (_coverType == "NONE") then { _coverType = "DEFILADE"; };
- };
- // Check for forest canopy using selectBestPlaces
- private _forestDensity = selectBestPlaces [_pos, 50, "forest", 1, 1];
- if (count _forestDensity > 0) then {
- private _density = (_forestDensity select 0) select 1;
- _coverScore = _coverScore + (_density * 20);
- if (_coverType == "NONE") then { _coverType = "FOREST"; };
- };
- // Return cover assessment
- private _assessment = createHashMap;
- _assessment set ["score", _coverScore];
- _assessment set ["type", _coverType];
- _assessment set ["hasCover", _coverScore > 20];
- _assessment
- };
- // Assess threat environment between two positions
- fnc_assessThreatEnvironment = {
- params ["_startPos", "_endPos", "_side"];
- private _enemySide = if (_side == west) then {east} else {west};
- private _midPoint = [
- ((_startPos select 0) + (_endPos select 0)) / 2,
- ((_startPos select 1) + (_endPos select 1)) / 2,
- 0
- ];
- // Check for enemy presence along route
- private _routeLength = _startPos distance2D _endPos;
- private _searchRadius = (_routeLength / 2) max 300;
- private _nearbyEnemies = _midPoint nearEntities [["CAManBase", "LandVehicle", "Air"], _searchRadius];
- private _enemyCount = {side _x == _enemySide && alive _x} count _nearbyEnemies;
- // Check intel for known enemy positions
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _recentEnemyReports = 0;
- {
- private _report = _y;
- private _age = time - (_report get "time");
- if (_age < 60) then {
- private _reportPos = _report get "position";
- if ((_reportPos distance2D _midPoint) < _searchRadius) then {
- _recentEnemyReports = _recentEnemyReports + 1;
- };
- };
- } forEach _intel;
- // Calculate exposure score
- private _exposureScore = 0;
- // Sample points along route to check cover
- private _routeSamples = 5;
- private _totalCover = 0;
- for "_i" from 0 to (_routeSamples - 1) do {
- private _fraction = _i / _routeSamples;
- private _samplePos = [
- (_startPos select 0) + ((_endPos select 0) - (_startPos select 0)) * _fraction,
- (_startPos select 1) + ((_endPos select 1) - (_startPos select 1)) * _fraction,
- 0
- ];
- private _coverData = [_samplePos] call fnc_evaluateTerrainCover;
- _totalCover = _totalCover + (_coverData get "score");
- };
- private _avgCover = _totalCover / _routeSamples;
- // High exposure if little cover
- if (_avgCover < 20) then {
- _exposureScore = _exposureScore + 40;
- } else {
- if (_avgCover < 50) then {
- _exposureScore = _exposureScore + 20;
- };
- };
- // Threat level calculation
- private _threatLevel = "LOW";
- private _threatScore = _enemyCount + _recentEnemyReports + (_exposureScore / 10);
- if (_threatScore > 15) then {
- _threatLevel = "EXTREME";
- } else {
- if (_threatScore > 10) then {
- _threatLevel = "HIGH";
- } else {
- if (_threatScore > 5) then {
- _threatLevel = "MEDIUM";
- };
- };
- };
- // Return threat assessment
- private _assessment = createHashMap;
- _assessment set ["threatLevel", _threatLevel];
- _assessment set ["threatScore", _threatScore];
- _assessment set ["enemyCount", _enemyCount];
- _assessment set ["exposureScore", _exposureScore];
- _assessment set ["avgCover", _avgCover];
- _assessment
- };
- // Select waypoints that maximize cover along a route
- fnc_selectCoveredWaypoints = {
- params ["_startPos", "_targetPos", "_groupType"];
- private _waypoints = [];
- private _distance = _startPos distance2D _targetPos;
- // For short distances, go direct
- if (_distance < 300) exitWith {
- [_targetPos]
- };
- // Calculate number of waypoints based on distance
- private _waypointCount = (floor (_distance / 400)) max 2;
- _waypointCount = _waypointCount min 6;
- // Generate candidate waypoints along corridor
- private _dirToTarget = _startPos getDir _targetPos;
- private _segmentLength = _distance / (_waypointCount + 1);
- for "_i" from 1 to _waypointCount do {
- private _baseDistance = _i * _segmentLength;
- private _basePos = _startPos getPos [_baseDistance, _dirToTarget];
- // Search for covered positions in area around base position
- private _searchRadius = 200;
- private _bestPos = _basePos;
- private _bestCoverScore = 0;
- // Sample 8 positions in a circle
- for "_angle" from 0 to 315 step 45 do {
- private _testPos = _basePos getPos [random _searchRadius, _angle];
- // Validate position
- if (!surfaceIsWater _testPos) then {
- // Check cover at this position
- private _coverData = [_testPos] call fnc_evaluateTerrainCover;
- private _coverScore = _coverData get "score";
- // Prefer positions with good cover
- if (_coverScore > _bestCoverScore) then {
- _bestCoverScore = _coverScore;
- _bestPos = _testPos;
- };
- };
- };
- // Use BIS_fnc_findSafePos to validate final position
- private _safePos = [_bestPos, 0, 50, 5, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _safePos > 0 && !surfaceIsWater _safePos) then {
- _waypoints pushBack _safePos;
- } else {
- _waypoints pushBack _bestPos;
- };
- };
- _waypoints
- };
- // Determine appropriate movement behavior based on terrain and threat
- fnc_determineMovementBehavior = {
- params ["_group", "_currentPos", "_targetPos", "_side"];
- // Assess threat environment
- private _threatData = [_currentPos, _targetPos, _side] call fnc_assessThreatEnvironment;
- private _threatLevel = _threatData get "threatLevel";
- private _avgCover = _threatData get "avgCover";
- // Assess cover at current position
- private _currentCover = [_currentPos] call fnc_evaluateTerrainCover;
- private _hasCover = _currentCover get "hasCover";
- // Determine behavior
- private _behavior = "AWARE";
- private _speed = "NORMAL";
- private _formation = "LINE";
- private _combatMode = "YELLOW";
- // Classify group type
- private _groupType = [_group] call fnc_classifyGroup;
- switch (_threatLevel) do {
- case "EXTREME": {
- if (_avgCover > 40) then {
- // Good cover available - use stealth
- _behavior = "STEALTH";
- _speed = "LIMITED";
- _formation = "FILE";
- _combatMode = "GREEN";
- } else {
- // Poor cover - aggressive movement
- _behavior = "COMBAT";
- _speed = "FULL";
- _formation = "LINE";
- _combatMode = "RED";
- };
- };
- case "HIGH": {
- if (_avgCover > 30) then {
- _behavior = "AWARE";
- _speed = "LIMITED";
- _formation = "STAG COLUMN";
- _combatMode = "YELLOW";
- } else {
- _behavior = "COMBAT";
- _speed = "NORMAL";
- _formation = "LINE";
- _combatMode = "RED";
- };
- };
- case "MEDIUM": {
- _behavior = "AWARE";
- _speed = "NORMAL";
- _formation = "COLUMN";
- _combatMode = "YELLOW";
- };
- case "LOW": {
- _behavior = "SAFE";
- _speed = "NORMAL";
- _formation = "COLUMN";
- _combatMode = "YELLOW";
- };
- };
- // Special considerations for group types
- if (_groupType in ["SNIPER", "SPECOPS", "ELITE"]) then {
- // Elite units prefer stealth when cover is available
- if (_avgCover > 25) then {
- _behavior = "STEALTH";
- _speed = "LIMITED";
- _formation = "FILE";
- };
- };
- if (_groupType in ["ARMOR", "MECHANIZED"]) then {
- // Vehicles are less affected by cover, more aggressive
- _speed = "NORMAL";
- if (_threatLevel in ["HIGH", "EXTREME"]) then {
- _behavior = "COMBAT";
- _combatMode = "RED";
- };
- };
- // Return movement parameters
- private _params = createHashMap;
- _params set ["behavior", _behavior];
- _params set ["speed", _speed];
- _params set ["formation", _formation];
- _params set ["combatMode", _combatMode];
- _params
- };
- // Find tactical positions on the battlefield (high ground, flanking routes, defensive positions)
- fnc_findTacticalPosition = {
- params ["_centerPos", "_role", "_friendlyBase", "_enemyBase"];
- private _position = [];
- private _rawPosition = [];
- private _playAreaCenter = missionNamespace getVariable ["PLAY_AREA_CENTER", [worldSize/2, worldSize/2, 0]];
- private _playAreaRadius = missionNamespace getVariable ["PLAY_AREA_RADIUS", 5000];
- switch (_role) do {
- case "HIGH_GROUND": {
- // Find elevated positions near the battle
- private _bestHeight = -999;
- for "_i" from 0 to 8 do {
- private _testPos = _centerPos getPos [random 800, random 360];
- private _height = getTerrainHeightASL _testPos;
- // Check if position is within play area
- if (_height > _bestHeight && !surfaceIsWater _testPos && (_testPos distance2D _playAreaCenter) < _playAreaRadius) then {
- _bestHeight = _height;
- _position = _testPos;
- };
- };
- };
- case "FLANK_LEFT": {
- // Position to the left of the line between friendly and enemy base
- private _dirToEnemy = _friendlyBase getDir _enemyBase;
- private _flankDir = _dirToEnemy - 90;
- private _distance = ((_friendlyBase distance _enemyBase) * 0.6) min (_playAreaRadius - 100);
- _rawPosition = _centerPos getPos [_distance, _flankDir];
- };
- case "FLANK_RIGHT": {
- // Position to the right of the line between friendly and enemy base
- private _dirToEnemy = _friendlyBase getDir _enemyBase;
- private _flankDir = _dirToEnemy + 90;
- private _distance = ((_friendlyBase distance _enemyBase) * 0.6) min (_playAreaRadius - 100);
- _rawPosition = _centerPos getPos [_distance, _flankDir];
- };
- case "DEFENSIVE": {
- // Position between friendly base and battle center
- private _dirToBase = _centerPos getDir _friendlyBase;
- private _distance = ((_centerPos distance _friendlyBase) * 0.4) min (_playAreaRadius - 100);
- _rawPosition = _centerPos getPos [_distance, _dirToBase];
- };
- case "FORWARD": {
- // Position toward enemy base
- private _dirToEnemy = _centerPos getDir _enemyBase;
- private _distance = (400 + random 200) min (_playAreaRadius - 100);
- _rawPosition = _centerPos getPos [_distance, _dirToEnemy];
- };
- case "SUPPORT": {
- // Position behind the main battle line
- private _dirToBase = _centerPos getDir _friendlyBase;
- private _distance = (300 + random 200) min (_playAreaRadius - 100);
- _rawPosition = _centerPos getPos [_distance, _dirToBase];
- };
- };
- // MODIFIED: Centralized validation for all roles except HIGH_GROUND
- if (count _rawPosition > 0) then {
- _position = [_rawPosition, 50, 200, 10, 0, 0.4, 0] call BIS_fnc_findSafePos;
- };
- // Ensure position is valid and within play area
- if (count _position == 0 || (_position distance2D _playAreaCenter) > _playAreaRadius) then {
- _position = _centerPos;
- };
- _position
- };
- // Create a comprehensive battle plan based on available forces and intel
- fnc_createBattlePlan = {
- params ["_side", "_availableGroups", "_enemyBase", "_ownBase"];
- private _battlePlan = createHashMap;
- // Get intel to determine battle center
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _enemyPositions = [];
- {
- private _report = _y;
- private _age = time - (_report get "time");
- if (_age < 120) then {
- _enemyPositions pushBack (_report get "position");
- };
- } forEach _intel;
- // Determine battle center
- private _battleCenter = _enemyBase;
- if (count _enemyPositions > 0) then {
- private _avgX = 0; private _avgY = 0;
- {_avgX = _avgX + (_x select 0); _avgY = _avgY + (_x select 1);} forEach _enemyPositions;
- _battleCenter = [_avgX / count _enemyPositions, _avgY / count _enemyPositions, 0];
- };
- // OPTIMIZATION: Pre-allocate arrays for group classification
- private _infantryGroups = [];
- private _armorGroups = [];
- private _mechanizedGroups = [];
- private _airGroups = [];
- private _specialGroups = [];
- // OPTIMIZATION: Classify groups with caching
- {
- private _group = _x;
- // Check if classification is cached and still valid
- private _cachedType = _group getVariable ["classifiedType", ""];
- private _cacheTime = _group getVariable ["classificationTime", 0];
- // Cache is valid for 60 seconds
- if (_cachedType == "" || (time - _cacheTime) > 60) then {
- _cachedType = [_group] call fnc_classifyGroup;
- _group setVariable ["classifiedType", _cachedType];
- _group setVariable ["classificationTime", time];
- };
- // Use cached type for categorization
- switch (_cachedType) do {
- case "INFANTRY": {_infantryGroups pushBack _group;};
- case "ARMOR": {_armorGroups pushBack _group;};
- case "MECHANIZED": {_mechanizedGroups pushBack _group;};
- case "AIR": {_airGroups pushBack _group;};
- case "SNIPER";
- case "SPECOPS";
- case "ELITE": {_specialGroups pushBack _group;};
- };
- } forEach _availableGroups;
- // Calculate force strength
- private _totalInfantry = count _infantryGroups;
- private _totalArmor = count _armorGroups;
- private _totalMech = count _mechanizedGroups;
- // Initialize battle plan structure
- _battlePlan set ["attackers", []];
- _battlePlan set ["defenders", []];
- _battlePlan set ["leftFlank", []];
- _battlePlan set ["rightFlank", []];
- _battlePlan set ["reserve", []];
- _battlePlan set ["support", []];
- _battlePlan set ["battleCenter", _battleCenter];
- // Defense: Keep some forces back if under pressure
- private _enemiesNearBase = [_side, 800] call fnc_enemiesNearBase;
- if (_enemiesNearBase) then {
- private _defenderCount = (count _availableGroups * 0.3) max 2;
- for "_i" from 0 to (_defenderCount min (count _infantryGroups) - 1) do {
- (_battlePlan get "defenders") pushBack (_infantryGroups select _i);
- };
- _infantryGroups = _infantryGroups - (_battlePlan get "defenders");
- };
- // Special forces: Flanking and harassment
- {
- if ((count (_battlePlan get "leftFlank")) < 2) then {
- (_battlePlan get "leftFlank") pushBack _x;
- } else {
- (_battlePlan get "rightFlank") pushBack _x;
- };
- } forEach _specialGroups;
- // Armor: Main assault force
- {
- (_battlePlan get "attackers") pushBack _x;
- } forEach _armorGroups;
- // Mechanized: Support assault or flanking
- private _mechCount = count _mechanizedGroups;
- for "_i" from 0 to (_mechCount - 1) do {
- private _group = _mechanizedGroups select _i;
- if (_i % 2 == 0) then {
- (_battlePlan get "attackers") pushBack _group;
- } else {
- if ((count (_battlePlan get "leftFlank")) <= (count (_battlePlan get "rightFlank"))) then {
- (_battlePlan get "leftFlank") pushBack _group;
- } else {
- (_battlePlan get "rightFlank") pushBack _group;
- };
- };
- };
- // OPTIMIZATION: Simplified infantry distribution using direct calculations
- private _infCount = count _infantryGroups;
- if (_infCount > 0) then {
- private _attackerCount = ceil (_infCount * 0.4);
- private _flankCount = ceil (_infCount * 0.3);
- // Attackers
- for "_i" from 0 to (_attackerCount - 1) do {
- if (_i < _infCount) then {
- (_battlePlan get "attackers") pushBack (_infantryGroups select _i);
- };
- };
- // Flankers
- for "_i" from _attackerCount to (_attackerCount + _flankCount - 1) do {
- if (_i < _infCount) then {
- if ((_i - _attackerCount) % 2 == 0) then {
- (_battlePlan get "leftFlank") pushBack (_infantryGroups select _i);
- } else {
- (_battlePlan get "rightFlank") pushBack (_infantryGroups select _i);
- };
- };
- };
- // Reserve
- for "_i" from (_attackerCount + _flankCount) to (_infCount - 1) do {
- (_battlePlan get "reserve") pushBack (_infantryGroups select _i);
- };
- };
- // Air units: Support role
- {
- (_battlePlan get "support") pushBack _x;
- } forEach _airGroups;
- _battlePlan
- };
- // Execute a specific role for a group with appropriate waypoints
- fnc_executeGroupRole = {
- params ["_group", "_role", "_battleCenter", "_ownBase", "_enemyBase"];
- private _groupType = [_group] call fnc_classifyGroup;
- private _targetPos = [0,0,0];
- private _finalOrder = _role; // Default to role name if no specific override
- switch (_role) do {
- case "ATTACK": {
- _targetPos = [_battleCenter, "FORWARD", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- // Attack logic remains standard
- _finalOrder = "ATTACK";
- _group setVariable ["HC_ROLE", "ATTACKER", true];
- };
- case "DEFEND": {
- _targetPos = [_battleCenter, "DEFENSIVE", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- // Intelligent Defense: Check if enemies are actually near the position
- private _enemiesNear = (_targetPos nearEntities [["CAManBase", "LandVehicle"], 600]) findIf {side _x != side _group && alive _x} != -1;
- if (_enemiesNear) then {
- // Active combat nearby: Use standard DEFEND (SAD cycle)
- _finalOrder = "DEFEND";
- _group setVariable ["HC_ROLE", "DEFENDER", true];
- } else {
- // No immediate threat: Use passive GUARD or SENTRY waypoints
- // 50/50 chance to Guard (Patrol/Engage) or Sentry (Hold until spotted)
- if (random 1 > 0.5) then {
- _finalOrder = "GUARD";
- _group setVariable ["HC_ROLE", "GUARD", true];
- } else {
- _finalOrder = "SENTRY";
- _group setVariable ["HC_ROLE", "SENTRY", true];
- };
- };
- };
- case "FLANK_LEFT": {
- _targetPos = [_battleCenter, "FLANK_LEFT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- _finalOrder = "FLANK_LEFT";
- _group setVariable ["HC_ROLE", "FLANKER", true];
- };
- case "FLANK_RIGHT": {
- _targetPos = [_battleCenter, "FLANK_RIGHT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- _finalOrder = "FLANK_RIGHT";
- _group setVariable ["HC_ROLE", "FLANKER", true];
- };
- case "RESERVE": {
- _targetPos = [_battleCenter, "SUPPORT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- // Reserves should HOLD position until called upon
- _finalOrder = "HOLD";
- _group setVariable ["HC_ROLE", "RESERVE", true];
- };
- case "SUPPORT": {
- if (_groupType == "AIR") then {
- // Air units patrol/loiter
- _targetPos = _battleCenter;
- _finalOrder = "SUPPORT_AIR";
- } else {
- _targetPos = [_battleCenter, "HIGH_GROUND", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- // Ground support (like mortars/HMG teams) should HOLD high ground or SENTRY
- _finalOrder = "SENTRY";
- };
- _group setVariable ["HC_ROLE", "SUPPORT", true];
- };
- };
- // Pass the calculated specific order and target to the assignment function
- [_group, _targetPos, _finalOrder, _groupType] call fnc_assignGroupObjective;
- };
- // Get strategic groups
- fnc_getStrategicGroups = {
- params ["_side"];
- allGroups select {
- (side _x == _side) &&
- (count (units _x) > 0) &&
- ({isPlayer _x} count (units _x) == 0)
- };
- };
- // Check for enemies near base
- fnc_enemiesNearBase = {
- params ["_side", ["_radius", 500]];
- private _ownBasePos = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _enemySide = if (_side == west) then {east} else {west};
- private _nearbyEnemyUnits = _ownBasePos nearEntities [["CAManBase", "LandVehicle", "Air"], _radius];
- private _firstEnemy = _nearbyEnemyUnits findIf {alive _x && side _x == _enemySide};
- (_firstEnemy != -1)
- };
- // Check if group is locked
- fnc_isGroupLocked = {
- params ["_group"];
- if (isNull _group || {count (units _group) == 0}) exitWith {false};
- if (_group getVariable ["HC_FORCED_LOCK", false]) exitWith {true};
- private _currentTime = time;
- private _assignmentTime = _group getVariable ["HC_ASSIGNMENT_TIME", 0];
- if (_assignmentTime == 0) exitWith {false};
- if (_currentTime - _assignmentTime < HC_LOCK_DURATION) exitWith {true};
- if (_currentTime - (_group getVariable ["HC_LAST_COMBAT", 0]) < HC_COMBAT_LOCK_TIME) exitWith {true};
- false
- };
- // Get available groups
- fnc_getAvailableGroups = {
- params ["_side"];
- private _allGroups = [_side] call fnc_getStrategicGroups;
- private _availableGroups = [];
- {
- if !([_x] call fnc_isGroupLocked) then {
- _availableGroups pushBack _x;
- _x setVariable ["HC_ASSIGNMENT_TIME", nil];
- _x setVariable ["HC_TARGET", nil];
- _x setVariable ["HC_ORDER", nil];
- HC_Active_Groups deleteAt (groupId _x);
- };
- } forEach _allGroups;
- _availableGroups
- };
- // Assign group objective
- fnc_assignGroupObjective = {
- params ["_group", "_targetPos", "_orderType", ["_groupType", "INFANTRY"]];
- if (typeName _targetPos != "ARRAY" || count _targetPos < 2) exitWith {
- diag_log format ["ERROR: Invalid target position for group %1: %2", groupId _group, _targetPos];
- };
- _group setVariable ["HC_ASSIGNMENT_TIME", time];
- _group setVariable ["HC_TARGET", _targetPos];
- _group setVariable ["HC_ORDER", _orderType];
- _group setVariable ["HC_FORCED_LOCK", false];
- HC_Active_Groups set [groupId _group, _group];
- // Clear existing waypoints before assigning new vanilla tasks
- // (Note: ATTACK/FLANK/etc. handle their own cleanup inside called functions)
- switch (_orderType) do {
- case "ATTACK": {
- [_group, _targetPos, _groupType] call fnc_createTacticalPath;
- };
- case "DEFEND": {
- [_group, _targetPos] call fnc_createDefenseWaypoints;
- };
- case "FLANK_LEFT": {
- [_group, "FLANK_LEFT", _targetPos, _groupType] call fnc_createAdvancedWaypoints;
- };
- case "FLANK_RIGHT": {
- [_group, "FLANK_RIGHT", _targetPos, _groupType] call fnc_createAdvancedWaypoints;
- };
- case "SUPPORT_AIR": {
- // Assuming center of map or target area is handled by the specialized heli function
- private _centerPos = missionNamespace getVariable ["mission_centralPoint", _targetPos];
- [_group, _targetPos, _centerPos] call fnc_createHelicopterAttackWaypoints;
- };
- case "PATROL": {
- [_group, _targetPos] call fnc_createPatrolWaypoints;
- };
- case "RETREAT": {
- while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
- private _wp = _group addWaypoint [_targetPos, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "FULL";
- };
- // --- NEW VANILLA WAYPOINT TYPES ---
- case "GUARD": {
- while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
- private _wp = _group addWaypoint [_targetPos, 0];
- _wp setWaypointType "GUARD"; // AI will engage any detected enemies within range, then return
- _wp setWaypointBehaviour "COMBAT";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointCombatMode "RED";
- _wp setWaypointCompletionRadius 50;
- };
- case "SENTRY": {
- while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
- private _wp = _group addWaypoint [_targetPos, 0];
- _wp setWaypointType "SENTRY"; // AI holds until enemy seen, then proceeds (effectively wakes up)
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- _wp setWaypointCombatMode "YELLOW"; // Fire at will
- _wp setWaypointCompletionRadius 20;
- };
- case "HOLD": {
- while {count (waypoints _group) > 0} do { deleteWaypoint ((waypoints _group) select 0); };
- private _wp = _group addWaypoint [_targetPos, 0];
- _wp setWaypointType "HOLD"; // AI holds position strictly
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointCombatMode "YELLOW";
- _wp setWaypointFormation "DIAMOND"; // Good for all-around defense
- };
- default {
- [_group, _targetPos] call fnc_createPatrolWaypoints;
- };
- };
- };
- // Classify group type
- fnc_classifyGroup = {
- params ["_group"];
- private _units = units _group select {alive _x};
- if (count _units == 0) exitWith { "EMPTY" };
- // OPTIMIZATION: Check vehicles first (fastest check)
- private _leader = leader _group;
- if (!isNull _leader) then {
- private _vehicle = vehicle _leader;
- if (_vehicle != _leader) exitWith {
- // Unit is in a vehicle - classify by vehicle type
- if (_vehicle isKindOf "Air") exitWith { "AIR" };
- if (_vehicle isKindOf "Tank") exitWith { "ARMOR" };
- if ("APC" in typeOf _vehicle) exitWith { "MECHANIZED" };
- "MOTORIZED"
- };
- };
- // OPTIMIZATION: Check special unit flags first (most units won't have these)
- private _firstUnit = _units select 0;
- if (_firstUnit getVariable ["isSniper", false]) exitWith { "SNIPER" };
- if (_firstUnit getVariable ["isSpecOps", false]) exitWith { "SPECOPS" };
- if (_firstUnit getVariable ["isElite", false]) exitWith { "ELITE" };
- // OPTIMIZATION: Only do detailed composition check if no special flags found
- // Count special unit types
- private _sniperCount = 0;
- private _specOpsCount = 0;
- private _eliteCount = 0;
- {
- if (_x getVariable ["isSniper", false]) then { _sniperCount = _sniperCount + 1; };
- if (_x getVariable ["isSpecOps", false]) then { _specOpsCount = _specOpsCount + 1; };
- if (_x getVariable ["isElite", false]) then { _eliteCount = _eliteCount + 1; };
- // Early exit if we found enough special units
- if (_sniperCount >= 2) exitWith {};
- if (_specOpsCount >= 3) exitWith {};
- if (_eliteCount >= 3) exitWith {};
- } forEach _units;
- if (_sniperCount >= 2) exitWith { "SNIPER" };
- if (_specOpsCount >= 3) exitWith { "SPECOPS" };
- if (_eliteCount >= 3) exitWith { "ELITE" };
- // OPTIMIZATION: Only check unit types if needed for specialized roles
- private _atCount = 0;
- {
- private _typeStr = toLower (typeOf _x);
- if ("_at_" in _typeStr || "_lat_" in _typeStr) then {
- _atCount = _atCount + 1;
- if (_atCount >= 2) exitWith {}; // Early exit
- };
- } forEach _units;
- if (_atCount >= 2) exitWith { "ANTI_ARMOR" };
- // Default to infantry
- "INFANTRY"
- };
- // Calculate how detectable a target is based on movement and stance
- fnc_calculateTargetDetectability = {
- params ["_target"];
- private _velocity = velocity _target;
- private _speed = sqrt((_velocity select 0)^2 + (_velocity select 1)^2); // 2D speed in m/s
- private _stance = stance _target; // "STAND", "CROUCH", "PRONE", "UNDEFINED"
- // Base detectability by stance
- private _stanceMultiplier = 1.0;
- switch (_stance) do {
- case "STAND": { _stanceMultiplier = 1.0; };
- case "CROUCH": { _stanceMultiplier = 0.6; };
- case "PRONE": { _stanceMultiplier = 0.3; };
- default { _stanceMultiplier = 1.0; }; // Undefined or in vehicle
- };
- // Movement speed modifiers
- private _movementMultiplier = 1.0;
- if (_speed < 0.5) then {
- // Stationary or very slow
- _movementMultiplier = 0.5;
- } else {
- if (_speed < 2.5) then {
- // Slow walk/crouch walk
- _movementMultiplier = 0.7;
- } else {
- if (_speed < 4.5) then {
- // Normal walk
- _movementMultiplier = 0.85;
- } else {
- if (_speed < 6.5) then {
- // Fast walk / jog
- _movementMultiplier = 1.0;
- } else {
- // Sprint
- _movementMultiplier = 1.3;
- };
- };
- };
- };
- // NEW: Foliage concealment check
- private _foliageMultiplier = 1.0;
- private _targetPos = getPosATL _target;
- // Check for nearby foliage that could conceal the target
- private _nearFoliage = nearestTerrainObjects [_targetPos, ["BUSH", "TREE", "SMALL TREE"], 3, false, true];
- if (count _nearFoliage > 0) then {
- // Target is in/near foliage
- private _foliageCount = count _nearFoliage;
- if (_foliageCount >= 3) then {
- // Heavy foliage - significant concealment
- _foliageMultiplier = 0.3;
- } else {
- if (_foliageCount >= 2) then {
- // Moderate foliage
- _foliageMultiplier = 0.5;
- } else {
- // Light foliage
- _foliageMultiplier = 0.7;
- };
- };
- // Extra concealment bonus if prone or crouched in foliage
- if (_stance in ["PRONE", "CROUCH"]) then {
- _foliageMultiplier = _foliageMultiplier * 0.7;
- };
- // Penalty if moving fast through foliage (rustling bushes)
- if (_speed > 2.5) then {
- _foliageMultiplier = _foliageMultiplier * 1.4;
- };
- };
- // Combine all factors
- private _finalDetectability = _stanceMultiplier * _movementMultiplier * _foliageMultiplier;
- // Clamp between 0.05 and 1.3
- _finalDetectability = (_finalDetectability max 0.05) min 1.3;
- _finalDetectability
- };
- // Calculate spotter's detection skill
- fnc_calculateSpotterSkill = {
- params ["_spotter"];
- private _skillMultiplier = 1.0;
- // Elite troops are the best spotters
- if (_spotter getVariable ["isElite", false]) exitWith { 1.4 };
- // SpecOps and Snipers are excellent spotters
- if (_spotter getVariable ["isSpecOps", false]) exitWith { 1.3 };
- if (_spotter getVariable ["isSniper", false]) exitWith { 1.3 };
- // Upgraded troops are above average
- if (_spotter getVariable ["isUpgraded", false]) exitWith { 1.15 };
- // Standard infantry (default)
- 1.0
- };
- // ==================== INTEL OPTIMIZATION START ====================
- // MODIFIED: This function now also populates the new lightweight tactical report queue.
- HC_fnc_gatherIntelligence = {
- params ["_unit", "_side"];
- if (!alive _unit) exitWith {};
- // NEW: Prevent intel gathering while parachuting or in freefall
- private _vehicle = vehicle _unit;
- if (_vehicle != _unit && {_vehicle isKindOf "ParachuteBase"}) exitWith {};
- if ((getPosATL _unit select 2) > 5 && (velocity _unit select 2) < -5 && _vehicle == _unit) exitWith {};
- // Performance throttling - skip some spotting checks when server is struggling
- private _skipSpotting = missionNamespace getVariable ["PERF_SKIP_SPOTTING", false];
- if (_skipSpotting) then {
- private _spottingChance = missionNamespace getVariable ["PERF_SPOTTING_CHANCE", 1.0];
- if (random 1 > _spottingChance) exitWith {}; // Skip this unit's spotting check
- };
- private _enemySide = if (_side == west) then {east} else {west};
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _viewDistance = 500;
- if (_unit getVariable ["isElite", false]) then { _viewDistance = 1200; };
- if (_unit getVariable ["isSpecOps", false]) then { _viewDistance = 1200; };
- if (_unit getVariable ["isSniper", false]) then { _viewDistance = 1500; };
- if (vehicle _unit != _unit) then { _viewDistance = 1000; };
- // OPTIMIZATION: Use nearEntities with type filter to reduce initial search
- private _potentialEnemies = _unit nearEntities [["CAManBase", "LandVehicle", "Air"], _viewDistance];
- // OPTIMIZATION: Early exit if no potential enemies found
- if (count _potentialEnemies == 0) exitWith {};
- // OPTIMIZATION: Filter enemies with combined checks to reduce iterations
- private _nearEnemies = _potentialEnemies select {
- (side _x == _enemySide || (_x getVariable ["isAAF", false])) &&
- alive _x
- };
- // OPTIMIZATION: Early exit if no visible enemies
- if (count _nearEnemies == 0) exitWith {};
- if (behaviour _unit in ["COMBAT", "STEALTH"]) then {
- (group _unit) setVariable ["HC_LAST_COMBAT", time];
- };
- // OPTIMIZATION: Get tactical reports array once
- private _tacticalReports = if (_side == west) then {BLUFOR_TACTICAL_REPORTS} else {OPFOR_TACTICAL_REPORTS};
- {
- private _enemy = _x;
- // REPLACED: Threshold lowered from 1.5 to 0.1.
- // Any perception (> 0) counts as intel. If they are shooting, this is definitely > 0.1.
- if (_unit knowsAbout _enemy > 0.1) then {
- // Successfully spotted the enemy - gather intel
- private _enemyID = netId _enemy;
- // OPTIMIZATION: Determine entity type with early exit checks
- private _entityType = "INFANTRY";
- private _isTank = false;
- if !(_enemy isKindOf "CAManBase") then {
- if (_enemy isKindOf "Air") then {
- _entityType = "AIR_VEHICLE";
- } else {
- _entityType = "GROUND_VEHICLE";
- if (_enemy isKindOf "Tank") then {
- _isTank = true;
- };
- };
- };
- // OPTIMIZATION: Create report with minimal overhead
- private _intelReport = createHashMap;
- _intelReport set ["position", getPos _enemy];
- _intelReport set ["time", time];
- _intelReport set ["velocity", velocity _enemy];
- _intelReport set ["lastUpdate", time];
- _intelReport set ["type", typeOf _enemy];
- _intelReport set ["isMounted", vehicle _enemy != _enemy];
- _intelReport set ["entityType", _entityType];
- _intelReport set ["isTank", _isTank];
- _intelReport set ["isAAF", _enemy getVariable ["isAAF", false]];
- _intel set [_enemyID, _intelReport];
- // Add to tactical report queue
- _tacticalReports pushBack [time, _entityType, getPos _enemy, _isTank];
- // OPTIMIZATION: Cap array size inline (no separate check needed)
- if (count _tacticalReports > HC_REPORTS_MAX_SIZE) then {
- _tacticalReports deleteAt 0;
- };
- };
- } forEach _nearEnemies;
- };
- // Make AI aware of enemy presence in nearby intel areas without direct revelation
- HC_fnc_investigateNearbyIntel = {
- params ["_unit", "_side"];
- // Only process for group leaders to avoid redundant checks
- if (_unit != leader (group _unit)) exitWith {};
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _unitPos = getPosATL _unit;
- private _unitGroup = group _unit;
- // Don't interfere with locked groups or groups already in combat
- if ([_unitGroup] call fnc_isGroupLocked) exitWith {};
- if (behaviour _unit == "COMBAT") exitWith {};
- private _closestIntelPos = [];
- private _closestDistance = 9999;
- // Find closest recent intel within 500m
- {
- private _intelReport = _y;
- private _intelPos = _intelReport get "position";
- private _intelAge = time - (_intelReport get "time");
- // Only process recent intel (less than 90 seconds old)
- if (_intelAge < 90) then {
- private _distance = _unitPos distance2D _intelPos;
- if (_distance <= 500 && _distance < _closestDistance) then {
- _closestDistance = _distance;
- _closestIntelPos = _intelPos;
- };
- };
- } forEach _intel;
- // If we found nearby intel, make the group investigate
- if (count _closestIntelPos > 0) then {
- private _lastIntelReaction = _unitGroup getVariable ["lastIntelReaction", 0];
- // Only react every 60 seconds to avoid constant waypoint changes
- if (time - _lastIntelReaction > 60) then {
- _unitGroup setVariable ["lastIntelReaction", time];
- // Set group to combat-aware behavior
- _unitGroup setBehaviour "AWARE";
- _unitGroup setCombatMode "YELLOW";
- _unitGroup setSpeedMode "NORMAL";
- // Only add investigation waypoint if group doesn't have many waypoints already
- if (count (waypoints _unitGroup) < 2) then {
- // Add waypoint to investigate the intel area
- private _wp = _unitGroup addWaypoint [_closestIntelPos, 100];
- _wp setWaypointType "SAD"; // Search and Destroy - will engage if they find enemies
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointCombatMode "YELLOW";
- _wp setWaypointCompletionRadius 150;
- };
- };
- };
- };
- // Plan coordinated attack
- fnc_planCoordinatedAttack = {
- params ["_attackGroups", "_targetPos", "_side"];
- if (count _attackGroups == 0) exitWith {};
- if (count _attackGroups < 2) exitWith {
- private _group = _attackGroups select 0;
- [_group, "ASSAULT", _targetPos, [_group] call fnc_classifyGroup] call fnc_createAdvancedWaypoints;
- };
- private _assaultGroups = [];
- private _supportGroups = [];
- private _flankingGroups = [];
- {
- private _group = _x;
- private _type = [_group] call fnc_classifyGroup;
- switch (_type) do {
- case "ARMOR";
- case "MECHANIZED";
- case "SNIPER";
- case "SUPPORT": { _supportGroups pushBack _group; };
- case "SPECOPS";
- case "ELITE": { _flankingGroups pushBack _group; };
- default {
- if (count _assaultGroups <= count _flankingGroups) then {
- _assaultGroups pushBack _group;
- } else {
- _flankingGroups pushBack _group;
- };
- };
- };
- } forEach _attackGroups;
- { [_x, "ASSAULT", _targetPos, [_x] call fnc_classifyGroup] call fnc_createAdvancedWaypoints; } forEach _assaultGroups;
- {
- private _grpType = [_x] call fnc_classifyGroup;
- private _order = if (_grpType == "SNIPER") then {"DEEP_FLANK"} else {"OVERWATCH"};
- [_x, _order, _targetPos, _grpType] call fnc_createAdvancedWaypoints;
- } forEach _supportGroups;
- private _flankCount = count _flankingGroups;
- {
- private _group = _x;
- private _grpType = [_group] call fnc_classifyGroup;
- private _flankSide = if (_forEachIndex < (_flankCount / 2)) then {"FLANK_LEFT"} else {"FLANK_RIGHT"};
- [_group, _flankSide, _targetPos, _grpType] call fnc_createAdvancedWaypoints;
- } forEach _flankingGroups;
- };
- fnc_processIntelligence = {
- params ["_side"];
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- // OPTIMIZATION: Early exit if no intel to process
- if (count _intel == 0) exitWith {};
- private _currentTime = time;
- private _keysToRemove = [];
- // OPTIMIZATION: Removed complex threat categorization - it's rarely used effectively
- // Simply clean old intel and let other systems query what they need directly
- {
- private _report = _y;
- private _age = _currentTime - (_report get "time");
- if (_age > HC_INTEL_DECAY_TIME) then {
- _keysToRemove pushBack _x;
- };
- } forEach _intel;
- // Clean up old intel
- { _intel deleteAt _x; } forEach _keysToRemove;
- // OPTIMIZATION: No need to store empty threat categorization
- // Other functions can query intel directly when needed
- };
- // Create tactical path
- fnc_createTacticalPath = {
- params ["_group", "_targetPos", "_groupType"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _leader = leader _group;
- if (isNull _leader) exitWith {};
- private _startPos = position _leader;
- private _distance = _startPos distance _targetPos;
- private _side = side _group;
- // Short distance - direct approach with terrain-based behavior
- if (_distance < 500) then {
- private _movementParams = [_group, _startPos, _targetPos, _side] call fnc_determineMovementBehavior;
- private _wp = _group addWaypoint [_targetPos, 20];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour (_movementParams get "behavior");
- _wp setWaypointSpeed (_movementParams get "speed");
- _wp setWaypointFormation (_movementParams get "formation");
- _wp setWaypointCombatMode (_movementParams get "combatMode");
- _wp setWaypointCompletionRadius 100;
- } else {
- // Long distance - use covered waypoints
- private _coveredWaypoints = [_startPos, _targetPos, _groupType] call fnc_selectCoveredWaypoints;
- // Create waypoints with terrain-appropriate behaviors
- {
- private _wpPos = _x;
- private _movementParams = [_group, _startPos, _wpPos, _side] call fnc_determineMovementBehavior;
- private _wp = _group addWaypoint [_wpPos, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour (_movementParams get "behavior");
- _wp setWaypointSpeed (_movementParams get "speed");
- _wp setWaypointFormation (_movementParams get "formation");
- _wp setWaypointCombatMode (_movementParams get "combatMode");
- _wp setWaypointCompletionRadius 75;
- _startPos = _wpPos; // Update for next segment assessment
- } forEach _coveredWaypoints;
- // Final waypoint at target with aggressive settings
- private _finalWp = _group addWaypoint [_targetPos, 20];
- _finalWp setWaypointType "SAD";
- _finalWp setWaypointBehaviour "COMBAT";
- _finalWp setWaypointSpeed "NORMAL";
- _finalWp setWaypointCombatMode "RED";
- _finalWp setWaypointCompletionRadius 100;
- };
- };
- // Create advanced waypoints
- fnc_createAdvancedWaypoints = {
- params ["_group", "_orderType", "_targetPos", "_groupType"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _side = side _group;
- private _startPos = position leader _group;
- switch (_orderType) do {
- case "ASSAULT": {
- [_group, _targetPos, _groupType] call fnc_createTacticalPath;
- };
- case "FLANK_LEFT": {
- private _angle = [_startPos, _targetPos] call BIS_fnc_dirTo;
- private _flankAngle = _angle - 90 + (random 40 - 20);
- // Use covered waypoints for flanking
- private _flankDist1 = 400 + (random 400);
- private _flankPos1 = _targetPos getPos [_flankDist1, _flankAngle];
- private _flankWaypoints = [_startPos, _flankPos1, _groupType] call fnc_selectCoveredWaypoints;
- {
- private _movementParams = [_group, _startPos, _x, _side] call fnc_determineMovementBehavior;
- private _wp = _group addWaypoint [_x, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour (_movementParams get "behavior");
- _wp setWaypointSpeed (_movementParams get "speed");
- _wp setWaypointFormation (_movementParams get "formation");
- _wp setWaypointCombatMode (_movementParams get "combatMode");
- _startPos = _x;
- } forEach _flankWaypoints;
- // Final approach position
- private _flankDist2 = 200 + (random 300);
- private _flankPos2 = _targetPos getPos [_flankDist2, _flankAngle - 45];
- private _wp2 = _group addWaypoint [_flankPos2, 30];
- _wp2 setWaypointType "MOVE";
- _wp2 setWaypointBehaviour "COMBAT";
- _wp2 setWaypointSpeed "NORMAL";
- _wp2 setWaypointFormation "LINE";
- private _wp3 = _group addWaypoint [_targetPos, 50];
- _wp3 setWaypointType "SAD";
- _wp3 setWaypointBehaviour "COMBAT";
- _wp3 setWaypointCombatMode "RED";
- };
- case "FLANK_RIGHT": {
- private _angle = [_startPos, _targetPos] call BIS_fnc_dirTo;
- private _flankAngle = _angle + 90 + (random 40 - 20);
- // Use covered waypoints for flanking
- private _flankDist1 = 400 + (random 400);
- private _flankPos1 = _targetPos getPos [_flankDist1, _flankAngle];
- private _flankWaypoints = [_startPos, _flankPos1, _groupType] call fnc_selectCoveredWaypoints;
- {
- private _movementParams = [_group, _startPos, _x, _side] call fnc_determineMovementBehavior;
- private _wp = _group addWaypoint [_x, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour (_movementParams get "behavior");
- _wp setWaypointSpeed (_movementParams get "speed");
- _wp setWaypointFormation (_movementParams get "formation");
- _wp setWaypointCombatMode (_movementParams get "combatMode");
- _startPos = _x;
- } forEach _flankWaypoints;
- // Final approach position
- private _flankDist2 = 200 + (random 300);
- private _flankPos2 = _targetPos getPos [_flankDist2, _flankAngle + 45];
- private _wp2 = _group addWaypoint [_flankPos2, 30];
- _wp2 setWaypointType "MOVE";
- _wp2 setWaypointBehaviour "COMBAT";
- _wp2 setWaypointSpeed "NORMAL";
- _wp2 setWaypointFormation "LINE";
- private _wp3 = _group addWaypoint [_targetPos, 50];
- _wp3 setWaypointType "SAD";
- _wp3 setWaypointBehaviour "COMBAT";
- _wp3 setWaypointCombatMode "RED";
- };
- case "OVERWATCH": {
- // Find covered overwatch position
- private _overwatchPos = [_targetPos, 300, 600, 10, 0, 2, 0] call BIS_fnc_findSafePos;
- if (count _overwatchPos == 0) then {
- _overwatchPos = _targetPos getPos [400, random 360];
- };
- // Assess cover at overwatch position
- private _coverData = [_overwatchPos] call fnc_evaluateTerrainCover;
- // Use covered approach if available
- if (_startPos distance2D _overwatchPos > 400) then {
- private _approachWaypoints = [_startPos, _overwatchPos, _groupType] call fnc_selectCoveredWaypoints;
- {
- private _movementParams = [_group, _startPos, _x, _side] call fnc_determineMovementBehavior;
- private _wp = _group addWaypoint [_x, 30];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour (_movementParams get "behavior");
- _wp setWaypointSpeed (_movementParams get "speed");
- _startPos = _x;
- } forEach _approachWaypoints;
- };
- private _wp = _group addWaypoint [_overwatchPos, 10];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "LIMITED";
- private _patrolWp = _group addWaypoint [_overwatchPos, 30];
- _patrolWp setWaypointType "SAD";
- _patrolWp setWaypointBehaviour "AWARE";
- _patrolWp setWaypointSpeed "LIMITED";
- _patrolWp setWaypointCompletionRadius 150;
- };
- case "DEEP_FLANK": {
- private _angleBack = [_targetPos, _startPos] call BIS_fnc_dirTo;
- private _flankPos = _targetPos getPos [600, _angleBack + (random 60 - 30)];
- // Use maximum stealth for deep flanking
- private _deepWaypoints = [_startPos, _flankPos, _groupType] call fnc_selectCoveredWaypoints;
- {
- private _wp = _group addWaypoint [_x, 60];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- _wp setWaypointFormation "FILE";
- _wp setWaypointCombatMode "GREEN";
- } forEach _deepWaypoints;
- private _wpFinal = _group addWaypoint [_flankPos, 5];
- _wpFinal setWaypointType "SAD";
- _wpFinal setWaypointBehaviour "STEALTH";
- _wpFinal setWaypointCompletionRadius 200;
- };
- case "RECON": {
- for "_i" from 0 to 2 do {
- private _reconPos = _targetPos getPos [300 + (_i * 100), random 360];
- // Evaluate cover at recon position
- private _coverData = [_reconPos] call fnc_evaluateTerrainCover;
- private _hasCover = _coverData get "hasCover";
- private _wp = _group addWaypoint [_reconPos, 30];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- _wp setWaypointFormation "FILE";
- };
- if (count (waypoints _group) > 0) then {
- (_group addWaypoint [waypointPosition [_group, 0], 0]) setWaypointType "CYCLE";
- };
- };
- };
- _group setVariable ["HC_ORDER", _orderType, true];
- _group setVariable ["HC_ORDER_TIME", time, true];
- _group setVariable ["HC_TARGET", _targetPos, true];
- };
- // Create patrol waypoints
- fnc_createPatrolWaypoints = {
- params ["_group", "_targetPos", ["_behavior", "AWARE"], ["_formation", "LINE"]];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _patrolPos = [_targetPos, 500, 1000, 10, 2, 0, 10] call BIS_fnc_findSafePos;
- if (count _patrolPos == 0) then { _patrolPos = _targetPos; };
- (_group addWaypoint [_patrolPos, 50]) setWaypointType "MOVE";
- for "_i" from 1 to 3 do {
- _patrolPos = [_targetPos, 300, 1000, 10, 2, 0, 10] call BIS_fnc_findSafePos;
- if (count _patrolPos == 0) then { _patrolPos = _targetPos; };
- private _wp = _group addWaypoint [_patrolPos, 75];
- _wp setWaypointType "SAD";
- _wp setWaypointCompletionRadius 100;
- };
- if (count (waypoints _group) > 1) then {
- (_group addWaypoint [waypointPosition [_group, 1], 0]) setWaypointType "CYCLE";
- };
- _group setBehaviour _behavior;
- _group setCombatMode "RED";
- };
- // Create defense waypoints
- fnc_createDefenseWaypoints = {
- params ["_group", "_targetPos"];
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _patrolPoints = [];
- for "_i" from 0 to 4 do {
- private _point = [_targetPos, 0, 250, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _point > 0) then { _patrolPoints pushBack _point; };
- };
- if (count _patrolPoints == 0) then { _patrolPoints pushBack _targetPos; };
- {
- private _wp = _group addWaypoint [_x, 10];
- _wp setWaypointType "SAD";
- _wp setWaypointCompletionRadius 100;
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointCombatMode "RED";
- } forEach _patrolPoints;
- if (count (waypoints _group) > 1) then {
- (_group addWaypoint [waypointPosition [_group, 0], 0]) setWaypointType "CYCLE";
- };
- _group setBehaviour "COMBAT";
- };
- // Get side state
- HC_fnc_getSideState = {
- params ["_side"];
- private _availableGroups = [];
- private _busyGroups = [];
- {
- if (side _x == _side && count (units _x) > 0 && {isPlayer _x} count (units _x) == 0) then {
- private _group = _x;
- if ([_group] call fnc_isGroupLocked) then {
- _busyGroups pushBack _group;
- } else {
- _group setVariable ["HC_ASSIGNMENT_TIME", nil];
- _group setVariable ["HC_TARGET", nil];
- _group setVariable ["HC_ORDER", nil];
- HC_Active_Groups deleteAt (groupId _group);
- _availableGroups pushBack _group;
- };
- };
- } forEach allGroups;
- private _state = createHashMap;
- _state set ["groups", _availableGroups];
- _state set ["busyGroups", _busyGroups];
- _state
- };
- fnc_getBattleHotspot = {
- params ["_side"];
- private _enemySide = if (_side == west) then {east} else {west};
- private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- // Find the midpoint between bases as default
- private _defaultPos = [
- ((_enemyBase select 0) + (_ownBase select 0)) / 2,
- ((_enemyBase select 1) + (_ownBase select 1)) / 2,
- 0
- ];
- // Try to find actual combat areas
- private _combatPositions = [];
- {
- if (side _x == _side && behaviour leader _x == "COMBAT") then {
- _combatPositions pushBack (getPos leader _x);
- };
- } forEach allGroups;
- if (count _combatPositions > 0) exitWith {
- selectRandom _combatPositions
- };
- _defaultPos
- };
- // NEW: Analyzes terrain between bases to find tactical intermediate objectives (Towns, Hills, etc.)
- fnc_HC_assessTerrainObjectives = {
- params ["_side"];
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
- // Default to enemy base if no terrain features found
- private _bestObjective = _enemyBase;
- // 1. Find potential tactical locations (Cities, Villages, Hills)
- private _midPoint = [((_ownBase select 0) + (_enemyBase select 0)) / 2, ((_ownBase select 1) + (_enemyBase select 1)) / 2, 0];
- private _searchRadius = (_ownBase distance _enemyBase) / 1.2;
- private _locations = nearestLocations [_midPoint, ["NameCityCapital", "NameCity", "NameVillage", "Hill", "Strategic"], _searchRadius];
- // 2. Filter locations to create a "Corridor of Advance"
- // Only keep locations that are roughly between the two bases (within 800m of the direct line)
- private _corridorWidth = 1000;
- private _validLocations = [];
- {
- private _locPos = locationPosition _x;
- // Calculate distance of point to the line segment between bases
- 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);
- _distToLine = _distToLine / (_ownBase distance _enemyBase);
- if (_distToLine < _corridorWidth) then {
- _validLocations pushBack _locPos;
- };
- } forEach _locations;
- // 3. Sort locations by distance from OWN base (Sequential capture)
- _validLocations = [_validLocations, [], {_x distance _ownBase}, "ASCEND"] call BIS_fnc_sortBy;
- // 4. Determine the "Front Line" Objective
- // Iterate through sorted locations. The first one that isn't fully secured is the target.
- private _foundIntermediate = false;
- {
- private _locPos = _x;
- // Check control status
- private _friendlyCount = {side _x == _side && alive _x} count (_locPos nearEntities [["CAManBase", "LandVehicle"], 200]);
- private _enemySide = if (_side == west) then {east} else {west};
- private _enemyCount = {side _x == _enemySide && alive _x} count (_locPos nearEntities [["CAManBase", "LandVehicle"], 200]);
- // Conditions to consider a location "Secured"
- // 1. We have significant force there AND enemies are cleared OR
- // 2. We are closer to the NEXT objective than this one (we moved past it)
- private _isSecured = (_friendlyCount > 2 && _enemyCount == 0);
- // If not secured, this is our next Tactical Objective
- if (!_isSecured) exitWith {
- _bestObjective = _locPos;
- _foundIntermediate = true;
- };
- } forEach _validLocations;
- // 5. If all intermediate terrain is secured, push for the Enemy Base,
- // but try to find a covered assault position 300m out rather than center-mass.
- if (!_foundIntermediate) then {
- private _dirToEnemy = _ownBase getDir _enemyBase;
- // Offset slightly to utilize terrain if possible
- private _assaultPos = _enemyBase getPos [300, _dirToEnemy];
- private _coverPos = [_assaultPos, 0, 150, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _coverPos > 0) then {
- _bestObjective = _coverPos;
- } else {
- _bestObjective = _enemyBase;
- };
- };
- _bestObjective
- };
- // Main commander decision-making
- fnc_commanderMakeDecision = {
- params ["_side"];
- if (isNil "bluforSpawnObj" || isNil "opforSpawnObj") exitWith {};
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
- // Get Play Area constraints
- private _playAreaCenter = missionNamespace getVariable ["PLAY_AREA_CENTER", [0,0,0]];
- private _playAreaRadius = missionNamespace getVariable ["PLAY_AREA_RADIUS", 2000];
- private _friendlyState = [_side] call HC_fnc_getSideState;
- private _availableGroups = _friendlyState get "groups";
- if (count _availableGroups == 0) exitWith {};
- // PRIORITY 0: Emergency base defense (Always overrides everything)
- if ([_side, 600] call fnc_enemiesNearBase) then {
- _availableGroups = [_availableGroups, [], {leader _x distance _ownBase}, "ASCEND"] call BIS_fnc_sortBy;
- private _groupsToDefendBase = _availableGroups select [0, floor(count _availableGroups / 3) max 1];
- { [_x, _ownBase, "DEFEND"] call fnc_assignGroupObjective; } forEach _groupsToDefendBase;
- _availableGroups = _availableGroups - _groupsToDefendBase;
- };
- if (count _availableGroups == 0) exitWith {};
- // --- STEP 1: SEGREGATE SCOUTING FORCES ---
- // Identify units best suited for wide flank security (Light Vics, SpecOps, Snipers, Air)
- private _scoutGroups = [];
- private _mainBattleGroups = [];
- {
- private _grp = _x;
- private _type = [_grp] call fnc_classifyGroup;
- // Check if group is light/fast/stealthy
- // Note: Standard Infantry can also be scouts if we have too many, but let's prioritize these first
- if (_type in ["SNIPER", "SPECOPS", "ELITE", "AIR"] ||
- (_type == "MOTORIZED") || // Assuming Motorized are light vehicles like MRAPs
- (_type == "MECHANIZED" && random 1 > 0.7)) then { // 30% chance for APCs to help flanks
- _scoutGroups pushBack _grp;
- } else {
- _mainBattleGroups pushBack _grp;
- };
- } forEach _availableGroups;
- // --- STEP 2: ASSIGN WIDE FLANK PATROLS ---
- // These units ignore the "Tactical Objective" and focus on the edges of the Red Circle
- if (count _scoutGroups > 0) then {
- private _attackDir = _ownBase getDir _enemyBase;
- // Use 65% of the radius to stay safely inside the circle but wide enough to spot players
- private _flankDist = _playAreaRadius * 0.65;
- // Calculate dynamic flank positions relative to the PLAY AREA CENTER, not the units
- private _leftFlankPos = _playAreaCenter getPos [_flankDist, _attackDir - 90];
- private _rightFlankPos = _playAreaCenter getPos [_flankDist, _attackDir + 90];
- // Find safe spots for these coordinates
- private _safeLeft = [_leftFlankPos, 0, 100, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _safeLeft == 0) then { _safeLeft = _leftFlankPos; };
- private _safeRight = [_rightFlankPos, 0, 100, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _safeRight == 0) then { _safeRight = _rightFlankPos; };
- {
- private _grp = _x;
- // Alternate assignment: Even index -> Left, Odd index -> Right
- private _target = if (_forEachIndex % 2 == 0) then { _safeLeft } else { _safeRight };
- // Assign a Patrol/SAD task to the flank
- // We use "PATROL" so they move around that area looking for enemies
- [_grp, _target, "PATROL"] call fnc_assignGroupObjective;
- // Mark them as Flankers for debugging/tracking
- _grp setVariable ["HC_ROLE", "WIDE_FLANK_SECURITY", true];
- } forEach _scoutGroups;
- };
- // If no main force left (e.g. only had snipers), exit
- if (count _mainBattleGroups == 0) exitWith {};
- // --- STEP 3: MAIN FORCE LOGIC (Center/Towns) ---
- // The remaining heavy troops (Tanks, main Infantry) proceed with the existing terrain logic
- // Calculate the best tactical objective based on terrain analysis
- private _tacticalObjective = [_side] call fnc_HC_assessTerrainObjectives;
- private _currentStrategyName = "ADVANCE";
- if (_tacticalObjective distance2D _enemyBase < 300) then {
- _currentStrategyName = "BASE_ASSAULT";
- } else {
- _currentStrategyName = "SECTOR_CONTROL";
- };
- if (STRATEGY_ENABLED) then {
- private _strategicRecommendation = [_side, _mainBattleGroups] call fnc_getStrategicRecommendation;
- if (count _strategicRecommendation > 0) then {
- private _objective = _strategicRecommendation get "objective";
- private _recommendedGroupCount = _strategicRecommendation get "groupCount";
- private _prediction = _strategicRecommendation get "prediction";
- private _score = _strategicRecommendation get "score";
- private _expectedCasualties = _prediction get "expectedCasualties";
- if (_score >= STRATEGY_MIN_SCORE && _expectedCasualties <= STRATEGY_CASUALTY_TOLERANCE) then {
- // Strategy System Override (e.g. Taking a Hill)
- private _objectivePos = _objective get "position";
- private _objectiveType = _objective get "type";
- private _sortedGroups = [_mainBattleGroups, [], {(leader _x) distance _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- private _groupsToAssign = _sortedGroups select [0, _recommendedGroupCount min count _sortedGroups];
- private _battlePlan = [_side, _groupsToAssign, _objectivePos, _ownBase] call fnc_createBattlePlan;
- private _battleCenter = _battlePlan get "battleCenter";
- private _attackers = _battlePlan get "attackers";
- private _defenders = _battlePlan get "defenders";
- private _leftFlank = _battlePlan get "leftFlank";
- private _rightFlank = _battlePlan get "rightFlank";
- private _reserve = _battlePlan get "reserve";
- private _support = _battlePlan get "support";
- [_attackers, "ATTACK", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
- [_defenders, "DEFEND", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
- [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
- [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
- [_reserve, "RESERVE", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
- [_support, "SUPPORT", _battleCenter, _ownBase, _objectivePos] call fnc_HC_assignGroupWithSpacing;
- if (_side == west) then { BLUFOR_STRATEGY = _objectiveType; } else { OPFOR_STRATEGY = _objectiveType; };
- } else {
- // Fallback to Terrain Logic
- private _battlePlan = [_side, _mainBattleGroups, _tacticalObjective, _ownBase] call fnc_createBattlePlan;
- private _battleCenter = _battlePlan get "battleCenter";
- private _attackers = _battlePlan get "attackers";
- private _defenders = _battlePlan get "defenders";
- private _leftFlank = _battlePlan get "leftFlank";
- private _rightFlank = _battlePlan get "rightFlank";
- private _reserve = _battlePlan get "reserve";
- private _support = _battlePlan get "support";
- [_attackers, "ATTACK", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_defenders, "DEFEND", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_reserve, "RESERVE", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_support, "SUPPORT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- if (_side == west) then { BLUFOR_STRATEGY = _currentStrategyName; } else { OPFOR_STRATEGY = _currentStrategyName; };
- };
- } else {
- // No strategy available -> Terrain Logic
- private _battlePlan = [_side, _mainBattleGroups, _tacticalObjective, _ownBase] call fnc_createBattlePlan;
- private _battleCenter = _battlePlan get "battleCenter";
- private _attackers = _battlePlan get "attackers";
- private _defenders = _battlePlan get "defenders";
- private _leftFlank = _battlePlan get "leftFlank";
- private _rightFlank = _battlePlan get "rightFlank";
- private _reserve = _battlePlan get "reserve";
- private _support = _battlePlan get "support";
- [_attackers, "ATTACK", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_defenders, "DEFEND", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_reserve, "RESERVE", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_support, "SUPPORT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- if (_side == west) then { BLUFOR_STRATEGY = _currentStrategyName; } else { OPFOR_STRATEGY = _currentStrategyName; };
- };
- } else {
- // Strategy Disabled -> Terrain Logic
- private _battlePlan = [_side, _mainBattleGroups, _tacticalObjective, _ownBase] call fnc_createBattlePlan;
- private _battleCenter = _battlePlan get "battleCenter";
- private _attackers = _battlePlan get "attackers";
- private _defenders = _battlePlan get "defenders";
- private _leftFlank = _battlePlan get "leftFlank";
- private _rightFlank = _battlePlan get "rightFlank";
- private _reserve = _battlePlan get "reserve";
- private _support = _battlePlan get "support";
- [_attackers, "ATTACK", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_defenders, "DEFEND", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_reserve, "RESERVE", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- [_support, "SUPPORT", _battleCenter, _ownBase, _tacticalObjective] call fnc_HC_assignGroupWithSpacing;
- if (_side == west) then { BLUFOR_STRATEGY = _currentStrategyName; } else { OPFOR_STRATEGY = _currentStrategyName; };
- };
- };
- // Create attack helicopter waypoints with improved combat patterns
- fnc_createHelicopterAttackWaypoints = {
- params ["_group", "_targetArea", "_centerPos"];
- // Clear existing waypoints
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _heli = vehicle (leader _group);
- if (isNull _heli || {!(_heli isKindOf "Air")}) exitWith {};
- // Set optimal flight height for attack helicopters
- _heli flyInHeight 150;
- // Create attack run pattern
- private _attackDistance = 2000; // Stand-off distance for initial approach
- private _orbitRadius = 1500; // Orbit radius around target area
- // Waypoint 1: Move to attack position (approach from distance)
- private _approachAngle = random 360;
- private _approachPos = _targetArea getPos [_attackDistance, _approachAngle];
- private _wp1 = _group addWaypoint [_approachPos, 50];
- _wp1 setWaypointType "MOVE";
- _wp1 setWaypointBehaviour "AWARE";
- _wp1 setWaypointSpeed "NORMAL";
- _wp1 setWaypointStatements ["true", "(vehicle this) flyInHeight 100;"];
- // Waypoint 2: SAD at target area
- private _wp2 = _group addWaypoint [_targetArea, 200];
- _wp2 setWaypointType "SAD";
- _wp2 setWaypointBehaviour "COMBAT";
- _wp2 setWaypointSpeed "LIMITED";
- _wp2 setWaypointTimeout [30, 45, 60];
- _wp2 setWaypointStatements ["true", "(vehicle this) flyInHeight 80;"];
- // Waypoint 3-5: Orbit pattern around target
- for "_i" from 0 to 2 do {
- private _orbitAngle = _approachAngle + (120 * (_i + 1));
- private _orbitPos = _targetArea getPos [_orbitRadius, _orbitAngle];
- private _wpOrbit = _group addWaypoint [_orbitPos, 100];
- _wpOrbit setWaypointType "SAD";
- _wpOrbit setWaypointBehaviour "COMBAT";
- _wpOrbit setWaypointSpeed "NORMAL";
- _wpOrbit setWaypointTimeout [10, 15, 20];
- };
- // Waypoint 6: Return to patrol the center area
- private _wpCenter = _group addWaypoint [_centerPos, 500];
- _wpCenter setWaypointType "SAD";
- _wpCenter setWaypointBehaviour "AWARE";
- _wpCenter setWaypointSpeed "NORMAL";
- _wpCenter setWaypointStatements ["true", "(vehicle this) flyInHeight 150;"];
- // Cycle back to waypoint 1
- private _wpCycle = _group addWaypoint [_approachPos, 0];
- _wpCycle setWaypointType "CYCLE";
- // Force the group to start moving
- _group setCurrentWaypoint [_group, 1];
- };
- // Server-side loops
- if (isServer) then {
- // Main decision-making loop
- [] spawn {
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- sleep 10;
- while {true} do {
- // Adaptive decision interval based on performance
- private _baseInterval = HC_DECISION_INTERVAL;
- private _adaptiveInterval = [_baseInterval] call fnc_getAdaptiveSleep;
- if (time - BLUFOR_LAST_DECISION > _adaptiveInterval) then {
- [west] call fnc_commanderMakeDecision;
- BLUFOR_LAST_DECISION = time;
- };
- sleep 3;
- if (time - OPFOR_LAST_DECISION > _adaptiveInterval) then {
- [east] call fnc_commanderMakeDecision;
- OPFOR_LAST_DECISION = time;
- };
- sleep (_adaptiveInterval - 3);
- };
- };
- // Check active groups for objective completion
- [] spawn {
- while {true} do {
- sleep 10;
- private _activeGroupIds = keys HC_Active_Groups;
- {
- private _group = HC_Active_Groups getOrDefault [_x, grpNull];
- if (isNull _group || count (units _group) == 0) then {
- HC_Active_Groups deleteAt _x;
- } else {
- private _targetPos = _group getVariable ["HC_TARGET", [0,0,0]];
- if !(_targetPos isEqualTo [0,0,0]) then {
- private _leader = leader _group;
- if (!isNull _leader && alive _leader) then {
- if ((_leader distance _targetPos) < HC_SUCCESS_UNLOCK_RADIUS) then {
- _group setVariable ["HC_ASSIGNMENT_TIME", 0];
- };
- };
- };
- };
- } forEach _activeGroupIds;
- };
- };
- // Intel processing and sharing
- [] spawn {
- waitUntil { time > 5 };
- while {true} do {
- [west] call fnc_processIntelligence;
- [east] call fnc_processIntelligence;
- publicVariable "BLUFOR_INTEL";
- publicVariable "OPFOR_INTEL";
- sleep 5;
- };
- };
- // Support evaluation and calling loop
- [] spawn {
- waitUntil { time > 5 };
- // Function to handle support calls for a side
- private _fnc_handleSideSupport = {
- params ["_side"];
- private _support = [_side] call fnc_evaluateSupportNeed;
- if (_support == "") exitWith {};
- private _recentReports = if (_side == west) then {BLUFOR_TACTICAL_REPORTS} else {OPFOR_TACTICAL_REPORTS};
- private _enemySide = if (_side == west) then {east} else {west};
- private _target = [0,0,0];
- switch (_support) do {
- case "ARTILLERY": {
- private _bestTarget = [0,0,0];
- private _mostRecentTime = 0;
- private _maxIntelAge = 30; // CHANGED: Artillery only uses intel from last 30 seconds
- private _enemyBaseMarker = if (_side == west) then {"opfor_base_area"} else {"blufor_base_area"};
- private _enemyBasePos = markerPos _enemyBaseMarker;
- private _enemyBaseSize = (markerSize _enemyBaseMarker) select 0;
- // Loop through all reports to find the newest FRESH one
- {
- private _report = _x;
- private _reportTime = _report select 0;
- private _reportAge = time - _reportTime;
- // CHANGED: Only consider reports from the last 30 seconds
- if (_reportAge <= _maxIntelAge && _reportTime > _mostRecentTime) then {
- private _pos = _report select 2;
- // Check if it's a valid target
- if (_pos distance2D _enemyBasePos >= _enemyBaseSize) then {
- private _nearbyFriendlies = _pos nearEntities [["CAManBase", "LandVehicle"], 50];
- private _friendliesInArea = {side _x == _side && alive _x} count _nearbyFriendlies;
- if (_friendliesInArea == 0) then {
- // It's valid and it's the newest fresh report so far
- _mostRecentTime = _reportTime;
- _bestTarget = _pos;
- };
- };
- };
- } forEach _recentReports;
- // CHANGED: Only set target if we found a fresh report
- if (_mostRecentTime > 0) then {
- _target = _bestTarget;
- };
- };
- case "SMOKE": {
- private _casualtyAreas = [];
- private _friendlyGroups = allGroups select {side _x == _side && count (units _x) > 0};
- {
- private _group = _x;
- private _casualties = {damage _x > 0.3} count (units _group);
- if (_casualties >= 2 && behaviour leader _group == "COMBAT") then {
- _casualtyAreas pushBack (getPos leader _group);
- };
- } forEach _friendlyGroups;
- if (count _casualtyAreas > 0) then {
- _target = selectRandom _casualtyAreas;
- };
- };
- default {
- private _recentCombatAreas = [];
- { _recentCombatAreas pushBackUnique (_x select 2); } forEach _recentReports;
- _target = if (count _recentCombatAreas > 0) then {selectRandom _recentCombatAreas} else {[_side] call fnc_getBattleHotspot};
- };
- };
- if !(_target isEqualTo [0,0,0]) then {
- switch (_support) do {
- case "FLARE": { [_side, _target] call fnc_callFlareSupport; };
- case "SMOKE": { [_side, _target] call fnc_callSmokeSupport; };
- case "DRONE": { [_side, _target] call fnc_callReconDroneSupport; };
- case "ARTILLERY": { [_side, _target] call fnc_callArtillerySupport; };
- };
- };
- };
- while {true} do {
- [west] call _fnc_handleSideSupport;
- sleep 30;
- [east] call _fnc_handleSideSupport;
- sleep 30;
- };
- };
- // Battlefield situation monitor - reassigns groups based on changing conditions
- [] spawn {
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- sleep 30;
- while {true} do {
- // Check both sides for groups that need reassignment
- {
- private _side = _x;
- private _allGroups = [_side] call fnc_getStrategicGroups;
- {
- private _group = _x;
- private _assignTime = _group getVariable ["HC_ASSIGNMENT_TIME", 0];
- private _currentRole = _group getVariable ["HC_ROLE", ""];
- private _target = _group getVariable ["HC_TARGET", [0,0,0]];
- // Validate target is an array before using distance
- if (typeName _target == "ARRAY" && count _target >= 2) then {
- // Check if group has been on same task for too long without progress
- if (time - _assignTime > 180 && !([_group] call fnc_isGroupLocked)) then {
- private _leader = leader _group;
- if (!isNull _leader && alive _leader) then {
- // If group hasn't moved much toward target, reassign
- if (!(_target isEqualTo [0,0,0]) && ((_leader distance _target) > 300)) then {
- // Clear old assignment
- _group setVariable ["HC_ASSIGNMENT_TIME", nil];
- _group setVariable ["HC_TARGET", nil];
- _group setVariable ["HC_ORDER", nil];
- _group setVariable ["HC_ROLE", nil];
- HC_Active_Groups deleteAt (groupId _group);
- };
- };
- };
- };
- // Reassign defenders if base is no longer under threat
- if (_currentRole == "DEFENDER" && !([_side, 800] call fnc_enemiesNearBase)) then {
- if (time - _assignTime > 120) then {
- _group setVariable ["HC_ASSIGNMENT_TIME", nil];
- _group setVariable ["HC_TARGET", nil];
- _group setVariable ["HC_ORDER", nil];
- _group setVariable ["HC_ROLE", nil];
- HC_Active_Groups deleteAt (groupId _group);
- };
- };
- } forEach _allGroups;
- } forEach [west, east];
- sleep 45;
- };
- };
- };
- ==================== END OF: highcommand.sqf ====================
- ==================== START OF: init.sqf ====================
- // init.sqf
- // Mission initialization script for Arma 3
- // ==================== CHANGE START ====================
- // Explicitly set date and time multiplier on the server to ensure consistency
- // This overrides mission.sqm settings and prevents unpredictable time acceleration.
- if (isServer) then {
- setDate [2035, 6, 24, 8, 0]; // Set start time to 08:00
- setTimeMultiplier 1; // Ensure default timescale is realtime (1x)
- // Randomize overcast and set rain based on it.
- private _overcast = random 1; // Generate a random overcast value from 0 to 1.
- 0 setOvercast _overcast; // Apply the new overcast value.
- private _rain = 0; // Initialize _rain to 0 (no rain).
- // If overcast is greater than 70%, enable rain.
- if (_overcast > 0.7) then {
- // Rain intensity is a random value up to the current overcast level.
- _rain = random _overcast;
- };
- 0 setRain _rain;
- forceWeatherChange;
- };
- // ===================== CHANGE END =====================
- // Global Mission Configuration Variables
- BLUFOR_POINTS = 1000;
- OPFOR_POINTS = 1000;
- BLUFOR_STRENGTH = 0;
- OPFOR_STRENGTH = 0;
- // Base Spawning Configuration
- RANDOMIZE_BASE_LOCATIONS = 1; // 1 to randomize base locations, 0 for fixed North/South
- missionNamespace setVariable ["RANDOMIZE_BASE_LOCATIONS", RANDOMIZE_BASE_LOCATIONS];
- COAST_EXCLUSION_RADIUS = 100; // Minimum distance from coast for base spawns
- PATROL_RADIUS = 300; // Used for general AI patrols (also relevant for HC)
- // AI Spawning & Waves
- maxAI = 170;
- maxLightVehiclesPerSide = 2; // Max light vehicles (APCs, MRAPs) per side
- maxTanksPerSide = 1; // Max tanks per side
- maxAttackHelisPerSide = 1; // Max attack helicopters per side
- waveDelay = 3; // Base delay in seconds between AI waves
- BLUFOR_LAST_WAVES = []; // History of last spawned BLUFOR wave types
- OPFOR_LAST_WAVES = []; // History of last spawned OPFOR wave types
- WAVE_HISTORY_SIZE = 100; // How many past wave types to remember
- // AI Unit Values (points gained per kill, or points cost per spawn)
- INFANTRY_VALUE = 1;
- SPECOPS_VALUE = 2;
- UPGRADED_VALUE = 2;
- SNIPER_VALUE = 2;
- ELITE_VALUE = 3;
- VEHICLE_VALUE = 20; // Cost for spawning AI light vehicles
- TANK_VALUE = 40; // Cost for spawning AI tanks
- ATTACK_HELI_VALUE = 30; // Cost for spawning AI attack helicopters
- // AI Wave Composition Weights (used by fnc_getRandomWaveType)
- WAVE_WEIGHTS = [
- ["infantry", 50],
- ["upgraded", 30],
- ["specops", 20],
- ["sniper", 15],
- ["elite", 10],
- ["light_vehicle", 25],
- ["tank", 10],
- ["attack_heli", 10]
- ];
- // AAF (Syndicate/Looter) Configuration
- AAF_START_SIZE = round(maxAI / 5); // Initial AAF garrison size at mission start
- // Strategy System Configuration
- STRATEGY_ENABLED = true; // Enable strategic thinking system
- STRATEGY_MIN_SCORE = 10; // Minimum score to execute an objective
- STRATEGY_CASUALTY_TOLERANCE = 0.4; // Maximum acceptable casualty rate (40%)
- // AI Behavior (botai.sqf)
- BOTAI_SUPPRESSION_THRESHOLD = 50; // Distance threshold for AI to react to nearby fire
- BOTAI_REACTION_COOLDOWN = 5; // Seconds between AI tactical decisions
- BOTAI_CRITICAL_DAMAGE = 0.6; // Damage level for defensive actions
- BOTAI_HEAVY_SUPPRESSION = 5; // Suppression threshold
- BOTAI_SUPPRESSION_DECAY = 0.95; // Suppression decay per second
- BOTAI_GRENADE_RANGE_MAX = 50; // Max grenade throw distance
- BOTAI_GRENADE_RANGE_MIN = 15; // Min grenade throw distance
- // Cleanup System Configuration
- CLEANUP_BODY_LIMIT = 20;
- CLEANUP_DELAY = 30; // Check every 30 seconds
- CLEANUP_BODY_TIMER = 300; // Delete bodies older than 5 minutes
- CLEANUP_WEAPON_TIMER = 300; // Delete dropped weapons after 5 minutes
- CLEANUP_VEHICLE_TIMER = 120; // Delete destroyed vehicles after 2 minutes
- CLEANUP_MIN_DISTANCE = 300; // Min distance from players for cleanup
- // High Command Configuration
- HC_DECISION_INTERVAL = 90; // Strategic decision interval (seconds)
- HC_THREAT_EVAL_RADIUS = 4000; // Radius for threat evaluation
- HC_MIN_ATTACK_FORCE = 10; // Min force strength for major attack
- HC_AGGRESSIVE_THRESHOLD = 0.8; // Force ratio for aggressive stance
- HC_RETREAT_THRESHOLD = 0.2; // Force ratio for retreat
- HC_REGROUP_THRESHOLD = 0.7; // Force ratio for regrouping
- HC_INTEL_SHARE_RADIUS = 10000; // Intel sharing radius
- HC_INTEL_DECAY_TIME = 180; // Intel validity duration (seconds)
- HC_SEARCH_GRID_SIZE = 200; // Grid cell size for search operations
- // Enhanced High Command Configuration
- HC_COORDINATED_ATTACK_RADIUS = 600; // Coordination distance for assaults
- HC_SUPPORT_REQUEST_COOLDOWN = 240; // Cooldown for support requests
- HC_SUPPORT_MIN_ENEMY_BASE_DISTANCE = 600; // NEW: Minimum distance from enemy base to call support
- HC_MORALE_BASE = 1.0; // Base morale multiplier
- HC_SUPPLY_CHECK_INTERVAL = 60; // Supply check interval
- HC_REINFORCEMENT_THRESHOLD = 0.8; // Reinforcement request threshold
- HC_FLARE_COOLDOWN = 600; // 10 minute cooldown for flare support
- HC_SMOKE_COOLDOWN = 900; // 15 minute cooldown for smoke support
- HC_SMOKE_ROUNDS = 3; // Number of smoke shells
- HC_DRONE_COOLDOWN = 900; // 15 minute cooldown for recon drone
- HC_ARTILLERY_COOLDOWN = 1800; // 30 minute cooldown for artillery support
- HC_ARTILLERY_ROUNDS = 5; // Number of artillery shells
- // Group Lock Configuration (HC)
- HC_LOCK_DURATION = 300; // Task persistence duration
- HC_ENGAGEMENT_LOCK_RADIUS = 150; // Combat lock distance
- HC_SUCCESS_UNLOCK_RADIUS = 100; // Success unlock distance
- HC_COMBAT_LOCK_TIME = 180; // Combat lock duration
- // Commander Personality Types (HC)
- HC_COMMANDER_PERSONALITIES = ["AGGRESSIVE", "DEFENSIVE", "BALANCED", "CUNNING"];
- // Player Purchase System Configuration (player_server.sqf)
- PURCHASE_VEHICLE_DATA = createHashMapFromArray [
- // Utility (Cost: 1)
- ["B_Quadbike_01_F", ["Quad Bike", 1, west]],
- ["O_Quadbike_01_F", ["Quad Bike", 1, east]],
- // Light Vehicles (Cost: 10)
- ["B_MRAP_01_hmg_F", ["Hunter HMG", 10, west]],
- ["B_Heli_Light_01_F", ["Pawnee", 10, west]],
- ["O_MRAP_02_hmg_F", ["Ifrit HMG", 10, east]],
- ["O_Heli_Light_02_unarmed_F", ["Orca", 10, east]],
- // Heavy Vehicles (Cost: 20)
- ["B_APC_Wheeled_01_cannon_F", ["Marshall", 20, west]],
- ["O_APC_Wheeled_02_rcws_v2_F", ["Marid", 20, east]],
- // Tanks (Cost: 30)
- ["B_MBT_01_cannon_F", ["Slammer", 30, west]],
- ["O_MBT_02_cannon_F", ["Varsuk", 30, east]]
- ];
- // ==================== CHANGE START ====================
- PLAYER_SQUAD_COST = 15;
- PLAYER_SQUAD_COOLDOWN = 120; // 2 minutes
- // ===================== CHANGE END =====================
- // Compile and preprocess mission scripts
- [] call compile preprocessFileLineNumbers "highcommand.sqf";
- [] call compile preprocessFileLineNumbers "strategy.sqf";
- [] call compile preprocessFileLineNumbers "botai.sqf";
- [] call compile preprocessFileLineNumbers "performance.sqf";
- [] call compile preprocessFileLineNumbers "AIstuff.sqf";
- [] call compile preprocessFileLineNumbers "AAF.sqf";
- [] call compile preprocessFileLineNumbers "cleanup.sqf";
- [] call compile preprocessFileLineNumbers "arsenal.sqf";
- if (isServer) then {
- [] call compile preprocessFileLineNumbers "player_server.sqf";
- };
- if (hasInterface) then {
- [] call compile preprocessFileLineNumbers "playerinit.sqf";
- };
- // Main mission setup
- [] call compile preprocessFileLineNumbers "mission_setup.sqf";
- fnc_disablePlayerStaminaAndSway = {
- if (hasInterface) then {
- waitUntil {!isNull player};
- player enableStamina false;
- player enableFatigue false;
- player setCustomAimCoef 0;
- while {true} do {
- player setFatigue 0;
- player setCustomAimCoef 0;
- sleep 1;
- };
- };
- };
- [] call fnc_disablePlayerStaminaAndSway;
- // Server-side loop to re-task lost groups and merge small groups
- if (isServer) then {
- [] spawn {
- waitUntil {!isNil "mission_centralPoint" && !isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- private _centerPos = missionNamespace getVariable ["mission_centralPoint", [worldSize/2, worldSize/2, 0]];
- private _maxDistance = 5000;
- private _maxHeliDistance = 8000;
- while {true} do {
- sleep 60;
- // OPTIMIZATION: Pre-calculate base positions once per cycle
- private _bluforBase = getPos bluforSpawnObj;
- private _opforBase = getPos opforSpawnObj;
- // OPTIMIZATION: Pre-filter groups more aggressively
- private _aiGroups = allGroups select {
- !isNull _x &&
- count (units _x) > 0 &&
- ({isPlayer _x} count (units _x) == 0) &&
- (side _x in [west, east]) &&
- {!isNull leader _x && alive leader _x} // Only process groups with valid leaders
- };
- // Check for small groups that need merging
- {
- [_x] call fnc_checkAndMergeSmallGroups;
- } forEach _aiGroups;
- // Re-filter after potential merging (some groups may have been deleted)
- _aiGroups = allGroups select {
- !isNull _x &&
- count (units _x) > 0 &&
- ({isPlayer _x} count (units _x) == 0) &&
- (side _x in [west, east]) &&
- {!isNull leader _x && alive leader _x}
- };
- {
- private _group = _x;
- private _leader = leader _group;
- private _vehicle = vehicle _leader;
- private _isVehicleGroup = (_vehicle != _leader);
- private _side = side _group;
- private _distanceFromCenter = _leader distance2D _centerPos;
- // OPTIMIZATION: Pre-calculate positions based on side
- private _enemyBasePos = if (_side == west) then {_opforBase} else {_bluforBase};
- private _ownBasePos = if (_side == west) then {_bluforBase} else {_opforBase};
- // Handle attack helicopters
- if (_isVehicleGroup && {_vehicle isKindOf "Air"}) then {
- // OPTIMIZATION: Only check waypoints if distance threshold exceeded
- if (_distanceFromCenter > _maxHeliDistance) then {
- private _wpPos = waypointPosition [_group, currentWaypoint _group];
- private _hasValidWaypoints = (count (waypoints _group) > 0) && !(_wpPos isEqualTo [0,0,0]);
- if (!_hasValidWaypoints) then {
- [_group, _enemyBasePos, _centerPos] call fnc_createHelicopterAttackWaypoints;
- };
- };
- } else {
- // Ground unit logic
- if (_distanceFromCenter > _maxDistance) then {
- if (_isVehicleGroup) then {
- [_group, _enemyBasePos, _ownBasePos] call fnc_createVehiclePatrolWaypoints;
- } else {
- [_group, _enemyBasePos] call fnc_createPatrolWaypoints;
- };
- };
- };
- } forEach _aiGroups;
- };
- };
- };
- ==================== END OF: init.sqf ====================
- ==================== START OF: loadoutlimitations.sqf ====================
- // Allowed gear lists for Virtual Arsenal
- // BLUFOR allowed uniforms
- BLUFOR_UNIFORMS = [
- "U_B_CombatUniform_mcam",
- "U_B_CombatUniform_mcam_tshirt",
- "U_B_CombatUniform_mcam_vest",
- "U_B_CombatUniform_mcam_worn",
- "U_B_CTRG_1",
- "U_B_CTRG_2",
- "U_B_CTRG_3",
- "U_B_CTRG_Soldier_F",
- "U_B_CTRG_Soldier_2_F",
- "U_B_CTRG_Soldier_3_F",
- "U_B_CTRG_Soldier_urb_1_F",
- "U_B_CTRG_Soldier_urb_2_F",
- "U_B_CTRG_Soldier_urb_3_F",
- "U_B_GhillieSuit",
- "U_B_T_Soldier_F",
- "U_B_T_Soldier_AR_F",
- "U_B_T_Soldier_SL_F",
- "U_B_T_Sniper_F",
- "U_B_T_FullGhillie_tna_F",
- "U_B_survival_uniform"
- ];
- // BLUFOR allowed vests
- BLUFOR_VESTS = [
- "V_PlateCarrier1_rgr",
- "V_PlateCarrier2_rgr",
- "V_PlateCarrierGL_rgr",
- "V_PlateCarrierSpec_rgr",
- "V_Chestrig_rgr",
- "V_Chestrig_khk",
- "V_Chestrig_oli",
- "V_BandollierB_rgr",
- "V_BandollierB_khk",
- "V_BandollierB_oli"
- ];
- // BLUFOR allowed headgear
- BLUFOR_HEADGEAR = [
- "H_HelmetB",
- "H_HelmetB_black",
- "H_HelmetB_camo",
- "H_HelmetB_desert",
- "H_HelmetB_grass",
- "H_HelmetB_sand",
- "H_HelmetB_snakeskin",
- "H_HelmetSpecB",
- "H_HelmetSpecB_black",
- "H_HelmetSpecB_paint2",
- "H_HelmetSpecB_paint1",
- "H_HelmetB_light",
- "H_HelmetB_light_black",
- "H_HelmetB_light_desert",
- "H_HelmetB_light_grass",
- "H_HelmetB_light_sand",
- "H_HelmetB_light_snakeskin",
- "H_Booniehat_mcam",
- "H_Booniehat_khk",
- "H_MilCap_mcam",
- "H_Watchcap_camo",
- "H_Bandanna_cbr",
- "H_Bandanna_khk",
- "H_Bandanna_sgg",
- "H_Shemag_olive",
- "H_ShemagOpen_tan",
- "H_MilCap_mcamo" // ADDED: BLUFOR Officer Cap
- ];
- // OPFOR allowed uniforms
- OPFOR_UNIFORMS = [
- "U_O_CombatUniform_ocamo",
- "U_O_CombatUniform_oucamo",
- "U_O_OfficerUniform_ocamo",
- "U_O_SpecopsUniform_ocamo",
- "U_O_SpecopsUniform_blk",
- "U_O_V_Soldier_Viper_F",
- "U_O_V_Soldier_Viper_hex_F",
- "U_O_GhillieSuit",
- "U_O_T_Soldier_F",
- "U_O_T_Officer_F",
- "U_O_T_Sniper_F",
- "U_O_T_FullGhillie_tna_F"
- ];
- // OPFOR allowed vests
- OPFOR_VESTS = [
- "V_TacVest_brn",
- "V_TacVest_camo",
- "V_TacVest_khk",
- "V_TacVest_oli",
- "V_HarnessO_brn",
- "V_HarnessO_gry",
- "V_HarnessOGL_brn",
- "V_HarnessOGL_gry",
- "V_BandollierB_cbr",
- "V_BandollierB_khk",
- "V_BandollierB_oli",
- "V_BandollierB_rgr"
- ];
- // OPFOR allowed headgear
- OPFOR_HEADGEAR = [
- "H_HelmetO_ocamo",
- "H_HelmetO_oucamo",
- "H_HelmetLeaderO_ocamo",
- "H_HelmetLeaderO_oucamo",
- "H_HelmetSpecO_ocamo",
- "H_HelmetSpecO_blk",
- "H_Booniehat_ocamo",
- "H_MilCap_ocamo",
- "H_MilCap_oucamo",
- "H_Bandanna_cbr",
- "H_Bandanna_khk",
- "H_Bandanna_sgg",
- "H_Shemag_olive",
- "H_ShemagOpen_tan",
- "H_Beret_ocamo" // ADDED: OPFOR Officer Beret
- ];
- ==================== END OF: loadoutlimitations.sqf ====================
- ==================== START OF: mission_setup.sqf ====================
- // mission_setup.sqf
- // Contains core mission initialization logic and main game loops.
- // REVISED: The SITREP function is now simplified to report team points every 5 minutes.
- fnc_broadcastSitRep = {
- while {true} do {
- sleep 300; // Report every 5 minutes
- private _bluforPoints = round (missionNamespace getVariable ["BLUFOR_POINTS", 0]);
- private _opforPoints = round (missionNamespace getVariable ["OPFOR_POINTS", 0]);
- systemChat format ["[SITREP] Team Points | BLUFOR: %1 | OPFOR: %2", _bluforPoints, _opforPoints];
- };
- };
- fnc_isPositionValidForBase = {
- params ["_pos"];
- // Check 1: Must not be in water.
- if (surfaceIsWater _pos) exitWith {false};
- // Check 2: Ground must not be too steep - made stricter for tower base access.
- private _normal = surfaceNormal [(_pos select 0), (_pos select 1)];
- if ((_normal select 2) < 0.98) exitWith {false};
- // Check 3: IMPROVED - Must not be too close to the coast.
- // Test multiple distances and more directions to ensure no water nearby
- private _exclusionRadius = COAST_EXCLUSION_RADIUS;
- // Check 16 directions instead of 8 for better coverage
- for "_angle" from 0 to 337.5 step 22.5 do {
- // Check at multiple distances from center outward
- for "_dist" from 20 to _exclusionRadius step 20 do {
- private _checkPos = _pos getPos [_dist, _angle];
- if (surfaceIsWater _checkPos) exitWith {false};
- };
- };
- // Additional concentric ring check at the exact exclusion radius
- for "_angle" from 0 to 360 step 15 do {
- private _checkPos = _pos getPos [_exclusionRadius, _angle];
- if (surfaceIsWater _checkPos) exitWith {false};
- };
- // NEW Check 4: Ensure the entire 600m base marker radius is clear of water
- // This is the radius used for the base area marker in fnc_createRandomizedBases
- private _baseMarkerRadius = 600;
- // Check perimeter of the base circle at 600m radius (24 points around the circle)
- for "_angle" from 0 to 345 step 15 do {
- private _perimeterPos = _pos getPos [_baseMarkerRadius, _angle];
- if (surfaceIsWater _perimeterPos) exitWith {false};
- };
- // Check intermediate rings within the base circle to ensure no water inside
- // Check at 200m, 400m, and 600m radius
- for "_radius" from 200 to 600 step 200 do {
- for "_angle" from 0 to 330 step 30 do {
- private _ringPos = _pos getPos [_radius, _angle];
- if (surfaceIsWater _ringPos) exitWith {false};
- };
- };
- // Check 5: Must not have terrain objects (trees, rocks, etc.) within 20 meters.
- private _terrainObjects = nearestTerrainObjects [_pos, ["TREE", "SMALL TREE", "BUSH", "ROCK", "ROCKS"], 20];
- if (count _terrainObjects > 0) exitWith {false};
- // Check 6: Must not have buildings or structures within 20 meters.
- private _nearbyObjects = nearestObjects [_pos, ["Building", "House", "Wall", "Fence"], 20];
- if (count _nearbyObjects > 0) exitWith {false};
- // Check 7: Get ALL objects within 20 meters and filter out harmless ones.
- private _allObjects = nearestObjects [_pos, [], 20];
- private _blockingObjects = _allObjects select {
- !(typeOf _x in ["Logic", "EmptyDetector"]) &&
- !(_x isKindOf "Man") &&
- !(_x isKindOf "Air") &&
- !(_x isKindOf "WeaponHolder")
- };
- if (count _blockingObjects > 0) exitWith {false};
- // If all checks pass, the position is valid.
- true
- };
- // MODIFIED: Robust base placement to ensure balanced, opposing starting locations.
- fnc_createRandomizedBases = {
- if (!isServer) exitWith {};
- // Ensure a default value is set if the variable isn't defined in init.sqf
- if (isNil "RANDOMIZE_BASE_LOCATIONS") then { RANDOMIZE_BASE_LOCATIONS = 1; };
- private _bluforPos = [];
- private _opforPos = [];
- if (RANDOMIZE_BASE_LOCATIONS == 0) then {
- // --- MARKER-BASED BASE LOCATIONS ---
- _bluforPos = markerPos "basemarker1";
- _opforPos = markerPos "basemarker2";
- private _centralPoint = [((_bluforPos select 0) + (_opforPos select 0)) / 2, ((_bluforPos select 1) + (_opforPos select 1)) / 2, 0];
- missionNamespace setVariable ["mission_centralPoint", _centralPoint, true];
- if (_bluforPos isEqualTo [0,0,0] || _opforPos isEqualTo [0,0,0]) then {
- systemChat "ERROR: 'basemarker1' or 'basemarker2' not found. Mission cannot start correctly. Please place these markers in the editor.";
- };
- } else {
- // --- NEW MULTI-STAGE RANDOMIZED BASE LOGIC ---
- private _layoutFound = false;
- // Step 1: Find all MEDIUM AND LARGE CITIES ONLY (no villages) to serve as potential battle locations
- private _allTowns = nearestLocations [
- getArray (configFile >> "CfgWorlds" >> worldName >> "centerPosition"),
- ["NameCity", "NameCityCapital"],
- worldSize
- ];
- // ==================== CHANGE START ====================
- // Exclude Neochori from the list of potential central cities
- _allTowns = _allTowns select { toLower (text _x) != "neochori" };
- // ===================== CHANGE END =====================
- // Shuffle the list of all towns to ensure variety in each session.
- _allTowns = _allTowns call BIS_fnc_arrayShuffle;
- // Helper function to find a valid spot within a search arc
- private _fnc_findValidSpotInArc = {
- params ["_center", "_distance", "_baseAngle"];
- private _foundPos = [];
- // Search in a 90-degree arc for a valid spot - INCREASED attempts from 15 to 30
- for "_i" from 0 to 30 do {
- private _testAngle = _baseAngle + (random 90) - 45;
- private _potentialPos = _center getPos [_distance, _testAngle];
- // NOW ACTUALLY USE THE VALIDATION FUNCTION
- if ([_potentialPos] call fnc_isPositionValidForBase) exitWith {
- _foundPos = _potentialPos;
- };
- };
- _foundPos
- };
- // Step 2: Iterate through the shuffled list of all towns and try to build a valid layout around them.
- {
- private _centralCity = _x;
- private _centralPos = position _centralCity;
- // Skip if the central city itself is too close to water
- if (surfaceIsWater _centralPos) then { continue; };
- // Try 20 different conflict axes for the current town (INCREASED from 10)
- for "_i" from 1 to 20 do {
- // The distance is now randomized for each placement attempt.
- // 1000m base + (100 to 700m random) = 1100m to 1700m total distance.
- private _baseDistance = 1400 + (random 300);
- private _conflictAxis = random 360;
- // Find a spot for BLUFOR
- private _potentialBluforPos = [_centralPos, _baseDistance, _conflictAxis] call _fnc_findValidSpotInArc;
- if (count _potentialBluforPos > 0) then {
- // Find a spot for OPFOR on the opposite side
- private _potentialOpforPos = [_centralPos, _baseDistance, _conflictAxis + 180] call _fnc_findValidSpotInArc;
- if (count _potentialOpforPos > 0) then {
- // DOUBLE CHECK both positions are valid
- if ([_potentialBluforPos] call fnc_isPositionValidForBase && [_potentialOpforPos] call fnc_isPositionValidForBase) then {
- // SUCCESS! We have a valid town and two opposing bases.
- _bluforPos = _potentialBluforPos;
- _opforPos = _potentialOpforPos;
- missionNamespace setVariable ["mission_centralPoint", _centralPos, true];
- _layoutFound = true;
- break; // Exit the conflict axis loop
- };
- };
- };
- };
- if (_layoutFound) then {
- break; // Exit the town iteration loop
- };
- } forEach _allTowns; // This now iterates through the full, shuffled list.
- };
- // --- COMMON BASE CREATION LOGIC ---
- // Emergency fallback if the entire random placement process fails
- if (count _bluforPos == 0 || count _opforPos == 0) then {
- _bluforPos = markerPos "basemarker1";
- _opforPos = markerPos "basemarker2";
- private _centralPoint = [((_bluforPos select 0) + (_opforPos select 0)) / 2, ((_bluforPos select 1) + (_opforPos select 1)) / 2, 0];
- missionNamespace setVariable ["mission_centralPoint", _centralPoint, true];
- };
- // Create BLUFOR spawn object
- bluforSpawnObj = createVehicle ["Land_Cargo_Tower_V1_F", _bluforPos, [], 0, "NONE"];
- bluforSpawnObj allowDamage false;
- bluforSpawnObj enableSimulationGlobal false;
- bluforSpawnObj setVectorUp [0,0,1];
- private _bluforGroundPos = getPosATL bluforSpawnObj;
- _bluforGroundPos set [2, 0];
- bluforSpawnObj setPosATL _bluforGroundPos;
- publicVariable "bluforSpawnObj";
- // Create OPFOR spawn object
- opforSpawnObj = createVehicle ["Land_Cargo_Tower_V3_F", _opforPos, [], 0, "NONE"];
- opforSpawnObj allowDamage false;
- opforSpawnObj enableSimulationGlobal false;
- opforSpawnObj setVectorUp [0,0,1];
- private _opforGroundPos = getPosATL opforSpawnObj;
- _opforGroundPos set [2, 0];
- opforSpawnObj setPosATL _opforGroundPos;
- publicVariable "opforSpawnObj";
- // Create BLUFOR Base Area Marker with a randomized offset
- private _bluforMarkerCenter = _bluforPos getPos [random 300, random 360];
- private _bluforMarker = createMarker ["blufor_base_area", _bluforMarkerCenter];
- _bluforMarker setMarkerShape "ELLIPSE";
- _bluforMarker setMarkerSize [600, 600];
- _bluforMarker setMarkerType "mil_unknown";
- _bluforMarker setMarkerColor "ColorBlue";
- _bluforMarker setMarkerBrush "Border";
- _bluforMarker setMarkerText "";
- // Create OPFOR Base Area Marker with a randomized offset
- private _opforMarkerCenter = _opforPos getPos [random 300, random 360];
- private _opforMarker = createMarker ["opfor_base_area", _opforMarkerCenter];
- _opforMarker setMarkerShape "ELLIPSE";
- _opforMarker setMarkerSize [600, 600];
- _opforMarker setMarkerType "mil_unknown";
- _opforMarker setMarkerColor "ColorRed";
- _opforMarker setMarkerBrush "Border";
- _opforMarker setMarkerText "";
- };
- fnc_decorateFOB = {
- params ["_baseCenterObj", "_side"];
- private _basePos = getPosATL _baseCenterObj;
- private _sideFlag = if (_side == west) then { "Flag_NATO_F" } else { "Flag_CSAT_F" };
- private _sideCamoNet = if (_side == west) then { "Land_CamoNet_BLUFOR_F" } else { "Land_CamoNet_OPFOR_F" };
- private _sideTower = if (_side == west) then { "Land_Cargo_Patrol_V4_F" } else { "Land_Cargo_Patrol_V3_F" };
- // Calculate direction to enemy base for barrier placement
- private _enemyBasePos = if (_side == west) then {
- if (!isNil "opforSpawnObj") then {getPos opforSpawnObj} else {[0,0,0]}
- } else {
- if (!isNil "bluforSpawnObj") then {getPos bluforSpawnObj} else {[0,0,0]}
- };
- private _dirToEnemy = _basePos getDir _enemyBasePos;
- // Create square wall perimeter around tower with gaps
- private _wallDistance = 15; // Distance from center to walls
- private _wallSegments = [
- // North wall (2 segments with gap in middle)
- [[-_wallDistance, _wallDistance, 0], 0, "Land_HBarrierWall6_F"],
- [[_wallDistance * 0.5, _wallDistance, 0], 0, "Land_HBarrierWall6_F"],
- // South wall (2 segments with gap in middle)
- [[-_wallDistance, -_wallDistance, 0], 180, "Land_HBarrierWall6_F"],
- [[_wallDistance * 0.5, -_wallDistance, 0], 180, "Land_HBarrierWall6_F"],
- // East wall (2 segments with gap in middle)
- [[_wallDistance, -_wallDistance * 0.5, 0], 90, "Land_HBarrierWall6_F"],
- [[_wallDistance, _wallDistance * 0.5, 0], 90, "Land_HBarrierWall4_F"],
- // West wall (2 segments with gap in middle)
- [[-_wallDistance, -_wallDistance * 0.5, 0], 270, "Land_HBarrierWall6_F"],
- [[-_wallDistance, _wallDistance * 0.5, 0], 270, "Land_HBarrierWall4_F"]
- ];
- // Create the wall segments
- {
- private _relPos = _x select 0;
- private _dir = _x select 1;
- private _className = _x select 2;
- private _worldPos = _basePos vectorAdd _relPos;
- private _wall = createVehicle [_className, _worldPos, [], 0, "NONE"];
- private _wallGroundPos = getPosATL _wall;
- _wallGroundPos set [2, 0];
- _wall setPosATL _wallGroundPos;
- _wall setDir _dir;
- _wall allowDamage true;
- _wall enableSimulationGlobal false;
- _wall setVectorUp [0,0,1];
- } forEach _wallSegments;
- // Create defensive barriers facing enemy base (30 meters from tower)
- private _barrierDistance = 30;
- private _barrierCount = 5; // Number of barriers in the arc
- private _arcWidth = 120; // Total arc width in degrees
- for "_i" from 0 to (_barrierCount - 1) do {
- // Calculate angle for each barrier in the arc
- private _angleOffset = -(_arcWidth / 2) + (_i * (_arcWidth / (_barrierCount - 1)));
- private _barrierDir = _dirToEnemy + _angleOffset;
- // Calculate position for barrier
- private _barrierPos = _basePos getPos [_barrierDistance, _barrierDir];
- // Create the barrier
- private _barrier = createVehicle ["Land_HBarrierBig_F", _barrierPos, [], 0, "NONE"];
- private _barrierGroundPos = getPosATL _barrier;
- _barrierGroundPos set [2, 0];
- _barrier setPosATL _barrierGroundPos;
- _barrier setDir (_barrierDir); // Face towards enemy
- _barrier allowDamage true;
- _barrier enableSimulationGlobal false;
- _barrier setVectorUp [0,0,1];
- };
- // Additional corner reinforcement barriers
- private _cornerBarriers = [
- [_barrierDistance, _dirToEnemy - 70],
- [_barrierDistance, _dirToEnemy + 70]
- ];
- {
- private _distance = _x select 0;
- private _dir = _x select 1;
- private _pos = _basePos getPos [_distance, _dir];
- private _barrier = createVehicle ["Land_HBarrierWall_corner_F", _pos, [], 0, "NONE"];
- private _groundPos = getPosATL _barrier;
- _groundPos set [2, 0];
- _barrier setPosATL _groundPos;
- _barrier setDir _dir;
- _barrier allowDamage true;
- _barrier enableSimulationGlobal false;
- _barrier setVectorUp [0,0,1];
- } forEach _cornerBarriers;
- // Fix watchtower positioning: Create at position, then place on ground
- private _towerPosRel = [-70, -70, 0];
- private _towerWorldPos = _basePos vectorAdd _towerPosRel;
- private _towerObj = createVehicle [_sideTower, _towerWorldPos, [], 0, "NONE"];
- _towerObj setDir 225;
- _towerObj allowDamage false;
- _towerObj enableSimulationGlobal false;
- _towerObj setVectorUp [0,0,1];
- private _towerGroundPos = getPosATL _towerObj;
- _towerGroundPos set [2, 0];
- _towerObj setPosATL _towerGroundPos;
- private _sideName = if (_side == west) then {"BLUFOR"} else {"OPFOR"};
- };
- // Create and manage the play area restriction zone
- fnc_createPlayArea = {
- if (!isServer) exitWith {};
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- private _bluforPos = getPos bluforSpawnObj;
- private _opforPos = getPos opforSpawnObj;
- // Calculate center point between bases
- private _centerX = ((_bluforPos select 0) + (_opforPos select 0)) / 2;
- private _centerY = ((_bluforPos select 1) + (_opforPos select 1)) / 2;
- private _playAreaCenter = [_centerX, _centerY, 0];
- // Calculate required radius (distance between bases / 2 + 200m buffer)
- private _baseDistance = _bluforPos distance2D _opforPos;
- private _playAreaRadius = (_baseDistance / 2) + 200;
- // Store globally for reference
- missionNamespace setVariable ["PLAY_AREA_CENTER", _playAreaCenter, true];
- missionNamespace setVariable ["PLAY_AREA_RADIUS", _playAreaRadius, true];
- // Create visual marker for the play area
- private _marker = createMarker ["play_area_boundary", _playAreaCenter];
- _marker setMarkerShape "ELLIPSE";
- _marker setMarkerSize [_playAreaRadius, _playAreaRadius];
- _marker setMarkerColor "ColorRed";
- _marker setMarkerBrush "Border";
- _marker setMarkerAlpha 0.5;
- // Spawn Land_Wall_Tin_4 objects around the perimeter to mark the boundary
- private _tapeSignSpacing = 12.5; // Spawn a sign every 12.5 meters around the perimeter (doubled from 25)
- private _circumference = 2 * pi * _playAreaRadius;
- private _numberOfSigns = floor (_circumference / _tapeSignSpacing);
- private _angleStep = 360 / _numberOfSigns;
- private _signsPerStack = 5; // Number of signs to stack vertically at each position
- private _stackSpacing = 3; // Vertical spacing between stacked signs in meters
- private _totalSignsSpawned = 0;
- // Calculate sleep delay to spread spawning over 10 seconds
- private _sleepDelay = 10 / _numberOfSigns;
- for "_i" from 0 to (_numberOfSigns - 1) do {
- private _angle = _i * _angleStep;
- private _signPos = _playAreaCenter getPos [_playAreaRadius, _angle];
- // Spawn 5 signs stacked vertically at this position
- for "_stack" from 0 to (_signsPerStack - 1) do {
- private _height = 10 + (_stack * _stackSpacing); // Start at 10m, then 13m, 16m, 19m, 22m
- private _stackedPos = +_signPos;
- _stackedPos set [2, _height];
- private _sign = createVehicle ["Land_Wall_Tin_4", _stackedPos, [], 0, "CAN_COLLIDE"];
- _sign setPosATL _stackedPos;
- _sign setDir _angle; // Orient the sign to face radially
- // Freeze the sign in place: disable simulation prevents physics and collision
- _sign enableSimulation false;
- _sign enableSimulationGlobal false;
- _sign allowDamage false;
- // Remove any possible interactions
- removeAllActions _sign;
- _totalSignsSpawned = _totalSignsSpawned + 1;
- };
- // Stagger spawning to reduce lag spike
- sleep _sleepDelay;
- };
- diag_log format ["Play Area Created - Center: %1, Radius: %2m, Wall Positions: %3, Total Walls Spawned: %4", _playAreaCenter, round _playAreaRadius, _numberOfSigns, _totalSignsSpawned];
- // Start player monitoring
- [] spawn fnc_monitorPlayAreaPlayers;
- // Start AI waypoint validation
- [] spawn fnc_monitorPlayAreaAI;
- };
- // Monitor players and warn/kill those outside play area
- fnc_monitorPlayAreaPlayers = {
- if (!isServer) exitWith {};
- waitUntil {!isNil "PLAY_AREA_CENTER" && !isNil "PLAY_AREA_RADIUS"};
- private _center = missionNamespace getVariable "PLAY_AREA_CENTER";
- private _radius = missionNamespace getVariable "PLAY_AREA_RADIUS";
- while {true} do {
- {
- if (isPlayer _x && alive _x && side _x != civilian) then {
- private _player = _x;
- private _distance = _player distance2D _center;
- if (_distance > _radius) then {
- // Player is outside play area
- private _warningTime = _player getVariable ["playAreaWarningTime", 0];
- if (_warningTime == 0) then {
- // First warning
- _player setVariable ["playAreaWarningTime", time, true];
- [_center, _radius] remoteExecCall ["fnc_client_showPlayAreaWarning", owner _player];
- } else {
- // Check if 10 seconds have passed
- if (time - _warningTime >= 10) then {
- // Kill the player
- _player setDamage 1;
- _player setVariable ["playAreaWarningTime", 0, true];
- ["You were eliminated for leaving the battle area!"] remoteExecCall ["hint", owner _player];
- };
- };
- } else {
- // Player is inside, clear any warnings
- if ((_player getVariable ["playAreaWarningTime", 0]) > 0) then {
- _player setVariable ["playAreaWarningTime", 0, true];
- [] remoteExecCall ["fnc_client_hidePlayAreaWarning", owner _player];
- };
- };
- };
- } forEach allPlayers;
- sleep 1;
- };
- };
- // Monitor AI groups and prevent waypoints outside play area
- fnc_monitorPlayAreaAI = {
- if (!isServer) exitWith {};
- waitUntil {!isNil "PLAY_AREA_CENTER" && !isNil "PLAY_AREA_RADIUS" && !isNil "mission_centralPoint"};
- private _center = missionNamespace getVariable "PLAY_AREA_CENTER";
- private _radius = missionNamespace getVariable "PLAY_AREA_RADIUS";
- private _strongholdPos = missionNamespace getVariable "mission_centralPoint";
- while {true} do {
- {
- private _group = _x;
- if (!isNull _group && count (units _group) > 0 && {!isPlayer leader _group}) then {
- private _leader = leader _group;
- // Skip if group is in helicopters (exception for air units)
- private _isAirUnit = false;
- if (!isNull _leader && vehicle _leader != _leader) then {
- if ((vehicle _leader) isKindOf "Air") then {
- _isAirUnit = true;
- };
- };
- if (!_isAirUnit) then {
- // Check all waypoints
- private _waypoints = waypoints _group;
- {
- private _wpPos = waypointPosition _x;
- // Safety check for [0,0,0] waypoints
- if (count _wpPos >= 2 && !(_wpPos isEqualTo [0,0,0])) then {
- private _wpDistance = _wpPos distance2D _center;
- if (_wpDistance > _radius) then {
- deleteWaypoint _x;
- };
- } else {
- // Remove invalid [0,0,0] waypoints immediately
- if (_wpPos isEqualTo [0,0,0]) then { deleteWaypoint _x; };
- };
- } forEach _waypoints;
- // Check Group Positions
- private _groupResetNeeded = false;
- // 1. Check Leader (Triggers Group Reset)
- if (!isNull _leader && alive _leader) then {
- if ((_leader distance2D _center) > _radius) then {
- _groupResetNeeded = true;
- };
- };
- if (_groupResetNeeded) then {
- // --- LEADER IS OUT: Teleport entire group back to THEIR BASE ---
- private _side = side _group;
- private _respawnPos = _strongholdPos; // Default fallback to center (for AAF/Indep)
- if (_side == west) then {
- if (!isNil "bluforSpawnObj") then {
- // Find safe spot near BLUFOR base
- _respawnPos = [getPos bluforSpawnObj, 15, 60, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _respawnPos == 0) then { _respawnPos = getPos bluforSpawnObj; };
- };
- };
- if (_side == east) then {
- if (!isNil "opforSpawnObj") then {
- // Find safe spot near OPFOR base
- _respawnPos = [getPos opforSpawnObj, 15, 60, 5, 0, 0.4, 0] call BIS_fnc_findSafePos;
- if (count _respawnPos == 0) then { _respawnPos = getPos opforSpawnObj; };
- };
- };
- // Teleport entire group
- {
- if (alive _x) then {
- // If driver of vehicle, move vehicle. Else move unit.
- if (vehicle _x != _x && driver (vehicle _x) == _x) then {
- (vehicle _x) setPos _respawnPos;
- (vehicle _x) setVelocity [0,0,0];
- } else {
- _x setPos _respawnPos;
- };
- };
- } forEach (units _group);
- // Clear old tasks immediately
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Give them a new task to move towards the center battlefield.
- private _wp = _group addWaypoint [_strongholdPos, 0];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE"; // Back to AWARE since they are safe at base
- _wp setWaypointSpeed "FULL";
- _wp setWaypointCompletionRadius 50;
- // Apply a 60-second HC lock to ensure they start moving back to the fight
- _group setVariable ["HC_FORCED_LOCK", true];
- // Restore combat mode
- _group setCombatMode "YELLOW";
- [_group] spawn {
- params ["_grp"];
- sleep 60;
- if (!isNull _grp) then {
- _grp setVariable ["HC_FORCED_LOCK", false];
- };
- };
- diag_log format ["PLAY AREA RESET: Group %1 teleported back to base.", _group];
- } else {
- // --- LEADER IS IN: Check for individual stragglers ---
- {
- private _unit = _x;
- // Only check living units, on foot, that are NOT the leader
- if (alive _unit && _unit != _leader && vehicle _unit == _unit) then {
- if ((_unit distance2D _center) > _radius) then {
- // Teleport straggler to leader
- private _targetPos = [];
- if (!isNull _leader && alive _leader) then {
- _targetPos = (getPosATL _leader) vectorAdd [random 4 - 2, random 4 - 2, 0];
- } else {
- // If leader dead/null, teleport to center stronghold
- _targetPos = _strongholdPos;
- };
- if (count _targetPos > 0) then {
- _unit setPosATL _targetPos;
- _unit setVelocity [0,0,0];
- };
- };
- };
- } forEach (units _group);
- };
- };
- };
- } forEach allGroups;
- sleep 3;
- };
- };
- // Player capture related functions
- fnc_captureInProgress = { params ["_unit"]; _unit getVariable ["capturing", false] };
- fnc_startCapture = {
- params ["_unit", "_targetBase", "_targetSide"];
- if ([_unit] call fnc_captureInProgress) exitWith { systemChat "Already capturing!"; };
- _unit setVariable ["capturing", true];
- _unit setVariable ["captureProgress", 0];
- private _captureStartTime = time;
- [_unit, _targetBase, _targetSide, _captureStartTime] spawn {
- params ["_unit", "_targetBase", "_targetSide", "_startTime"];
- while {alive _unit && [_unit] call fnc_captureInProgress && (_unit distance _targetBase) < 5} do {
- private _progress = ((time - _startTime) / 60) * 100;
- _unit setVariable ["captureProgress", _progress];
- hintSilent format ["Capturing %1 Base: %2%3", _targetSide, round _progress, "%"];
- if (_progress >= 100) exitWith {
- _unit setVariable ["capturing", false];
- hint "";
- private _winnerSide = if (side _unit == west) then {"BLUFOR"} else {"OPFOR"};
- private _loserSide = if (_targetSide == "OPFOR") then {"OPFOR"} else {"BLUFOR"};
- // ==================== CHANGE START ====================
- // Use the new centralized function to end the round.
- private _reason = format ["%1 has captured the %2 base!", _winnerSide, _loserSide];
- [_winnerSide, _loserSide, _reason] call fnc_endRound;
- // ===================== CHANGE END =====================
- };
- sleep 0.1;
- };
- _unit setVariable ["capturing", false];
- _unit setVariable ["captureProgress", 0];
- hint "";
- if (!alive _unit) then {
- systemChat "Capture failed - capturer died!";
- } else {
- if ((_unit distance _targetBase) >= 5) then {
- systemChat "Capture failed - moved too far from base!";
- };
- };
- };
- };
- fnc_cancelCapture = { params ["_unit"]; _unit setVariable ["capturing", false]; _unit setVariable ["captureProgress", 0]; hint ""; systemChat "Capture cancelled!"; };
- fnc_setupCaptureActions = {
- if (!hasInterface) exitWith {};
- [] spawn {
- waitUntil {!isNull player};
- sleep 1;
- private _captureActionBlufor = -1;
- private _captureActionOpfor = -1;
- private _cancelActionID = -1;
- while {true} do {
- private _playerSide = side player;
- private _nearOpforBase = (player distance opforSpawnObj) < 5;
- private _nearBluforBase = (player distance bluforSpawnObj) < 5;
- if (_playerSide == west && _nearOpforBase && _captureActionOpfor == -1 && !([player] call fnc_captureInProgress)) then {
- _captureActionOpfor = player addAction [
- "<t color='#FF0000'>Capture OPFOR Base</t>",
- {[_this select 0, opforSpawnObj, "OPFOR"] call fnc_startCapture;},
- nil, 10, true, true, "", "true", 5
- ];
- };
- if (_playerSide == west && (!_nearOpforBase || [player] call fnc_captureInProgress) && _captureActionOpfor != -1) then {
- player removeAction _captureActionOpfor; _captureActionOpfor = -1;
- };
- if (_playerSide == east && _nearBluforBase && _captureActionBlufor == -1 && !([player] call fnc_captureInProgress)) then {
- _captureActionBlufor = player addAction [
- "<t color='#0000FF'>Capture BLUFOR Base</t>",
- {[_this select 0, bluforSpawnObj, "BLUFOR"] call fnc_startCapture;},
- nil, 10, true, true, "", "true", 5
- ];
- };
- if (_playerSide == east && (!_nearBluforBase || [player] call fnc_captureInProgress) && _captureActionBlufor != -1) then {
- player removeAction _captureActionBlufor; _captureActionBlufor = -1;
- };
- if ([player] call fnc_captureInProgress && _cancelActionID == -1) then {
- _cancelActionID = player addAction [
- "<t color='#FFFF00'>Cancel Capture</t>",
- {[_this select 0] call fnc_cancelCapture;},
- nil, 11, true, true, "", "true"
- ];
- };
- if (!([player] call fnc_captureInProgress) && _cancelActionID != -1) then {
- player removeAction _cancelActionID; _cancelActionID = -1;
- };
- sleep 0.5;
- };
- };
- };
- // Function to get unit value for kill rewards
- fnc_getUnitValue = {
- params ["_unit"];
- private "_value";
- if (_unit getVariable ["isAAF", false]) then {
- _value = 1;
- } else {
- if (_unit getVariable ["isElite", false]) then {
- _value = ELITE_VALUE;
- } else {
- if (_unit getVariable ["isSpecOps", false]) then {
- _value = SPECOPS_VALUE;
- } else {
- if (_unit getVariable ["isUpgraded", false]) then {
- _value = UPGRADED_VALUE;
- } else {
- if (_unit getVariable ["isSniper", false]) then {
- _value = SNIPER_VALUE;
- } else {
- _value = INFANTRY_VALUE;
- };
- };
- };
- };
- };
- _value
- };
- // ==================== CHANGE START ====================
- // NEW: Centralized function to end the mission and declare a winner.
- fnc_endRound = {
- params ["_winnerSide", "_loserSide", "_reason"];
- if (!isServer) exitWith {};
- // Prevent this function from running more than once if multiple triggers happen at the same time.
- if (missionNamespace getVariable ["missionEnding", false]) exitWith {};
- missionNamespace setVariable ["missionEnding", true, true];
- private _winnerSideName = toUpper _winnerSide;
- private _endMessage = format ["%1 WINS! %2", _winnerSideName, _reason];
- [
- "EndMission",
- ["", _endMessage]
- ] remoteExec ["BIS_fnc_showNotification", 0]; // Show a large notification to all players.
- private _endType = if (_winnerSide == "BLUFOR") then {"end1"} else {"end2"};
- // End the mission after a short delay to let players see the message.
- sleep 5;
- [_endType, true, true] remoteExec ["BIS_fnc_endMission", 0];
- };
- // Function to prevent infantry from swimming
- fnc_preventInfantrySwimming = {
- if (!isServer) exitWith {};
- [] spawn {
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- sleep 30; // Wait for mission to stabilize
- while {true} do {
- // Get all AI infantry groups
- private _infantryGroups = allGroups select {
- !isNull _x &&
- count (units _x) > 0 &&
- ({isPlayer _x} count (units _x) == 0) &&
- (side _x in [west, east, independent]) &&
- {
- private _leader = leader _x;
- !isNull _leader &&
- alive _leader &&
- vehicle _leader == _leader // Only check infantry on foot
- }
- };
- {
- private _group = _x;
- private _leader = leader _group;
- private _leaderPos = getPosASL _leader;
- // Check if leader is in water
- if (surfaceIsWater _leaderPos) then {
- // Check cooldown to prevent spam
- private _lastRedirect = _group getVariable ["lastSwimRedirect", 0];
- if (time - _lastRedirect > 10) then {
- _group setVariable ["lastSwimRedirect", time];
- // Find nearest land position
- private _landPos = [];
- private _searchRadius = 50;
- private _maxSearchRadius = 300;
- // Expand search radius until land is found
- while {count _landPos == 0 && _searchRadius <= _maxSearchRadius} do {
- for "_angle" from 0 to 330 step 30 do {
- private _testPos = _leaderPos getPos [_searchRadius, _angle];
- if (!surfaceIsWater _testPos) exitWith {
- _landPos = _testPos;
- };
- };
- if (count _landPos == 0) then {
- _searchRadius = _searchRadius + 50;
- };
- };
- // If land found, create forced waypoint
- if (count _landPos > 0) then {
- // Clear existing waypoints
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Create urgent move waypoint to land
- private _wp = _group addWaypoint [_landPos, 0];
- _wp setWaypointType "MOVE";
- _wp setWaypointSpeed "FULL";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointCompletionRadius 30;
- // Force immediate compliance
- _group setSpeedMode "FULL";
- {
- if (alive _x) then {
- _x doMove _landPos;
- };
- } forEach (units _group);
- diag_log format ["[SWIM PREVENTION] Redirected group %1 from water at %2 to land at %3",
- groupId _group, _leaderPos, _landPos];
- };
- };
- };
- } forEach _infantryGroups;
- sleep 10; // Check every 10 seconds
- };
- };
- };
- if (isServer) then {
- // ==================== CHANGE START ====================
- // Initialize the hash map for storing player team selections to prevent team switching.
- missionNamespace setVariable ["PLAYER_TEAM_LOCKS", createHashMap, true];
- // ===================== CHANGE END =====================
- // Initialize randomized bases first (blocking call)
- [] call fnc_createRandomizedBases;
- // Main server-side loops and event handlers
- [] spawn {
- // Wait until all base objects are public and known to missionNamespace
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- // Spawn the new SITREP broadcast loop
- [] spawn fnc_broadcastSitRep;
- // Initialize all playable units as players
- { if (isPlayer _x) then { [_x] call fnc_server_initializePlayer; } } forEach playableUnits;
- // ==================== CHANGE START ====================
- // Handle players connecting after mission start, now with team lock enforcement.
- addMissionEventHandler ["PlayerConnected", {
- params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
- if (_jip) then {
- // Short delay to ensure the player's lobby unit has been created.
- [_uid, _owner] spawn {
- params ["_uid", "_owner"];
- sleep 3; // Longer delay for JIP synchronization
- // Check if this player has a locked team (meaning they disconnected earlier)
- private _locks = missionNamespace getVariable ["PLAYER_TEAM_LOCKS", createHashMap];
- private _lockedSide = _locks getOrDefault [_uid, sideUnknown];
- if (_lockedSide != sideUnknown) then {
- // Player is rejoining - create a new unit for them at base
- [objNull, _lockedSide, _uid, _owner] call fnc_server_playerChooseSide;
- } else {
- // New JIP player - find their unit and set up team selection
- private _playerUnit = objNull;
- {
- if (getPlayerUID _x == _uid) exitWith {
- _playerUnit = _x;
- };
- } forEach allPlayers;
- if (!isNull _playerUnit && side _playerUnit == civilian) then {
- [_playerUnit] remoteExecCall ["fnc_client_setupTeamSelection", owner _playerUnit];
- };
- };
- };
- };
- }];
- // Handle players disconnecting - remove their unit
- addMissionEventHandler ["PlayerDisconnected", {
- params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
- // Find the disconnecting player's unit by UID
- private _disconnectedUnit = objNull;
- {
- if (getPlayerUID _x == _uid) exitWith {
- _disconnectedUnit = _x;
- };
- } forEach allPlayers;
- // If we found their unit, store data and delete it
- if (!isNull _disconnectedUnit) then {
- // Store their side if not already locked (team lock system already handles this)
- private _locks = missionNamespace getVariable ["PLAYER_TEAM_LOCKS", createHashMap];
- private _currentSide = side _disconnectedUnit;
- // Only store side if they're actually on a team (not civilian)
- // Fixed: Check if the key doesn't exist in the hashmap
- if (_currentSide in [west, east] && {!(_uid in _locks)}) then {
- _locks set [_uid, _currentSide];
- missionNamespace setVariable ["PLAYER_TEAM_LOCKS", _locks, true];
- };
- // Delete the disconnected player's unit
- deleteVehicle _disconnectedUnit;
- // Log the disconnection
- diag_log format ["Player %1 (UID: %2) disconnected and unit removed", _name, _uid];
- };
- }];
- // ===================== CHANGE END =====================
- // EntityKilled Event Handler for points and respawn penalties
- addMissionEventHandler ["EntityKilled", {
- params ["_victim", "_killer", "_instigator"];
- // UPDATE STRENGTH TALLY ON DEATH
- if (!isPlayer _victim) then { // We don't tally players
- private _victimSide = side _victim;
- private _strengthVal = _victim getVariable ["strengthValue", 0]; // Get stored value
- if (_strengthVal > 0) then {
- if (_victimSide == west) then {
- BLUFOR_STRENGTH = (BLUFOR_STRENGTH - _strengthVal) max 0; // max 0 prevents negative scores
- } else {
- OPFOR_STRENGTH = (OPFOR_STRENGTH - _strengthVal) max 0;
- };
- };
- };
- private _actualKiller = if (!isNull _killer) then { _killer } else { _instigator };
- if (isNull _actualKiller || {_actualKiller == _victim}) exitWith {};
- private _killerGroup = group _actualKiller;
- private _victimGroup = group _victim;
- if (isNull _killerGroup || isNull _victimGroup) exitWith {};
- private _killerSide = side _killerGroup;
- private _victimSide = side _victimGroup;
- if (_killerSide == _victimSide) then {
- if (isPlayer _actualKiller && _victim isKindOf "CAManBase") then {
- private _currentPoints = _actualKiller getVariable ["playerPoints", 0];
- private _newPoints = (_currentPoints - 5) max 0;
- _actualKiller setVariable ["playerPoints", _newPoints, true];
- private _tkCount = _actualKiller getVariable ["teamkillCount", 0];
- _tkCount = _tkCount + 1;
- _actualKiller setVariable ["teamkillCount", _tkCount, true];
- if (_tkCount >= 3) then {
- _actualKiller setDamage 1;
- _actualKiller setVariable ["teamkillCount", 0, true];
- _actualKiller setVariable ["penaltyRespawn", true, true];
- systemChat format ["%1 has been executed for excessive teamkilling.", name _actualKiller];
- } else {
- [format["TEAMKILL! (%1/3). -5 Points. You now have %2 points.", _tkCount, _newPoints]] remoteExecCall ["hint", owner _actualKiller];
- };
- };
- } else {
- private _victimValue = if (_victim isKindOf "CAManBase") then {
- [_victim] call fnc_getUnitValue;
- } else {
- _victim getVariable ["unitValue", 0];
- };
- if (_killerSide == west) then {
- BLUFOR_POINTS = BLUFOR_POINTS + _victimValue; publicVariable "BLUFOR_POINTS";
- } else {
- if (_killerSide == east) then {
- OPFOR_POINTS = OPFOR_POINTS + _victimValue; publicVariable "OPFOR_POINTS";
- };
- };
- private _pointRecipient = objNull;
- if (isPlayer _actualKiller) then {
- _pointRecipient = _actualKiller;
- } else {
- // Check if the AI killer is owned by a player
- private _owner = _actualKiller getVariable ["ownerPlayer", objNull];
- if (!isNull _owner && isPlayer _owner && alive _owner) then {
- _pointRecipient = _owner;
- };
- };
- // If we have a valid player to give points to (either the killer or the owner)
- if (!isNull _pointRecipient) then {
- private _currentPoints = _pointRecipient getVariable ["playerPoints", 0];
- // ==================== CHANGE START ====================
- private _pointsAwarded = 0;
- if (_victim isKindOf "CAManBase") then {
- _pointsAwarded = 1;
- } else {
- // Award 10 points for tanks (includes APCs) and all air vehicles
- if (_victim isKindOf "Tank" || _victim isKindOf "Air") then {
- _pointsAwarded = 10;
- } else {
- // For other vehicles (MRAPs, Quads), award half their point value, with a minimum of 1
- _pointsAwarded = (_victimValue / 2) max 1;
- };
- };
- // ===================== CHANGE END =====================
- private _isOwner = (_pointRecipient != _actualKiller); // Check if the recipient is the bot's owner
- _pointRecipient setVariable ["playerPoints", _currentPoints + _pointsAwarded, true];
- private _hintMessage = "";
- if (_isOwner) then {
- _hintMessage = format["Your AI squad got a kill! +%1 Personal Points! You now have %2.", _pointsAwarded, _currentPoints + _pointsAwarded];
- } else {
- _hintMessage = format["+%1 Personal Points! You now have %2.", _pointsAwarded, _currentPoints + _pointsAwarded];
- };
- [_hintMessage] remoteExecCall ["hint", owner _pointRecipient];
- };
- };
- // ==================== CHANGE START ====================
- // Deduct points on player death and check for bankruptcy
- if (isPlayer _victim) then {
- // Deduct personal point
- private _playerPoints = _victim getVariable ["playerPoints", 0];
- private _newPlayerPoints = (_playerPoints - 1) max 0;
- _victim setVariable ["playerPoints", _newPlayerPoints, true];
- [format["You have died. -1 Point. You now have %1 points.", _newPlayerPoints]] remoteExecCall ["hint", owner _victim];
- // Deduct team point and check for game end condition
- if (_victimSide == west) then {
- BLUFOR_POINTS = BLUFOR_POINTS - 1;
- publicVariable "BLUFOR_POINTS";
- if (BLUFOR_POINTS < 1) then {
- ["OPFOR", "BLUFOR", "BLUFOR has run out of points to reinforce their troops."] call fnc_endRound;
- };
- } else {
- OPFOR_POINTS = OPFOR_POINTS - 1;
- publicVariable "OPFOR_POINTS";
- if (OPFOR_POINTS < 1) then {
- ["BLUFOR", "OPFOR", "OPFOR has run out of points to reinforce their troops."] call fnc_endRound;
- };
- };
- };
- // ===================== CHANGE END =====================
- }];
- // Spawn the first waves of standard infantry spread around the battlefield
- [] spawn {
- sleep 15;
- waitUntil {!isNil "mission_centralPoint"};
- private _centerPos = missionNamespace getVariable "mission_centralPoint";
- private _bluforBase = getPos bluforSpawnObj;
- private _opforBase = getPos opforSpawnObj;
- // Calculate different approach vectors for varied initial deployment
- private _bluforApproachAngles = [];
- private _opforApproachAngles = [];
- // BLUFOR approaches from their base direction with spread
- private _bluforBaseAngle = _centerPos getDir _bluforBase;
- _bluforApproachAngles pushBack (_bluforBaseAngle + 180); // Center
- _bluforApproachAngles pushBack (_bluforBaseAngle + 180 - 60); // Left flank
- _bluforApproachAngles pushBack (_bluforBaseAngle + 180 + 60); // Right flank
- // OPFOR approaches from their base direction with spread
- private _opforBaseAngle = _centerPos getDir _opforBase;
- _opforApproachAngles pushBack (_opforBaseAngle + 180); // Center
- _opforApproachAngles pushBack (_opforBaseAngle + 180 - 60); // Left flank
- _opforApproachAngles pushBack (_opforBaseAngle + 180 + 60); // Right flank
- for "_i" from 0 to 2 do {
- if (count allUnits >= maxAI) exitWith {};
- // BLUFOR INFANTRY WAVE - spread to different positions
- private _bluforGroup = ["infantry"] call fnc_spawnBluforWave;
- if (!isNull _bluforGroup) then {
- // Each wave goes to a different angle around the center
- private _angle = _bluforApproachAngles select _i;
- private _distance = 400 + (random 300); // 400-700m from center, randomized
- private _targetPos = _centerPos getPos [_distance, _angle];
- [_bluforGroup, _targetPos] call fnc_createPatrolWaypoints;
- };
- sleep 1;
- if (count allUnits >= maxAI) exitWith {};
- // OPFOR INFANTRY WAVE - spread to different positions
- private _opforGroup = ["infantry"] call fnc_spawnOpforWave;
- if (!isNull _opforGroup) then {
- // Each wave goes to a different angle around the center
- private _angle = _opforApproachAngles select _i;
- private _distance = 400 + (random 300); // 400-700m from center, randomized
- private _targetPos = _centerPos getPos [_distance, _angle];
- [_opforGroup, _targetPos] call fnc_createPatrolWaypoints;
- };
- sleep 2;
- };
- };
- // Main AI Wave Spawning Loop (for continuous reinforcement)
- [] spawn {
- sleep 15; // This now acts as a delay AFTER the initial waves
- while {true} do {
- if (count allUnits < maxAI) then {
- // Count alive non-player AI for each side
- private _bluforAliveCount = count (allUnits select {side _x == west && !isPlayer _x && alive _x});
- private _opforAliveCount = count (allUnits select {side _x == east && !isPlayer _x && alive _x});
- // Define the population cap threshold (110% of the enemy)
- private _bluforCap = _opforAliveCount * 1.1;
- private _opforCap = _bluforAliveCount * 1.1;
- // Define the low-point threshold to ignore the cap
- private _lowPointThreshold = 20;
- // Determine if each side is allowed to spawn based on population
- private _bluforCanSpawn = (_bluforAliveCount <= _bluforCap) || (OPFOR_POINTS < _lowPointThreshold);
- private _opforCanSpawn = (_opforAliveCount <= _opforCap) || (BLUFOR_POINTS < _lowPointThreshold);
- // BLUFOR WAVE
- if (_bluforCanSpawn) then {
- private _bluforWaveType = [BLUFOR_POINTS, west] call fnc_getRandomWaveType;
- if (!isNil "_bluforWaveType") then {
- [_bluforWaveType] call fnc_spawnBluforWave;
- };
- };
- sleep (waveDelay / 2);
- // OPFOR WAVE
- if (_opforCanSpawn) then {
- private _opforWaveType = [OPFOR_POINTS, east] call fnc_getRandomWaveType;
- if (!isNil "_opforWaveType") then {
- [_opforWaveType] call fnc_spawnOpforWave;
- };
- };
- sleep waveDelay;
- } else {
- sleep 60;
- };
- };
- };
- // ==================== CHANGE START ====================
- // NEW: Game Win/Loss Condition Check by Unit Count
- [] spawn {
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- // Do not start checks until 10 minutes (600 seconds) have passed.
- waitUntil {time > 600};
- while {true} do {
- if (missionNamespace getVariable ["missionEnding", false]) exitWith {};
- // Count ALL alive units (players + AI) for each side
- private _bluforUnitCount = count (allUnits select {side _x == west && alive _x});
- private _opforUnitCount = count (allUnits select {side _x == east && alive _x});
- // Check for BLUFOR loss due to catastrophic casualties (if they have players)
- if (_bluforUnitCount > 0 && _bluforUnitCount < 10) then {
- if (count (allPlayers select {side _x == west}) > 0) then {
- ["OPFOR", "BLUFOR", "BLUFOR's forces have been eliminated."] call fnc_endRound;
- break;
- };
- };
- // Check for OPFOR loss due to catastrophic casualties (if they have players)
- if (_opforUnitCount > 0 && _opforUnitCount < 10) then {
- if (count (allPlayers select {side _x == east}) > 0) then {
- ["BLUFOR", "OPFOR", "OPFOR's forces have been eliminated."] call fnc_endRound;
- break;
- };
- };
- sleep 15; // Check every 15 seconds
- };
- };
- // ===================== CHANGE END =====================
- // FOB Decoration and Respawn Position Setup
- [] spawn {
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- sleep 3;
- [bluforSpawnObj, west] call fnc_decorateFOB;
- [opforSpawnObj, east] call fnc_decorateFOB;
- [west, getPos bluforSpawnObj, "BLUFOR Base"] call BIS_fnc_addRespawnPosition;
- [east, getPos opforSpawnObj, "OPFOR Base"] call BIS_fnc_addRespawnPosition;
- // Create play area restriction zone
- [] call fnc_createPlayArea;
- };
- };
- };
- if (hasInterface) then {
- [] spawn { [] call fnc_setupCaptureActions; };
- };
- // Global friendly collision damage reduction (always active)
- [] spawn {
- while {true} do {
- {
- if (_x isKindOf "LandVehicle" && !(_x getVariable ["friendlyCollisionSetup", false])) then {
- _x setVariable ["friendlyCollisionSetup", true];
- _x addEventHandler ["HandleDamage", {
- params ["_vehicle", "_selection", "_damage", "_source", "_projectile", "_hitIndex", "_instigator", "_hitPoint"];
- if (_source isKindOf "LandVehicle" && _projectile == "" && _selection == "") then {
- private _vehicleSide = side (effectiveCommander _source);
- private _unitSide = side _vehicle;
- if (_vehicleSide == _unitSide) then {
- _damage = _damage * 0.05;
- };
- };
- _damage
- }];
- };
- } forEach vehicles;
- sleep 10;
- };
- };
- ==================== END OF: mission_setup.sqf ====================
- ==================== START OF: performance.sqf ====================
- // performance.sqf
- // IMPROVED: Enhanced performance monitoring with proper averaging, FPS integration, clean threshold logic,
- // smooth mathematical scaling, hysteresis for stability, and bug fixes (e.g., batch sizes).
- // Performance states: 10 (excellent, ~40+ FPS) to 1 (emergency, <10 FPS).
- // Changes:
- // - Increased history size to 20 for stable 3+ minute averaging.
- // - Replaced nested ifs with efficient array-based threshold counting.
- // - Added diag_fps as third metric for comprehensive server perf monitoring.
- // - Switched to smooth linear interpolation for scales (no more switch/stepped inconsistencies).
- // - Added hysteresis (change only if differs by 1+ state) to prevent oscillation.
- // - Aligned FPS scaling: 40 FPS = 10, 20 FPS ≈ 5, 10 FPS ≈ 3.
- // - Initial scale adjustment on startup.
- // - FIXED: Threshold arrays scoped locally to monitor function.
- // - FIXED: Global init with type checks (prevents String/NaN errors on PERF_STATE /= 10.0).
- // --- Global Performance Variables --- (with type safety)
- if (isNil "PERF_STATE" || {typeName PERF_STATE != "SCALAR"}) then { PERF_STATE = 10; };
- if (isNil "PERF_MAIN_LOOP_MULTIPLIER" || {typeName PERF_MAIN_LOOP_MULTIPLIER != "SCALAR"}) then { PERF_MAIN_LOOP_MULTIPLIER = 1.0; };
- if (isNil "PERF_AI_BATCH_SIZE" || {typeName PERF_AI_BATCH_SIZE != "SCALAR"}) then { PERF_AI_BATCH_SIZE = 10; };
- if (isNil "PERF_SEARCH_RADIUS_MULTIPLIER" || {typeName PERF_SEARCH_RADIUS_MULTIPLIER != "SCALAR"}) then { PERF_SEARCH_RADIUS_MULTIPLIER = 1.0; };
- if (isNil "PERF_COOLDOWN_MULTIPLIER" || {typeName PERF_COOLDOWN_MULTIPLIER != "SCALAR"}) then { PERF_COOLDOWN_MULTIPLIER = 1.0; };
- // Monitor server performance and adjust dynamically
- fnc_monitorPerformance = {
- private _deltaHistory = [];
- private _execTimeHistory = [];
- private _historySize = 20; // 3.3+ min average for stability
- private _hysteresis = 1; // Min state change to trigger update
- // Threshold arrays (local to this function)
- private _deltaThresholds = [0.020, 0.025, 0.030, 0.040, 0.050, 0.060, 0.075, 0.090, 0.110];
- private _execThresholds = [0.015, 0.020, 0.030, 0.040, 0.050, 0.060, 0.075, 0.090, 0.110];
- while {true} do {
- private _schedulerDelta = diag_deltaTime;
- private _lastExecTime = missionNamespace getVariable ["BOTAI_lastExecTime", 0];
- private _fps = diag_fps;
- _deltaHistory pushBack _schedulerDelta;
- _execTimeHistory pushBack _lastExecTime;
- if (count _deltaHistory > _historySize) then { _deltaHistory deleteAt 0; };
- if (count _execTimeHistory > _historySize) then { _execTimeHistory deleteAt 0; };
- private _avgDelta = 0;
- {_avgDelta = _avgDelta + _x;} forEach _deltaHistory;
- _avgDelta = _avgDelta / count _deltaHistory;
- private _avgExecTime = 0;
- {_avgExecTime = _avgExecTime + _x;} forEach _execTimeHistory;
- _avgExecTime = _avgExecTime / count _execTimeHistory;
- // Clean state calculation: 10 - number of exceeded thresholds
- private _deltaState = 10 - (count (_deltaThresholds select {_avgDelta >= _x}));
- private _execState = 10 - (count (_execThresholds select {_avgExecTime >= _x}));
- private _fpsState = round (_fps * 0.25) max 1 min 10; // 40 FPS=10, 20 FPS=5, 10 FPS=3
- private _newState = _deltaState min _execState min _fpsState;
- private _oldState = PERF_STATE;
- // Ensure number, clamp
- _newState = round (_newState max 1 min 10);
- // Hysteresis: only update on meaningful change
- if (_newState <= _oldState - _hysteresis || _newState >= _oldState + _hysteresis) then {
- PERF_STATE = _newState;
- diag_log format ["[PERF] State Change: %1 -> %2 (Delta: %3ms, Exec: %4ms)",
- _oldState, PERF_STATE,
- round (_avgDelta * 1000),
- round (_avgExecTime * 1000)];
- [] call fnc_adjustPerformanceScales;
- };
- sleep 10;
- };
- };
- // Adaptive sleep helper (unchanged)
- fnc_getAdaptiveSleep = {
- params ["_baseSleep"];
- (_baseSleep * PERF_MAIN_LOOP_MULTIPLIER)
- };
- // Smooth mathematical scaling (no switch; fixes batch size bugs)
- fnc_adjustPerformanceScales = {
- // Safe PERF_STATE handling (fallback + clamp)
- private _perfState = 10;
- if (!isNil "PERF_STATE" && {typeName PERF_STATE == "SCALAR"}) then {
- _perfState = PERF_STATE;
- } else {
- PERF_STATE = 10;
- };
- _perfState = round (_perfState max 1 min 10);
- PERF_STATE = _perfState;
- private _scale = _perfState / 10.0; // 1.0 (best) to 0.1 (worst)
- PERF_MAIN_LOOP_MULTIPLIER = 1.0 + 9.0 * (1.0 - _scale); // 1x to 10x slowdown
- PERF_AI_BATCH_SIZE = round(10.0 * _scale); // 10 to 1
- PERF_SEARCH_RADIUS_MULTIPLIER = 0.6 + 0.4 * _scale; // 1.0 to 0.6
- PERF_COOLDOWN_MULTIPLIER = 1.0 + 4.0 * (1.0 - _scale); // 1x to 5x
- };
- if (isServer) then {
- [] spawn {
- sleep 5;
- // Initial scale set
- [] call fnc_adjustPerformanceScales;
- // Start monitoring
- [] spawn fnc_monitorPerformance;
- diag_log "[PERFORMANCE] System initialized.";
- };
- };
- ==================== END OF: performance.sqf ====================
- ==================== START OF: playerinit.sqf ====================
- // playerinit.sqf
- // Client-side initialization and UI functions
- fnc_client_showBriefing = {
- if (isNull (findDisplay 9300)) then {
- if (createDialog "MissionBriefingDialog") then {
- waitUntil {!isNull (findDisplay 9300)};
- };
- };
- };
- missionNamespace setVariable ["fnc_client_showBriefing", fnc_client_showBriefing, true];
- fnc_client_openPlayerMenu = {
- if (isNull (findDisplay 9000)) then {
- if (createDialog "PlayerPurchaseMenu") then {
- private _pointsCtrl = (findDisplay 9000) displayCtrl 9002;
- private _points = player getVariable ["playerPoints", 0];
- _pointsCtrl ctrlSetText format ["Points: %1", _points];
- };
- };
- };
- missionNamespace setVariable ["fnc_client_openPlayerMenu", fnc_client_openPlayerMenu, true];
- fnc_client_addVehicleWaypoint = {
- params ["_vehiclePos", "_displayName"];
- private _grp = group player;
- _grp addWaypoint [_vehiclePos, 0];
- private _newWPIndex = (count (waypoints _grp)) - 1;
- private _wp = (waypoints _grp) select _newWPIndex;
- _wp setWaypointType "MOVE";
- _wp setWaypointCompletionRadius 30;
- [_grp, _newWPIndex] setWaypointDescription format["Your %1", _displayName];
- hint format ["Waypoint added to your map for the %1.", _displayName];
- };
- missionNamespace setVariable ["fnc_client_addVehicleWaypoint", fnc_client_addVehicleWaypoint, true];
- player setVariable ["spawnTime", time, true];
- fnc_client_completeSideSwitch = {
- params ["_newUnit"];
- // CHANGE: Remove the local check that was preventing execution
- // Wait for the unit to become local if it isn't already
- if (!local _newUnit) then {
- waitUntil {local _newUnit || !alive _newUnit};
- };
- // Exit if unit died during the wait
- if (!alive _newUnit) exitWith {
- hint "Failed to join team - unit was destroyed.";
- };
- selectPlayer _newUnit;
- waitUntil {player == _newUnit};
- player setVariable ["sideSwitchComplete", true, true];
- [player] call fnc_initializePlayerUnit;
- [player] call fnc_teleportToBase;
- [player] call fnc_client_initializePlayer;
- waitUntil {!isNil "BLUFOR_POINTS"};
- [] call fnc_client_createTeamScoreUI;
- hint "You have joined a team. Good luck, Officer.";
- // SHOW BRIEFING ON TEAM SWITCH
- [] call fnc_client_showBriefing;
- };
- missionNamespace setVariable ["fnc_client_completeSideSwitch", fnc_client_completeSideSwitch, true];
- fnc_client_createTeamScoreUI = {
- // UI has been disabled by user request as the information is provided in chat.
- };
- fnc_teleportToBase = {
- params ["_unit"];
- if (!local _unit) exitWith {};
- private _side = side _unit;
- private _baseObj = objNull;
- if (_side == west) then {
- _baseObj = bluforSpawnObj;
- } else {
- if (_side == east) then {
- _baseObj = opforSpawnObj;
- };
- };
- if (!isNull _baseObj) then {
- private _spawnPos = getPosATL _baseObj;
- _spawnPos set [2, 0.5];
- if (count _spawnPos < 3 || {!finite (_spawnPos select 0)} || {!finite (_spawnPos select 1)}) exitWith {
- systemChat "Error: Invalid base position detected";
- };
- _unit setPosATL _spawnPos;
- _unit setDir (random 360);
- _unit setVelocity [0,0,0];
- _unit setDamage 0;
- } else {
- systemChat format ["Error: No base object found for side %1", _side];
- };
- };
- fnc_singleplayerRespawn = {
- params ["_deadUnit"];
- private _unitType = typeOf _deadUnit;
- private _unitGroup = group _deadUnit;
- private _unitSide = side _unitGroup;
- private _unitLoadout = _deadUnit getVariable ["savedLoadout", getUnitLoadout _deadUnit];
- private _playerPoints = _deadUnit getVariable ["playerPoints", 0];
- private _teamkillCount = _deadUnit getVariable ["teamkillCount", 0];
- private _isPenaltyRespawn = _deadUnit getVariable ["penaltyRespawn", false];
- private _respawnDelay = if (_isPenaltyRespawn) then {600} else {5};
- private _baseObj = if (_unitSide == west) then {bluforSpawnObj} else {opforSpawnObj};
- private _spawnPos = getPosATL _baseObj;
- _spawnPos set [2, 0.5];
- private _newUnit = _unitGroup createUnit [_unitType, _spawnPos, [], 0, "NONE"];
- _newUnit setPosATL _spawnPos;
- _newUnit setDir (random 360);
- _newUnit enableSimulation false;
- _newUnit hideObjectGlobal true;
- _newUnit allowDamage false;
- selectPlayer _newUnit;
- deleteVehicle _deadUnit;
- cutText ["", "BLACK OUT", 0];
- private _respawnEndTime = time + _respawnDelay;
- while {time < _respawnEndTime} do {
- private _timeLeft = ceil (_respawnEndTime - time);
- private _message = if (_isPenaltyRespawn) then {
- format ["PENALTY RESPAWN IN: %1", [_timeLeft, "MM:SS"] call BIS_fnc_secondsToString]
- } else {
- format ["RESPAWNING IN: %1", _timeLeft]
- };
- cutText [_message, "BLACK FADED", 0];
- sleep 0.1;
- };
- _newUnit enableSimulation true;
- _newUnit hideObjectGlobal false;
- _newUnit setUnitLoadout _unitLoadout;
- _newUnit setVariable ["playerPoints", _playerPoints, true];
- _newUnit setVariable ["teamkillCount", _teamkillCount, true];
- if (_isPenaltyRespawn) then {
- _newUnit setVariable ["penaltyRespawn", false, true];
- _newUnit setVariable ["clearMyPenaltyFlag", true, true];
- };
- [_newUnit] call fnc_initializePlayerUnit;
- cutText ["", "BLACK IN", 2];
- _newUnit setDamage 0;
- _newUnit setFatigue 0;
- // Apply respawn protection
- [_newUnit] call fnc_applyRespawnProtection;
- "DynamicBlur" ppEffectEnable true;
- "DynamicBlur" ppEffectAdjust [0];
- "DynamicBlur" ppEffectCommit 0;
- "DynamicBlur" ppEffectEnable false;
- "RadialBlur" ppEffectEnable true;
- "RadialBlur" ppEffectAdjust [0, 0, 0, 0];
- "RadialBlur" ppEffectCommit 0;
- "RadialBlur" ppEffectEnable false;
- "chromAberration" ppEffectEnable true;
- "chromAberration" ppEffectAdjust [0,0,true];
- "chromAberration" ppEffectCommit 0;
- "chromAberration" ppEffectEnable false;
- "ColorCorrections" ppEffectEnable true;
- "ColorCorrections" ppEffectAdjust [1, 1, 0, [0,0,0,0], [0,0,0,0], [0,0,0,0]];
- "ColorCorrections" ppEffectCommit 0;
- "ColorCorrections" ppEffectEnable false;
- "FilmGrain" ppEffectEnable true;
- "FilmGrain" ppEffectAdjust [0, 1, 1, 1, 0, false];
- "FilmGrain" ppEffectCommit 0;
- "FilmGrain" ppEffectEnable false;
- sleep 0.1;
- [] call fnc_client_showBriefing;
- // Force reset all screen effects 1 second after respawn
- [] spawn {
- sleep 1;
- "DynamicBlur" ppEffectEnable true;
- "DynamicBlur" ppEffectAdjust [0];
- "DynamicBlur" ppEffectCommit 0;
- "DynamicBlur" ppEffectEnable false;
- "RadialBlur" ppEffectEnable true;
- "RadialBlur" ppEffectAdjust [0, 0, 0, 0];
- "RadialBlur" ppEffectCommit 0;
- "RadialBlur" ppEffectEnable false;
- "chromAberration" ppEffectEnable true;
- "chromAberration" ppEffectAdjust [0,0,true];
- "chromAberration" ppEffectCommit 0;
- "chromAberration" ppEffectEnable false;
- "ColorCorrections" ppEffectEnable true;
- "ColorCorrections" ppEffectAdjust [1, 1, 0, [0,0,0,0], [1,1,1,1], [1,1,1,0]];
- "ColorCorrections" ppEffectCommit 0;
- "ColorCorrections" ppEffectEnable false;
- "FilmGrain" ppEffectEnable true;
- "FilmGrain" ppEffectAdjust [0, 1, 1, 1, 0, false];
- "FilmGrain" ppEffectCommit 0;
- "FilmGrain" ppEffectEnable false;
- "WetDistortion" ppEffectEnable true;
- "WetDistortion" ppEffectAdjust [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
- "WetDistortion" ppEffectCommit 0;
- "WetDistortion" ppEffectEnable false;
- "ColorInversion" ppEffectEnable true;
- "ColorInversion" ppEffectAdjust [0,0,0];
- "ColorInversion" ppEffectCommit 0;
- "ColorInversion" ppEffectEnable false;
- enableCamShake false;
- resetCamShake;
- };
- };
- fnc_multiplayerRespawn = {
- params ["_deadUnit"];
- private _unitLoadout = _deadUnit getVariable ["savedLoadout", getUnitLoadout _deadUnit];
- private _playerPoints = _deadUnit getVariable ["playerPoints", 0];
- private _teamkillCount = _deadUnit getVariable ["teamkillCount", 0];
- private _isPenaltyRespawn = _deadUnit getVariable ["penaltyRespawn", false];
- private _respawnDelay = if (_isPenaltyRespawn) then {600} else {5};
- // Store data on the server for post-respawn processing - ONLY for this specific player
- [_deadUnit, _unitLoadout, _playerPoints, _teamkillCount, _isPenaltyRespawn] remoteExecCall ["fnc_server_storeRespawnData", 2, false];
- // Set respawn time ONLY for this player
- setPlayerRespawnTime _respawnDelay;
- };
- // Helper function to check and replace disallowed gear
- fnc_checkAndReplaceGear = {
- params ["_unit"];
- if (isNil "BLUFOR_UNIFORMS" || isNil "OPFOR_UNIFORMS") exitWith {};
- private _side = playerSide;
- private _allowedUniforms = if (_side == west) then {BLUFOR_UNIFORMS} else {OPFOR_UNIFORMS};
- private _allowedVests = if (_side == west) then {BLUFOR_VESTS} else {OPFOR_VESTS};
- private _allowedHeadgear = if (_side == west) then {BLUFOR_HEADGEAR} else {OPFOR_HEADGEAR};
- private _changed = false;
- private _fnc_findReplacement = {
- params ["_invalidItem", "_allowedList"];
- private _replacement = _allowedList # 0;
- private _invalidLower = toLower _invalidItem;
- {
- if (_invalidLower find (toLower (getText (configFile >> "CfgWeapons" >> _x >> "displayName"))) > -1) exitWith {
- _replacement = _x;
- };
- } forEach _allowedList;
- _replacement
- };
- private _currentUniform = uniform _unit;
- // MODIFIED: Check for no uniform OR disallowed uniform
- if (_currentUniform == "" || !(_currentUniform in _allowedUniforms)) then {
- private _uniformItems = [];
- // Only save items if there was a uniform to begin with
- if (_currentUniform != "") then {
- _uniformItems = uniformItems _unit;
- removeUniform _unit;
- };
- // Determine replacement uniform
- private _replacementUniform = if (_currentUniform != "") then {
- [_currentUniform, _allowedUniforms] call _fnc_findReplacement
- } else {
- // No uniform - just use the first default uniform for the faction
- _allowedUniforms # 0
- };
- _unit forceAddUniform _replacementUniform;
- {_unit addItemToUniform _x} forEach _uniformItems;
- _changed = true;
- };
- private _currentVest = vest _unit;
- if (_currentVest != "" && !(_currentVest in _allowedVests)) then {
- private _vestItems = vestItems _unit;
- removeVest _unit;
- private _replacementVest = [_currentVest, _allowedVests] call _fnc_findReplacement;
- _unit addVest _replacementVest;
- {_unit addItemToVest _x} forEach _vestItems;
- _changed = true;
- };
- private _currentHeadgear = headgear _unit;
- if (_currentHeadgear != "" && !(_currentHeadgear in _allowedHeadgear)) then {
- removeHeadgear _unit;
- private _replacementHeadgear = [_currentHeadgear, _allowedHeadgear] call _fnc_findReplacement;
- _unit addHeadgear _replacementHeadgear;
- _changed = true;
- };
- if (_changed) then {
- hint "Invalid gear detected and replaced with allowed items.";
- };
- };
- fnc_initializePlayerUnit = {
- params ["_unit"];
- _unit disableAI "ALL";
- _unit enableAI "ANIM";
- _unit enableAI "MOVE";
- _unit addEventHandler ["InventoryOpened", {
- params ["_unit"];
- [_unit] call fnc_checkAndReplaceGear;
- }];
- _unit addEventHandler ["InventoryClosed", {
- params ["_unit"];
- _unit setVariable ["savedLoadout", getUnitLoadout _unit];
- [_unit] call fnc_checkAndReplaceGear;
- }];
- if (!isMultiplayer) then {
- _unit addEventHandler ["Killed", {
- params ["_unit"];
- // ==================== CHANGE START ====================
- // Get AI units in squad before respawn logic runs
- private _aiToDelete = units (group _unit) select {!isPlayer _x};
- if (count _aiToDelete > 0) then {
- // Tell the server to delete these specific AI units
- [_aiToDelete] remoteExecCall ["fnc_server_deletePlayerAISquad", 2];
- };
- // ===================== CHANGE END =====================
- _unit spawn {
- sleep 0.01;
- [_this] call fnc_singleplayerRespawn;
- };
- }];
- } else {
- _unit addEventHandler ["Killed", {
- params ["_unit"];
- // ==================== CHANGE START ====================
- // Get AI units in squad before respawn logic runs
- private _aiToDelete = units (group _unit) select {!isPlayer _x};
- if (count _aiToDelete > 0) then {
- // Tell the server to delete these specific AI units
- [_aiToDelete] remoteExecCall ["fnc_server_deletePlayerAISquad", 2];
- };
- // ===================== CHANGE END =====================
- _unit spawn {
- sleep 0.01;
- [_this] call fnc_multiplayerRespawn;
- };
- }];
- // NEW: Add Respawn event handler to immediately teleport to base in MP
- _unit addEventHandler ["Respawn", {
- params ["_unit", "_corpse"];
- // Mark the spawn time for tracking
- _unit setVariable ["spawnTime", time, true];
- // Immediately teleport to base
- [_unit] call fnc_teleportToBase;
- }];
- };
- // ==================== CHANGE START ====================
- // MOVED & REWORKED: Player intel reporting loop.
- // This now runs for each new player unit, ensuring intel continues after respawn.
- if (isNil {_unit getVariable "intelLoopStarted"}) then {
- _unit setVariable ["intelLoopStarted", true];
- [_unit] spawn {
- params ["_playerUnit"];
- // Wait for the unit to be on a team.
- waitUntil { !isNull _playerUnit && side _playerUnit in [west, east] };
- // Loop only as long as this specific unit is alive.
- while {alive _playerUnit} do {
- // Call the intel gathering function that AI also uses.
- [_playerUnit, side _playerUnit] call HC_fnc_gatherIntelligence;
- // Check every 8-13 seconds.
- sleep (8 + random 5);
- };
- };
- };
- // ===================== CHANGE END =====================
- };
- fnc_applyRespawnProtection = {
- params ["_unit"];
- if (!local _unit) exitWith {};
- // Set protection flag and make unit invulnerable
- _unit setVariable ["respawnProtection", true, true];
- _unit allowDamage false;
- // Visual feedback - slight transparency effect
- _unit setUnitTrait ["audibleCoef", 0.5];
- _unit setUnitTrait ["camouflageCoef", 0.5];
- // Show protection status
- hint "Spawn Protection Active: 10 seconds";
- // Create the protection removal script
- [_unit] spawn {
- params ["_protectedUnit"];
- private _protectionEndTime = time + 10;
- // Countdown loop
- while {time < _protectionEndTime && alive _protectedUnit} do {
- private _timeLeft = ceil (_protectionEndTime - time);
- hintSilent format ["Spawn Protection: %1 seconds remaining", _timeLeft];
- sleep 0.5;
- };
- // Remove protection
- if (!isNull _protectedUnit) then {
- _protectedUnit allowDamage true;
- _protectedUnit setVariable ["respawnProtection", false, true];
- _protectedUnit setUnitTrait ["audibleCoef", 1];
- _protectedUnit setUnitTrait ["camouflageCoef", 1];
- hintSilent "Spawn Protection Ended";
- // Clear hint after 2 seconds
- [] spawn {
- sleep 2;
- hintSilent "";
- };
- };
- };
- // Failsafe: Additional safety check to prevent permanent godmode
- [_unit] spawn {
- params ["_protectedUnit"];
- sleep 15; // 5 seconds after protection should have ended
- // Force remove protection if it somehow still exists
- if (!isNull _protectedUnit && (_protectedUnit getVariable ["respawnProtection", false])) then {
- _protectedUnit allowDamage true;
- _protectedUnit setVariable ["respawnProtection", false, true];
- _protectedUnit setUnitTrait ["audibleCoef", 1];
- _protectedUnit setUnitTrait ["camouflageCoef", 1];
- diag_log format ["WARNING: Respawn protection failsafe triggered for %1", name _protectedUnit];
- };
- };
- };
- fnc_client_moveIntoVehicle = {
- params ["_vehicle"];
- player assignAsDriver _vehicle;
- player moveInDriver _vehicle;
- };
- if (hasInterface) then {
- // Display play area warning to player
- fnc_client_showPlayAreaWarning = {
- params ["_center", "_radius"];
- // Create warning display
- if (isNil "PLAY_AREA_WARNING_ACTIVE" || {!PLAY_AREA_WARNING_ACTIVE}) then {
- PLAY_AREA_WARNING_ACTIVE = true;
- [] spawn {
- while {!isNil "PLAY_AREA_WARNING_ACTIVE" && {PLAY_AREA_WARNING_ACTIVE}} do {
- private _warningTime = player getVariable ["playAreaWarningTime", 0];
- if (_warningTime == 0) exitWith {
- PLAY_AREA_WARNING_ACTIVE = false;
- };
- private _timeLeft = 10 - (time - _warningTime);
- if (_timeLeft < 0) then {_timeLeft = 0;};
- hintSilent parseText format [
- "<t color='#ff0000' size='2' align='center'>WARNING!</t><br/>" +
- "<t color='#ffff00' size='1.5' align='center'>RETURN TO BATTLE AREA</t><br/>" +
- "<t color='#ffffff' size='1.2' align='center'>%1 SECONDS REMAINING</t>",
- ceil _timeLeft
- ];
- sleep 0.1;
- };
- // Clean up when loop exits
- hintSilent "";
- PLAY_AREA_WARNING_ACTIVE = false;
- };
- };
- };
- // Hide play area warning from player
- fnc_client_hidePlayAreaWarning = {
- if (!isNil "PLAY_AREA_WARNING_ACTIVE") then {
- PLAY_AREA_WARNING_ACTIVE = false;
- };
- hintSilent "";
- };
- // Existing client-side code continues below...
- [] spawn {
- waitUntil { !isNull player && player == player };
- // Compile loadout limitations on client
- [] call compile preprocessFileLineNumbers "loadoutlimitations.sqf";
- // Compile loadout limitations on client
- [] call compile preprocessFileLineNumbers "loadoutlimitations.sqf";
- // Periodic gear check to catch loadout changes bypassing event handlers
- [] spawn {
- waitUntil {!isNil "BLUFOR_UNIFORMS" && !isNil "OPFOR_UNIFORMS"};
- while {true} do {
- if (playerSide in [west, east]) then {
- [player] call fnc_checkAndReplaceGear;
- };
- sleep 5;
- };
- };
- if (side player == civilian) then {
- player setVariable ["bluforAction", player addAction [
- "<t color='#0040FF'>Join BLUFOR</t>",
- {
- params ["_target", "_caller", "_actionId", "_arguments"];
- _caller removeAction _actionId;
- _caller removeAction (_caller getVariable ["opforAction", -1]);
- [_caller, west] remoteExecCall ["fnc_server_playerChooseSide", 2];
- },
- [], 6, true, true, "", "true"
- ]];
- player setVariable ["opforAction", player addAction [
- "<t color='#FF0000'>Join OPFOR</t>",
- {
- params ["_target", "_caller", "_actionId", "_arguments"];
- _caller removeAction _actionId;
- _caller removeAction (_caller getVariable ["bluforAction", -1]);
- [_caller, east] remoteExecCall ["fnc_server_playerChooseSide", 2];
- },
- [], 5, true, true, "", "true"
- ]];
- hint "Choose your side using the scroll wheel menu.";
- } else {
- [player] call fnc_initializePlayerUnit;
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- sleep 1;
- [player] call fnc_teleportToBase;
- [player] call fnc_client_initializePlayer;
- waitUntil {!isNil "BLUFOR_POINTS"};
- [] call fnc_client_createTeamScoreUI;
- // SHOW BRIEFING ON INITIAL SPAWN
- [] call fnc_client_showBriefing;
- };
- };
- [] spawn {
- waitUntil { !isNull player };
- private _trackedMarkers = createHashMap;
- while {true} do {
- private _playerSide = side player;
- if (_playerSide in [west, east]) then {
- private _allFriendlyUnits = allUnits select { side _x == _playerSide && alive _x };
- private _infantryToMark = [];
- private _vehiclesToMark = [];
- private _playersToMark = [];
- {
- private _unit = _x;
- // OPTIMIZATION: Cache unit type on the unit itself to avoid repeated checks
- private _cachedType = _unit getVariable ["markerType", ""];
- if (_cachedType == "") then {
- // First time seeing this unit - determine and cache its type
- if (isPlayer _unit) then {
- _unit setVariable ["markerType", "PLAYER"];
- _cachedType = "PLAYER";
- } else {
- private _vehicle = vehicle _unit;
- if (_vehicle == _unit) then {
- _unit setVariable ["markerType", "INFANTRY"];
- _cachedType = "INFANTRY";
- } else {
- _unit setVariable ["markerType", "VEHICLE"];
- _unit setVariable ["markerVehicle", _vehicle];
- _cachedType = "VEHICLE";
- };
- };
- };
- // Use cached type for categorization
- switch (_cachedType) do {
- case "PLAYER": { _playersToMark pushBackUnique _unit; };
- case "INFANTRY": { _infantryToMark pushBack _unit; };
- case "VEHICLE": {
- private _veh = _unit getVariable ["markerVehicle", vehicle _unit];
- if (!isNull _veh && alive _veh) then {
- _vehiclesToMark pushBackUnique _veh;
- };
- };
- };
- } forEach _allFriendlyUnits;
- private _allCurrentNetIDs = (_infantryToMark apply {netId _x}) + (_vehiclesToMark apply {netId _x}) + (_playersToMark apply {netId _x});
- private _markersToDelete = [];
- {
- private _entityNetId = _x;
- if !(_entityNetId in _allCurrentNetIDs) then {
- private _markerName = _y;
- deleteMarkerLocal _markerName;
- _markersToDelete pushBack _entityNetId;
- };
- } forEach _trackedMarkers;
- { _trackedMarkers deleteAt _x } forEach _markersToDelete;
- private _sideColor = if (_playerSide == west) then {"ColorBlue"} else {"ColorRed"};
- private _markerPrefix = if (_playerSide == west) then {"b_"} else {"o_"};
- {
- private _vehicle = _x;
- private _vehicleNetId = netId _vehicle;
- private _markerName = format ["friendly_marker_%1", _vehicleNetId];
- // OPTIMIZATION: Only update position if vehicle moved >10m
- private _lastPos = _vehicle getVariable ["lastMarkerPos", [0,0,0]];
- private _currentPos = getPos _vehicle;
- private _shouldUpdate = (_lastPos distance2D _currentPos) > 10;
- if (isNil {_trackedMarkers get _vehicleNetId}) then {
- // New marker
- private _markerType = "mil_unknown";
- if (_vehicle isKindOf "Air") then {
- _markerType = _markerPrefix + "air";
- } else {
- if (_vehicle isKindOf "Tank") then {
- _markerType = _markerPrefix + "armor";
- } else {
- if (_vehicle isKindOf "APC") then {
- _markerType = _markerPrefix + "mech_inf";
- } else {
- _markerType = _markerPrefix + "motor_inf";
- };
- };
- };
- createMarkerLocal [_markerName, _currentPos];
- _markerName setMarkerTypeLocal _markerType;
- _markerName setMarkerColorLocal _sideColor;
- _markerName setMarkerSizeLocal [0.8, 0.8];
- _markerName setMarkerAlphaLocal 1;
- _markerName setMarkerTextLocal "";
- _trackedMarkers set [_vehicleNetId, _markerName];
- _vehicle setVariable ["lastMarkerPos", _currentPos];
- } else {
- // Existing marker - only update if moved significantly
- if (_shouldUpdate) then {
- _markerName setMarkerPosLocal _currentPos;
- _vehicle setVariable ["lastMarkerPos", _currentPos];
- };
- };
- } forEach _vehiclesToMark;
- {
- private _unit = _x;
- private _unitNetId = netId _unit;
- private _markerName = format ["friendly_marker_%1", _unitNetId];
- // OPTIMIZATION: Only update position if unit moved >10m
- private _lastPos = _unit getVariable ["lastMarkerPos", [0,0,0]];
- private _currentPos = getPos _unit;
- private _shouldUpdate = (_lastPos distance2D _currentPos) > 10;
- if (isNil {_trackedMarkers get _unitNetId}) then {
- createMarkerLocal [_markerName, _currentPos];
- _markerName setMarkerTypeLocal "mil_dot";
- _markerName setMarkerColorLocal _sideColor;
- _markerName setMarkerSizeLocal [0.7, 0.7];
- _markerName setMarkerAlphaLocal 1;
- _markerName setMarkerTextLocal "";
- _trackedMarkers set [_unitNetId, _markerName];
- _unit setVariable ["lastMarkerPos", _currentPos];
- } else {
- if (_shouldUpdate) then {
- _markerName setMarkerPosLocal _currentPos;
- _unit setVariable ["lastMarkerPos", _currentPos];
- };
- };
- } forEach _infantryToMark;
- {
- private _unit = _x;
- private _unitNetId = netId _unit;
- private _markerName = format ["friendly_marker_%1", _unitNetId];
- // OPTIMIZATION: Only update position if unit moved >10m
- private _lastPos = _unit getVariable ["lastMarkerPos", [0,0,0]];
- private _currentPos = getPos _unit;
- private _shouldUpdate = (_lastPos distance2D _currentPos) > 10;
- if (isNil {_trackedMarkers get _unitNetId}) then {
- createMarkerLocal [_markerName, _currentPos];
- _markerName setMarkerTypeLocal "mil_dot";
- _markerName setMarkerColorLocal _sideColor;
- _markerName setMarkerSizeLocal [0.7, 0.7];
- _markerName setMarkerAlphaLocal 1;
- _markerName setMarkerTextLocal (name _unit);
- _trackedMarkers set [_unitNetId, _markerName];
- _unit setVariable ["lastMarkerPos", _currentPos];
- } else {
- if (_shouldUpdate) then {
- _markerName setMarkerPosLocal _currentPos;
- _unit setVariable ["lastMarkerPos", _currentPos];
- };
- };
- } forEach _playersToMark;
- };
- // OPTIMIZATION: Increased from 2s to 3s
- sleep 3;
- };
- };
- [] spawn {
- waitUntil { !isNull player && !isNil "BLUFOR_INTEL" && !isNil "OPFOR_INTEL" };
- private _enemyIntelMarkers = createHashMap;
- while {true} do {
- private _playerSide = side player;
- private _currentIntelData = if (_playerSide == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _allKnownIntelNetIds = keys _currentIntelData;
- private _enemyColorName = "ColorRed";
- if (_playerSide == east) then { _enemyColorName = "ColorBlue"; };
- private _aafColorName = "ColorGreen";
- private _markerPrefix = if (_playerSide == west) then {"o_"} else {"b_"};
- private _aafMarkerPrefix = "n_";
- // OPTIMIZATION: Pre-calculate decay threshold (90% of max age)
- private _maxIntelAge = HC_INTEL_DECAY_TIME;
- private _staleThreshold = _maxIntelAge * 0.9;
- {
- private _enemyNetId = _x;
- private _intelReport = _currentIntelData get _enemyNetId;
- if (isNil "_intelReport" || {typeName _intelReport != "HASHMAP"}) then {
- continue;
- };
- private _timeSpotted = _intelReport get "time";
- private _intelAge = time - _timeSpotted;
- // OPTIMIZATION: Skip intel that's about to expire (>90% of max age)
- if (_intelAge > _staleThreshold) then {
- continue;
- };
- private _enemyPos = _intelReport get "position";
- private _isAAF = _intelReport get "isAAF";
- private _entityType = _intelReport getOrDefault ["entityType", "UNKNOWN"];
- private _isTank = _intelReport get "isTank";
- // OPTIMIZATION: Pre-calculate marker name once
- private _markerName = format ["enemy_intel_%1", _enemyNetId];
- private _fadeAlpha = 1 - (_intelAge / _maxIntelAge);
- _fadeAlpha = (_fadeAlpha max 0.2) min 1.0;
- private _markerColorName = if (_isAAF) then { _aafColorName } else { _enemyColorName };
- private _markerType = "mil_dot";
- private _currentPrefix = if (_isAAF) then {_aafMarkerPrefix} else {_markerPrefix};
- if (_entityType == "GROUND_VEHICLE") then {
- if (_isTank) then {
- _markerType = _currentPrefix + "armor";
- } else {
- _markerType = _currentPrefix + "mech_inf";
- };
- } else {
- if (_entityType == "AIR_VEHICLE") then {
- _markerType = _currentPrefix + "air";
- };
- };
- if (isNil {_enemyIntelMarkers get _markerName}) then {
- createMarkerLocal [_markerName, _enemyPos];
- _enemyIntelMarkers set [_markerName, true];
- };
- _markerName setMarkerPosLocal _enemyPos;
- _markerName setMarkerTypeLocal _markerType;
- _markerName setMarkerColorLocal _markerColorName;
- _markerName setMarkerAlphaLocal _fadeAlpha;
- _markerName setMarkerSizeLocal [0.8, 0.8];
- _markerName setMarkerTextLocal "";
- } forEach _allKnownIntelNetIds;
- private _markersToDelete = [];
- {
- private _markerName = _x;
- private _enemyNetId = (_markerName splitString "_") select 2;
- if !(_enemyNetId in _allKnownIntelNetIds) then {
- deleteMarkerLocal _markerName;
- _markersToDelete pushBack _markerName;
- };
- } forEach (keys _enemyIntelMarkers);
- {
- _enemyIntelMarkers deleteAt _x;
- } forEach _markersToDelete;
- // OPTIMIZATION: Increased from 2s to 3s
- sleep 3;
- };
- };
- };
- if (isServer) then {
- [] spawn {
- waitUntil {time > 0};
- {
- if (!isPlayer _x && isPlayer leader _x == false) then {
- _x disableAI "ALL";
- _x enableAI "ANIM";
- _x enableAI "MOVE";
- };
- } forEach playableUnits;
- };
- };
- fnc_client_setupTeamSelection = {
- params ["_unit"];
- if (!hasInterface || _unit != player) exitWith {};
- // Wait for base objects to be available
- waitUntil {!isNil "bluforSpawnObj" && !isNil "opforSpawnObj"};
- _unit setVariable ["bluforAction", _unit addAction [
- "<t color='#0040FF'>Join BLUFOR</t>",
- {
- params ["_target", "_caller", "_actionId", "_arguments"];
- _caller removeAction _actionId;
- _caller removeAction (_caller getVariable ["opforAction", -1]);
- [_caller, west] remoteExecCall ["fnc_server_playerChooseSide", 2];
- },
- [], 6, true, true, "", "true"
- ]];
- _unit setVariable ["opforAction", _unit addAction [
- "<t color='#FF0000'>Join OPFOR</t>",
- {
- params ["_target", "_caller", "_actionId", "_arguments"];
- _caller removeAction _actionId;
- _caller removeAction (_caller getVariable ["bluforAction", -1]);
- [_caller, east] remoteExecCall ["fnc_server_playerChooseSide", 2];
- },
- [], 5, true, true, "", "true"
- ]];
- hint "Choose your side using the scroll wheel menu.";
- };
- ==================== END OF: playerinit.sqf ====================
- ==================== START OF: player_server.sqf ====================
- // player_server.sqf
- // Contains server-side logic for player points and purchases.
- fnc_server_purchaseVehicle = {
- params ["_player", "_vehicleClass"];
- if (!isServer) exitWith {};
- // Get vehicle data from server-side config (now from init.sqf)
- private _data = PURCHASE_VEHICLE_DATA getOrDefault [_vehicleClass, nil];
- if (isNil "_data") exitWith {
- ["SERVER: Invalid vehicle type selected."] remoteExecCall ["hint", owner _player];
- };
- private _displayName = _data select 0;
- private _cost = _data select 1;
- private _requiredSide = _data select 2;
- private _playerSide = side _player;
- private _playerPoints = _player getVariable ["playerPoints", 0];
- // Check if player is on the correct side for this vehicle
- if (_playerSide != _requiredSide) exitWith {
- ["SERVER: This vehicle is not available for your faction."] remoteExecCall ["hint", owner _player];
- };
- // Verify player has enough points
- if (_playerPoints < _cost) exitWith {
- [format["SERVER: Not enough points. You need %1, you have %2.", _cost, _playerPoints]] remoteExecCall ["hint", owner _player];
- };
- // Deduct points and broadcast the new value
- _player setVariable ["playerPoints", _playerPoints - _cost, true];
- // Find a flatter spawn position in a larger radius.
- private _baseObj = if (_playerSide == west) then { bluforSpawnObj } else { opforSpawnObj };
- private _spawnPos = [getPos _baseObj, 20, 100, 15, 0, 0.1, 0] call BIS_fnc_findSafePos;
- if (count _spawnPos == 0) then { _spawnPos = getPos _baseObj; }; // Fallback
- private _vehicle = createVehicle [_vehicleClass, _spawnPos vectorAdd [0,0,1.5], [], 0, "NONE"];
- _vehicle setPos _spawnPos;
- _vehicle setVectorUp [0,0,1];
- _vehicle setVelocity [0,0,0];
- // ==================== CHANGE START ====================
- _vehicle setVariable ["unitValue", _cost, true]; // Set the point value for kill rewards
- // ===================== CHANGE END =====================
- _vehicle lock 1;
- _vehicle setVariable ["ownerPlayer", _player, true];
- [_vehicle] remoteExecCall ["fnc_client_moveIntoVehicle", owner _player];
- [getPosATL _vehicle, _displayName] remoteExecCall ["fnc_client_addVehicleWaypoint", owner _player];
- [format["%1 delivered. %2 points remaining.", _displayName, _player getVariable "playerPoints"]] remoteExecCall ["hint", owner _player];
- };
- fnc_server_initializePlayer = {
- params ["_player"];
- if (!isServer) exitWith {};
- _player setVariable ["playerPoints", 5, true];
- _player setVariable ["teamkillCount", 0, true];
- _player setVariable ["penaltyRespawn", false, true];
- _player setVariable ["clearMyPenaltyFlag", false, true];
- [
- "Welcome! You start with 1 personal point. Get kills to earn more. Approach the arsenal box to open the purchase menu."
- ] remoteExecCall ["hint", owner _player];
- };
- // Server-side function to paradrop a player near the AAF stronghold
- fnc_server_paradropPlayer = {
- params ["_player"];
- if (!isServer) exitWith {};
- // Check if 2 minutes have passed
- if (time < 120) exitWith {
- ["Paradrop unavailable: Must wait 2 minutes into the mission."] remoteExecCall ["hint", owner _player];
- };
- // Check if mission_centralPoint exists (AAF stronghold position)
- if (isNil "mission_centralPoint") exitWith {
- ["Paradrop unavailable: Stronghold location unknown."] remoteExecCall ["hint", owner _player];
- };
- private _playerPoints = _player getVariable ["playerPoints", 0];
- private _cost = 2;
- // Verify player has enough points
- if (_playerPoints < _cost) exitWith {
- [format["Not enough points. You need %1, you have %2.", _cost, _playerPoints]] remoteExecCall ["hint", owner _player];
- };
- // Get player's side and base position
- private _playerSide = side _player;
- private _ownBase = if (_playerSide == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _strongholdPos = missionNamespace getVariable ["mission_centralPoint", [0,0,0]];
- // Validate stronghold position
- if (_strongholdPos isEqualTo [0,0,0]) exitWith {
- ["Paradrop unavailable: Invalid stronghold location."] remoteExecCall ["hint", owner _player];
- };
- // Calculate direction from stronghold to player's base
- private _dirToBase = _strongholdPos getDir _ownBase;
- // Calculate drop position: 300m offset toward own base, then randomize within 100m radius
- private _baseDropPos = _strongholdPos getPos [300, _dirToBase];
- // Add random offset within 100m radius
- private _randomAngle = random 360;
- private _randomDistance = random 100;
- private _dropPos = _baseDropPos getPos [_randomDistance, _randomAngle];
- // Set altitude to a random value between 400 and 600m
- _dropPos set [2, 400 + random 200];
- // Deduct points
- _player setVariable ["playerPoints", _playerPoints - _cost, true];
- private _groupToDrop = units (group _player);
- private _unitData = []; // Store original backpacks and other data
- {
- private _unit = _x;
- // Store unit's current backpack data
- _unitData pushBack [_unit, backpack _unit, backpackItems _unit, backpackMagazines _unit];
- // Calculate a slightly offset drop position for each unit
- private _offsetDropPos = _dropPos getPos [random 25, random 360];
- _offsetDropPos set [2, (_dropPos select 2) + (random 50)];
- // Disable damage before teleporting to prevent accidental death
- if (!isPlayer _unit) then {
- _unit allowDamage false;
- // NEW: Set flag to track parachute status
- _unit setVariable ["parachuteGiven", false, true];
- };
- // Teleport unit
- _unit setPosASL (AGLToASL _offsetDropPos);
- _unit setVelocity [0, 0, 0];
- // Only give the parachute to the human player. AI will get theirs later.
- if (isPlayer _unit) then {
- _unit addBackpack "B_Parachute";
- };
- } forEach _groupToDrop;
- // Notify player
- [format["Paradrop initiated for your squad! %1 points remaining.", _playerPoints - _cost]] remoteExecCall ["hint", owner _player];
- // Monitor for landing and restore backpacks for the whole group
- [_unitData] spawn {
- params ["_allUnitData"];
- while {count _allUnitData > 0} do {
- {
- private _dataEntry = _x;
- private _unit = _dataEntry select 0;
- if (!alive _unit) then {
- _allUnitData = _allUnitData - [_dataEntry];
- };
- if (isPlayer _unit) then {
- // This is the player unit - force their chute open at 120m as a failsafe
- if (alive _unit && !isTouchingGround _unit && backpack _unit == "B_Parachute" && ((getPosATL _unit) select 2) <= 120) then {
- _unit action ["OpenParachute", vehicle _unit];
- };
- } else {
- // This is an AI unit - give them a chute and open it at 120m
- // NEW: Check if parachute was already given using flag and vehicle check
- private _parachuteGiven = _unit getVariable ["parachuteGiven", false];
- private _vehicle = vehicle _unit;
- private _isInParachute = (_vehicle != _unit && {_vehicle isKindOf "ParachuteBase"});
- if (alive _unit && !isTouchingGround _unit && !_parachuteGiven && !_isInParachute && ((getPosATL _unit) select 2) <= 120) then {
- _unit addBackpack "B_Parachute";
- sleep 0.1;
- _unit action ["OpenParachute", vehicle _unit];
- // Set flag to prevent multiple parachutes
- _unit setVariable ["parachuteGiven", true, true];
- // Re-enable damage after the chute is open
- _unit allowDamage true;
- };
- };
- if (alive _unit && isTouchingGround _unit) then {
- private _originalBackpack = _dataEntry select 1;
- private _items = _dataEntry select 2;
- private _magazines = _dataEntry select 3;
- sleep 0.5;
- // Failsafe in case unit lands without ever opening chute
- if (!isPlayer _unit) then {
- _unit allowDamage true;
- // Clear the parachute flag
- _unit setVariable ["parachuteGiven", nil, true];
- };
- if (_originalBackpack != "") then {
- if (backpack _unit == "B_Parachute") then {
- removeBackpack _unit;
- };
- _unit addBackpack _originalBackpack;
- clearBackpackCargoGlobal (backpackContainer _unit);
- { (backpackContainer _unit) addItemCargoGlobal [_x, 1]; } forEach _items;
- { (backpackContainer _unit) addMagazineCargoGlobal [_x, 1]; } forEach _magazines;
- };
- _allUnitData = _allUnitData - [_dataEntry];
- };
- } forEach _allUnitData;
- sleep 0.5;
- };
- };
- };
- // Server-side function to store respawn data and handle post-respawn processing
- fnc_server_storeRespawnData = {
- params ["_deadUnit", "_unitLoadout", "_playerPoints", "_teamkillCount", "_isPenaltyRespawn"];
- if (!isServer) exitWith {};
- private _playerUID = getPlayerUID _deadUnit;
- // Store the data with a unique key for this specific death event
- private _deathTime = time;
- private _respawnDataKey = format["respawnData_%1_%2", _playerUID, _deathTime];
- private _respawnData = createHashMap;
- _respawnData set ["loadout", _unitLoadout];
- _respawnData set ["points", _playerPoints];
- _respawnData set ["teamkills", _teamkillCount];
- _respawnData set ["penalty", _isPenaltyRespawn];
- _respawnData set ["deathTime", _deathTime];
- missionNamespace setVariable [_respawnDataKey, _respawnData, false];
- // Monitor for this specific player's respawn using their UID and death time
- [_playerUID, _deathTime] spawn {
- params ["_uid", "_deathTime"];
- private _timeout = time + 60;
- private _respawnDataKey = format["respawnData_%1_%2", _uid, _deathTime];
- while {time < _timeout} do {
- // Look for a player with matching UID who respawned after the death time
- private _respawnedPlayer = objNull;
- {
- if (isPlayer _x &&
- getPlayerUID _x == _uid &&
- alive _x &&
- (_x getVariable ["spawnTime", 0]) > _deathTime) exitWith {
- _respawnedPlayer = _x;
- };
- } forEach allPlayers;
- if (!isNull _respawnedPlayer) exitWith {
- private _storedData = missionNamespace getVariable [_respawnDataKey, createHashMap];
- if (count _storedData > 0) then {
- private _loadout = _storedData get "loadout";
- private _points = _storedData get "points";
- private _teamkills = _storedData get "teamkills";
- private _penalty = _storedData get "penalty";
- // Apply data to this specific player
- _respawnedPlayer setUnitLoadout _loadout;
- _respawnedPlayer setVariable ["playerPoints", _points, true];
- _respawnedPlayer setVariable ["teamkillCount", _teamkills, true];
- if (_penalty) then {
- _respawnedPlayer setVariable ["clearMyPenaltyFlag", true, true];
- };
- // Apply respawn actions to ONLY this player
- private _playerOwner = owner _respawnedPlayer;
- [_respawnedPlayer] remoteExecCall ["fnc_teleportToBase", _playerOwner, false];
- [_respawnedPlayer] remoteExecCall ["fnc_initializePlayerUnit", _playerOwner, false];
- [_respawnedPlayer] remoteExecCall ["fnc_applyRespawnProtection", _playerOwner, false];
- [] remoteExecCall ["fnc_client_showBriefing", _playerOwner, false];
- // Clean up
- missionNamespace setVariable [_respawnDataKey, nil, false];
- };
- };
- sleep 1;
- };
- // Cleanup on timeout
- missionNamespace setVariable [_respawnDataKey, nil, false];
- };
- };
- // Server-side function to handle a player choosing their side.
- fnc_server_playerChooseSide = {
- params ["_civilianUnit", "_chosenSide"];
- if (!isServer) exitWith {};
- // Handle case where civilian unit might be null (for rejoining players)
- private _isRejoining = isNull _civilianUnit;
- // Get player info differently based on whether they're rejoining
- private _playerUID = "";
- private _playerOwner = 0;
- private _currentPlayerUnit = objNull;
- if (_isRejoining) then {
- // For rejoining players, we need to find them by the side they're joining
- _playerUID = param [2, ""];
- _playerOwner = param [3, 0];
- // Find if this player already has a unit they're controlling
- {
- if (getPlayerUID _x == _playerUID) exitWith {
- _currentPlayerUnit = _x;
- };
- } forEach allPlayers;
- } else {
- // Normal case - player choosing side from civilian
- if (!alive _civilianUnit) exitWith {};
- _playerUID = getPlayerUID _civilianUnit;
- _playerOwner = owner _civilianUnit;
- _currentPlayerUnit = _civilianUnit;
- };
- if (_playerUID != "") then {
- (missionNamespace getVariable "PLAYER_TEAM_LOCKS") set [_playerUID, _chosenSide];
- };
- // Spawn the entire process to allow delays
- [_civilianUnit, _chosenSide, _playerUID, _playerOwner, _isRejoining, _currentPlayerUnit] spawn {
- params ["_civilianUnit", "_chosenSide", "_playerUID", "_playerOwner", "_isRejoining", "_currentPlayerUnit"];
- private _officerType = if (_chosenSide == west) then {"B_officer_F"} else {"O_officer_F"};
- private _spawnObj = if (_chosenSide == west) then {bluforSpawnObj} else {opforSpawnObj};
- private _spawnPos = getPosATL _spawnObj;
- _spawnPos set [2, 0.5];
- private _playerGroup = createGroup _chosenSide;
- _playerGroup setGroupOwner _playerOwner;
- sleep 0.1;
- private _newUnit = _playerGroup createUnit [_officerType, _spawnPos, [], 0, "NONE"];
- // Mark the spawn time for respawn tracking
- _newUnit setVariable ["spawnTime", time, true];
- [_newUnit] call fnc_server_initializePlayer;
- sleep 0.2;
- // Switch player control
- [_newUnit] remoteExecCall ["fnc_client_completeSideSwitch", _playerOwner];
- // Wait for the switch to complete before deleting old unit
- private _timeout = time + 15;
- waitUntil {(_newUnit getVariable ["sideSwitchComplete", false]) || time > _timeout};
- // Delete the old unit (whether it's civilian or any other unit the player was controlling)
- if (!isNull _currentPlayerUnit && _currentPlayerUnit != _newUnit) then {
- deleteVehicle _currentPlayerUnit;
- };
- };
- };
- [] spawn {
- if (!isServer) exitWith {};
- while {true} do {
- sleep 5;
- {
- if (_x getVariable ["clearMyPenaltyFlag", false]) then {
- _x setVariable ["penaltyRespawn", false, true];
- _x setVariable ["clearMyPenaltyFlag", false, true];
- };
- } forEach playableUnits;
- };
- };
- fnc_server_purchaseAISquad = {
- params ["_player"];
- if (!isServer) exitWith {};
- private _cost = missionNamespace getVariable ["PLAYER_SQUAD_COST", 15];
- private _cooldown = missionNamespace getVariable ["PLAYER_SQUAD_COOLDOWN", 120];
- private _lastPurchaseTime = _player getVariable ["lastAISquadPurchaseTime", 0];
- // Cooldown check
- if (time < _lastPurchaseTime + _cooldown) exitWith {
- private _timeLeft = round((_lastPurchaseTime + _cooldown) - time);
- [format["AI Squad purchase on cooldown. Time remaining: %1s", _timeLeft]] remoteExecCall ["hint", owner _player];
- };
- // Cost check
- private _playerPoints = _player getVariable ["playerPoints", 0];
- if (_playerPoints < _cost) exitWith {
- [format["Not enough points to purchase an AI squad. Cost: %1, You have: %2.", _cost, _playerPoints]] remoteExecCall ["hint", owner _player];
- };
- // Deduct points
- _player setVariable ["playerPoints", _playerPoints - _cost, true];
- private _playerGroup = group _player;
- private _playerSide = side _player;
- // Wipe existing group members (except player)
- {
- if (_x != _player) then {
- private _vehicle = vehicle _x;
- // If the AI is in a parachute, delete the parachute as well.
- if (_vehicle != _x && {_vehicle isKindOf "ParachuteBase"}) then {
- deleteVehicle _vehicle;
- };
- deleteVehicle _x;
- };
- } forEach (units _playerGroup);
- // Select unit pool
- private _reconPool = if (_playerSide == west) then { BLUFOR_SPECOPS_POOL } else { OPFOR_SPECOPS_POOL };
- if (isNil "_reconPool" || {count _reconPool == 0}) exitWith { diag_log "ERROR: Recon pool not found for player's side."; };
- private _squadComposition = selectRandom _reconPool;
- // Spawn 3 random recon units
- for "_i" from 1 to 3 do {
- private _unitType = selectRandom _squadComposition;
- private _spawnPos = getPos _player;
- private _unit = _playerGroup createUnit [_unitType, _spawnPos, [], 5, "NONE"];
- // Apply recon AI settings from fnc_spawnBluforSpecOps
- [_unit, 0.75, "SPECOPS"] call fnc_enhanceIndividualAI;
- _unit setVariable ["isSpecOps", true, true];
- _unit setVariable ["unitValue", SPECOPS_VALUE, true];
- [_unit] call fnc_markAsImportant;
- [_unit, _playerSide] call fnc_updateStrengthTally; // Count towards maxAI/strength
- _unit setVariable ["ownerPlayer", _player, true]; // TAG THE AI WITH ITS OWNER
- };
- // Check and enforce hard AI cap after spawning
- private _sideAICount = count (allUnits select {side _x == _playerSide && !isPlayer _x && alive _x});
- private _hardCap = maxAI + 10;
- if (_sideAICount > _hardCap) then {
- private _unitsToDeleteCount = _sideAICount - _hardCap;
- systemChat format ["%1 side is over AI hard cap. Culling %2 units.", _playerSide, _unitsToDeleteCount];
- // Find all AI on the team, excluding the player's own group
- private _potentialUnitsToDelete = allUnits select {
- side _x == _playerSide &&
- !isPlayer _x &&
- alive _x &&
- group _x != _playerGroup
- };
- // Prioritize deleting standard infantry first
- private _standardInfantry = _potentialUnitsToDelete select {
- (_x getVariable ["unitValue", 999]) == INFANTRY_VALUE
- };
- // Delete the required number of units
- for "_i" from 1 to _unitsToDeleteCount do {
- if (count _standardInfantry > 0) then {
- private _unitToDelete = _standardInfantry select 0; // Get the first one
- deleteVehicle _unitToDelete;
- _standardInfantry deleteAt 0; // Remove it from the list
- } else {
- diag_log "AI hard cap reached, but no more standard infantry to delete.";
- break;
- };
- };
- };
- // Apply cooldown AFTER successful purchase
- _player setVariable ["lastAISquadPurchaseTime", time, true];
- [format["AI Recon Squad purchased for %1 points. %2 points remaining.", _cost, _player getVariable "playerPoints"]] remoteExecCall ["hint", owner _player];
- };
- // =====================================================================
- // Admin/Host/Singleplayer Settings Functions
- // =====================================================================
- // Function to set the max AI count
- fnc_server_setMaxAI = {
- params ["_count"];
- if (!isServer) exitWith {}; // Only server can change this
- maxAI = _count;
- publicVariable "maxAI";
- hint format ["Server: Max AI count has been set to %1.", maxAI];
- };
- // Function to set the time multiplier
- fnc_server_setTimescale = {
- params ["_multiplier"];
- if (!isServer) exitWith {}; // Only server can change this
- setTimeMultiplier _multiplier;
- private _text = "Realtime";
- if (_multiplier == 12) then { _text = "2h = 1 Day"; };
- if (_multiplier == 24) then { _text = "1h = 1 Day"; };
- hint format ["Server: Timescale set to %1.", _text];
- };
- // Function to adjust team points (SP only)
- fnc_server_adjustTeamPoints = {
- params ["_sideStr", "_amount"];
- if (!isServer || isMultiplayer) exitWith {}; // Only server in singleplayer
- if (_sideStr == "BLUFOR") then {
- BLUFOR_POINTS = BLUFOR_POINTS + _amount;
- publicVariable "BLUFOR_POINTS";
- hint format ["BLUFOR points adjusted by %1. New total: %2", _amount, round BLUFOR_POINTS];
- } else {
- OPFOR_POINTS = OPFOR_POINTS + _amount;
- publicVariable "OPFOR_POINTS";
- hint format ["OPFOR points adjusted by %1. New total: %2", _amount, round OPFOR_POINTS];
- };
- };
- // Function to add personal points to the player (SP only)
- fnc_server_addPersonalPoints = {
- params ["_player", "_amount"];
- if (!isServer || isMultiplayer) exitWith {}; // Only server in singleplayer
- private _currentPoints = _player getVariable ["playerPoints", 0];
- _player setVariable ["playerPoints", _currentPoints + _amount, true];
- [format ["Added %1 personal points. You now have %2.", _amount, _player getVariable 'playerPoints']] remoteExecCall ["hint", owner _player];
- };
- // Function to delete AI squad members upon player's death
- fnc_server_deletePlayerAISquad = {
- params ["_unitsToDelete"];
- if (!isServer) exitWith {};
- {
- // Ensure the unit object is not null and is a valid AI before deleting
- if (!isNull _x && !isPlayer _x) then {
- private _vehicle = vehicle _x;
- // If the AI is in a parachute, delete the parachute as well.
- if (_vehicle != _x && {_vehicle isKindOf "ParachuteBase"}) then {
- deleteVehicle _vehicle;
- };
- deleteVehicle _x;
- };
- } forEach _unitsToDelete;
- };
- ==================== END OF: player_server.sqf ====================
- ==================== START OF: strategy.sqf ====================
- // strategy.sqf
- // Strategic thinking and outcome prediction for High Command AI
- // Initialize strategy variables
- if (isNil "BLUFOR_STRATEGIC_PLAN") then { BLUFOR_STRATEGIC_PLAN = createHashMap; };
- if (isNil "OPFOR_STRATEGIC_PLAN") then { OPFOR_STRATEGIC_PLAN = createHashMap; };
- if (isNil "BLUFOR_HELD_POSITIONS") then { BLUFOR_HELD_POSITIONS = []; };
- if (isNil "OPFOR_HELD_POSITIONS") then { OPFOR_HELD_POSITIONS = []; };
- // Find high ground positions on the battlefield
- fnc_findHighGroundPositions = {
- params ["_side", "_centerPos", "_radius"];
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
- private _highGroundPositions = [];
- private _searchPositions = [];
- // Create search grid around center position
- for "_angle" from 0 to 315 step 45 do {
- for "_dist" from 300 to _radius step 200 do {
- private _testPos = _centerPos getPos [_dist, _angle];
- _searchPositions pushBack _testPos;
- };
- };
- // Evaluate each position for elevation
- {
- private _pos = _x;
- private _height = getTerrainHeightASL _pos;
- // Check surrounding area is lower (confirms it's high ground)
- private _surroundingHeights = [];
- for "_i" from 0 to 7 do {
- private _checkPos = _pos getPos [50, _i * 45];
- _surroundingHeights pushBack (getTerrainHeightASL _checkPos);
- };
- private _avgSurroundingHeight = 0;
- {_avgSurroundingHeight = _avgSurroundingHeight + _x;} forEach _surroundingHeights;
- _avgSurroundingHeight = _avgSurroundingHeight / count _surroundingHeights;
- // If this position is higher than surrounding area
- if (_height > (_avgSurroundingHeight + 10) && !surfaceIsWater _pos) then {
- private _highGroundData = createHashMap;
- _highGroundData set ["position", _pos];
- _highGroundData set ["elevation", _height];
- _highGroundData set ["elevationAdvantage", _height - _avgSurroundingHeight];
- _highGroundData set ["distanceToFront", _pos distance2D _centerPos];
- _highGroundData set ["distanceToEnemyBase", _pos distance2D _enemyBase];
- _highGroundData set ["distanceToOwnBase", _pos distance2D _ownBase];
- _highGroundPositions pushBack _highGroundData;
- };
- } forEach _searchPositions;
- // Sort by tactical value (elevation advantage + proximity to battle)
- _highGroundPositions = [_highGroundPositions, [], {
- private _elevScore = (_x get "elevationAdvantage") * 2;
- private _distScore = 1000 - ((_x get "distanceToFront") min 1000);
- _elevScore + _distScore
- }, "DESCEND"] call BIS_fnc_sortBy;
- // Return top 3 positions
- _highGroundPositions select [0, 3 min count _highGroundPositions]
- };
- // Find containment positions around enemy base to block reinforcements
- fnc_findContainmentPositions = {
- params ["_side", "_enemyBase"];
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- // Calculate the direction from enemy base to our base (their reinforcement route)
- private _reinforcementAxis = _enemyBase getDir _ownBase;
- private _containmentPositions = [];
- private _containmentRadius = 800; // 800m from enemy base
- // Create 5 containment positions in an arc facing our base (where enemies come from)
- // This blocks the enemy's path between their base and the battlefield
- for "_i" from 0 to 4 do {
- // Spread positions in a 120-degree arc centered on the reinforcement axis
- private _angleOffset = -60 + (_i * 30); // -60, -30, 0, 30, 60 degrees
- private _positionAngle = _reinforcementAxis + _angleOffset;
- private _testPos = _enemyBase getPos [_containmentRadius, _positionAngle];
- // Validate position
- if (!surfaceIsWater _testPos) then {
- private _height = getTerrainHeightASL _testPos;
- private _containmentData = createHashMap;
- _containmentData set ["position", _testPos];
- _containmentData set ["angle", _positionAngle];
- _containmentData set ["arcPosition", _i]; // 0=far left, 2=center, 4=far right
- _containmentData set ["elevation", _height];
- _containmentData set ["distanceToEnemyBase", _testPos distance2D _enemyBase];
- _containmentData set ["distanceToOwnBase", _testPos distance2D _ownBase];
- _containmentPositions pushBack _containmentData;
- };
- };
- _containmentPositions
- };
- // Find nearby friendly groups that could assist an objective
- fnc_findNearbyReinforcements = {
- params ["_side", "_objectivePos", "_excludeGroups", "_maxDistance"];
- private _allFriendlyGroups = allGroups select {
- side _x == _side &&
- count (units _x) > 0 &&
- ({isPlayer _x} count (units _x) == 0) &&
- !(_x in _excludeGroups)
- };
- private _nearbyGroups = [];
- {
- private _group = _x;
- private _leader = leader _group;
- if (!isNull _leader && alive _leader) then {
- private _distance = _leader distance2D _objectivePos;
- if (_distance <= _maxDistance) then {
- // Check if group is locked or busy
- private _isLocked = [_group] call fnc_isGroupLocked;
- if (!_isLocked) then {
- private _reinforcementData = createHashMap;
- _reinforcementData set ["group", _group];
- _reinforcementData set ["distance", _distance];
- _reinforcementData set ["type", [_group] call fnc_classifyGroup];
- _reinforcementData set ["strength", count (units _group select {alive _x})];
- _nearbyGroups pushBack _reinforcementData;
- };
- };
- };
- } forEach _allFriendlyGroups;
- // Sort by distance (closest first)
- _nearbyGroups = [_nearbyGroups, [], {_x get "distance"}, "ASCEND"] call BIS_fnc_sortBy;
- _nearbyGroups
- };
- // Calculate minimum defenders needed for base
- fnc_calculateMinimumDefenders = {
- params ["_side"];
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _enemySide = if (_side == west) then {east} else {west};
- // Count enemies near base
- private _nearbyEnemies = _ownBase nearEntities [["CAManBase", "LandVehicle", "Air"], 1000];
- private _enemyCount = {side _x == _enemySide && alive _x} count _nearbyEnemies;
- // Base minimum defenders
- private _minDefenders = 2;
- // Add defenders based on threat level
- if (_enemyCount > 20) then {
- _minDefenders = 6;
- } else {
- if (_enemyCount > 10) then {
- _minDefenders = 4;
- } else {
- if (_enemyCount > 5) then {
- _minDefenders = 3;
- };
- };
- };
- // Check time - more defenders at night
- if (sunOrMoon < 0.3) then {
- _minDefenders = _minDefenders + 1;
- };
- _minDefenders
- };
- // Check if base has adequate defenders
- fnc_checkBaseDefense = {
- params ["_side"];
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _minDefenders = [_side] call fnc_calculateMinimumDefenders;
- // Count current defenders
- private _currentDefenders = 0;
- {
- if (side _x == _side && count (units _x) > 0) then {
- private _role = _x getVariable ["HC_ROLE", ""];
- if (_role == "DEFENDER" || _role == "BASE_DEFENSE") then {
- _currentDefenders = _currentDefenders + 1;
- } else {
- // Also count groups very close to base as implicit defenders
- private _leader = leader _x;
- if (!isNull _leader && alive _leader && (_leader distance2D _ownBase) < 300) then {
- _currentDefenders = _currentDefenders + 1;
- };
- };
- };
- } forEach allGroups;
- private _defenseStatus = createHashMap;
- _defenseStatus set ["currentDefenders", _currentDefenders];
- _defenseStatus set ["minimumDefenders", _minDefenders];
- _defenseStatus set ["needsReinforcement", _currentDefenders < _minDefenders];
- _defenseStatus set ["defenderShortfall", (_minDefenders - _currentDefenders) max 0];
- _defenseStatus
- };
- // Create defensive perimeter for recon groups
- fnc_createReconPerimeter = {
- params ["_reconGroup", "_centerPos", "_radius"];
- // Create 4 waypoints in a diamond pattern around center
- while {count (waypoints _reconGroup) > 0} do {
- deleteWaypoint ((waypoints _reconGroup) select 0);
- };
- private _angles = [0, 90, 180, 270];
- {
- private _angle = _x;
- private _perimeterPos = _centerPos getPos [_radius, _angle];
- // Try to find cover at this position
- private _coverPos = [_perimeterPos, 0, 30, 3, 0, 0.7, 0] call BIS_fnc_findSafePos;
- if (count _coverPos == 0) then {
- _coverPos = _perimeterPos;
- };
- private _wp = _reconGroup addWaypoint [_coverPos, 10];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- _wp setWaypointCombatMode "YELLOW";
- _wp setWaypointCompletionRadius 15;
- if (_forEachIndex == (count _angles - 1)) then {
- _wp setWaypointStatements ["true", ""];
- };
- } forEach _angles;
- // Add cycle waypoint to patrol the perimeter
- if (count (waypoints _reconGroup) > 0) then {
- private _cycleWp = _reconGroup addWaypoint [waypointPosition [_reconGroup, 0], 0];
- _cycleWp setWaypointType "CYCLE";
- };
- _reconGroup setVariable ["HC_PERIMETER_CENTER", _centerPos, true];
- _reconGroup setVariable ["HC_PERIMETER_RADIUS", _radius, true];
- };
- // Evaluate potential objectives from intel
- fnc_identifyPotentialObjectives = {
- params ["_side"];
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- private _enemyBase = if (_side == west) then {getPos opforSpawnObj} else {getPos bluforSpawnObj};
- private _ownBase = if (_side == west) then {getPos bluforSpawnObj} else {getPos opforSpawnObj};
- private _objectives = [];
- private _enemyPositions = [];
- // Collect recent enemy positions from intel
- {
- private _report = _y;
- private _age = time - (_report get "time");
- if (_age < 120) then {
- _enemyPositions pushBack (_report get "position");
- };
- } forEach _intel;
- // Calculate battle center from enemy positions
- private _battleCenter = _enemyBase;
- if (count _enemyPositions > 0) then {
- private _avgX = 0;
- private _avgY = 0;
- {
- _avgX = _avgX + (_x select 0);
- _avgY = _avgY + (_x select 1);
- } forEach _enemyPositions;
- _battleCenter = [_avgX / count _enemyPositions, _avgY / count _enemyPositions, 0];
- };
- // PRIORITY 1: Base Defense (always consider this)
- private _defenseStatus = [_side] call fnc_checkBaseDefense;
- if (_defenseStatus get "needsReinforcement") then {
- private _defendObjective = createHashMap;
- _defendObjective set ["position", _ownBase];
- _defendObjective set ["type", "BASE_DEFENSE"];
- _defendObjective set ["enemyCount", 0];
- _defendObjective set ["priority", 100]; // Highest priority
- _defendObjective set ["requiredGroups", _defenseStatus get "defenderShortfall"];
- _objectives pushBack _defendObjective;
- };
- // PRIORITY 2: CONTAINMENT - Surround enemy base to block their reinforcements
- // This is the key strategic objective to win
- private _heldPositions = if (_side == west) then {BLUFOR_HELD_POSITIONS} else {OPFOR_HELD_POSITIONS};
- private _containmentPositions = [_side, _enemyBase] call fnc_findContainmentPositions;
- {
- private _containmentData = _x;
- private _containmentPos = _containmentData get "position";
- // Check if we already hold this containment position
- private _alreadyHeld = false;
- {
- if ((_x distance2D _containmentPos) < 150) then {
- _alreadyHeld = true;
- };
- } forEach _heldPositions;
- if (!_alreadyHeld) then {
- private _containmentObjective = createHashMap;
- _containmentObjective set ["position", _containmentPos];
- _containmentObjective set ["type", "CONTAINMENT"];
- _containmentObjective set ["enemyCount", 3]; // Assume moderate resistance
- _containmentObjective set ["priority", 85]; // Very high priority - key to victory
- _containmentObjective set ["arcPosition", _containmentData get "arcPosition"];
- _containmentObjective set ["requiresHolding", true]; // Must hold position
- _objectives pushBack _containmentObjective;
- };
- } forEach _containmentPositions;
- // PRIORITY 3: High Ground positions for snipers/elite
- private _highGroundPositions = [_side, _battleCenter, 2000] call fnc_findHighGroundPositions;
- {
- private _hgData = _x;
- private _hgPos = _hgData get "position";
- // Check if we already hold this position
- private _alreadyHeld = false;
- {
- if ((_x distance2D _hgPos) < 100) then {
- _alreadyHeld = true;
- };
- } forEach _heldPositions;
- if (!_alreadyHeld) then {
- private _hgObjective = createHashMap;
- _hgObjective set ["position", _hgPos];
- _hgObjective set ["type", "HIGH_GROUND"];
- _hgObjective set ["enemyCount", 2]; // Assume light resistance
- _hgObjective set ["priority", 70];
- _hgObjective set ["elevationAdvantage", _hgData get "elevationAdvantage"];
- _hgObjective set ["requiresSpecialized", true]; // Needs sniper/elite
- _objectives pushBack _hgObjective;
- };
- } forEach _highGroundPositions;
- // PRIORITY 4: Enemy concentrations from intel
- if (count _enemyPositions > 0) then {
- private _clusters = [_enemyPositions, 200] call fnc_clusterPositions;
- {
- private _cluster = _x;
- if (count _cluster >= 3) then {
- // Calculate cluster center
- private _avgX = 0;
- private _avgY = 0;
- {
- _avgX = _avgX + (_x select 0);
- _avgY = _avgY + (_x select 1);
- } forEach _cluster;
- private _centerPos = [
- _avgX / count _cluster,
- _avgY / count _cluster,
- 0
- ];
- // Determine if this is behind enemy lines
- private _distToEnemyBase = _centerPos distance2D _enemyBase;
- private _distToOwnBase = _centerPos distance2D _ownBase;
- private _isBehindEnemyLines = _distToEnemyBase < (_distToOwnBase * 0.5);
- private _objectiveType = if (_isBehindEnemyLines) then {
- "ENEMY_REAR"
- } else {
- "ENEMY_CONCENTRATION"
- };
- // Create objective for this enemy concentration
- private _objective = createHashMap;
- _objective set ["position", _centerPos];
- _objective set ["type", _objectiveType];
- _objective set ["enemyCount", count _cluster];
- _objective set ["priority", if (_isBehindEnemyLines) then {60} else {50}];
- if (_objectiveType == "ENEMY_REAR") then {
- _objective set ["requiresReconPerimeter", true];
- };
- _objectives pushBack _objective;
- };
- } forEach _clusters;
- };
- // PRIORITY 5: Enemy base assault (final objective after containment)
- private _baseObjective = createHashMap;
- _baseObjective set ["position", _enemyBase];
- _baseObjective set ["type", "ENEMY_BASE"];
- _baseObjective set ["enemyCount", 15]; // Assume base is well defended
- _baseObjective set ["priority", 40];
- _objectives pushBack _baseObjective;
- // PRIORITY 6: Defensive positions between own base and battle front
- if (count _enemyPositions > 0) then {
- private _dirToEnemy = _ownBase getDir _battleCenter;
- private _distToFront = _ownBase distance2D _battleCenter;
- // Create defensive line halfway to battle
- private _defensivePos = _ownBase getPos [_distToFront * 0.4, _dirToEnemy];
- private _defensiveObjective = createHashMap;
- _defensiveObjective set ["position", _defensivePos];
- _defensiveObjective set ["type", "DEFENSIVE_LINE"];
- _defensiveObjective set ["enemyCount", 0];
- _defensiveObjective set ["priority", 55];
- _objectives pushBack _defensiveObjective;
- };
- _objectives
- };
- // Predict outcome of sending forces to an objective
- fnc_predictBattleOutcome = {
- params ["_side", "_objective", "_assignedGroups", ["_reinforcements", []]];
- private _objectivePos = _objective get "position";
- private _enemyCount = _objective get "enemyCount";
- private _objectiveType = _objective get "type";
- // Calculate friendly force strength including reinforcements
- private _friendlyStrength = 0;
- private _friendlyUnits = 0;
- private _hasArmor = false;
- private _hasAir = false;
- private _hasSnipers = false;
- private _hasElite = false;
- private _allGroups = _assignedGroups + _reinforcements;
- {
- private _group = _x;
- private _groupType = [_group] call fnc_classifyGroup;
- private _unitCount = count (units _group select {alive _x});
- _friendlyUnits = _friendlyUnits + _unitCount;
- switch (_groupType) do {
- case "ARMOR": {
- _friendlyStrength = _friendlyStrength + (_unitCount * 4);
- _hasArmor = true;
- };
- case "MECHANIZED": {
- _friendlyStrength = _friendlyStrength + (_unitCount * 2);
- };
- case "AIR": {
- _friendlyStrength = _friendlyStrength + (_unitCount * 3);
- _hasAir = true;
- };
- case "ELITE": {
- _friendlyStrength = _friendlyStrength + (_unitCount * 1.8);
- _hasElite = true;
- };
- case "SPECOPS": {
- _friendlyStrength = _friendlyStrength + (_unitCount * 1.5);
- };
- case "SNIPER": {
- _friendlyStrength = _friendlyStrength + (_unitCount * 1.5);
- _hasSnipers = true;
- };
- default {
- _friendlyStrength = _friendlyStrength + _unitCount;
- };
- };
- } forEach _allGroups;
- // Estimate enemy strength at objective
- private _enemyStrength = _enemyCount * 1.2;
- // Adjust for objective type
- switch (_objectiveType) do {
- case "ENEMY_BASE": {
- _enemyStrength = _enemyStrength * 1.5; // Base defenders have advantage
- };
- case "HIGH_GROUND": {
- _enemyStrength = _enemyStrength * 0.8; // Usually lightly defended
- // Bonus if we have snipers for high ground
- if (_hasSnipers || _hasElite) then {
- _friendlyStrength = _friendlyStrength * 1.2;
- };
- };
- case "DEFENSIVE_LINE": {
- _enemyStrength = 0; // Setting up defense, no immediate enemy
- };
- case "BASE_DEFENSE": {
- _enemyStrength = _enemyCount * 1.3; // Attackers have initiative
- };
- case "ENEMY_REAR": {
- _enemyStrength = _enemyStrength * 1.1; // Slightly harder (surrounded)
- // SpecOps bonus for behind enemy lines
- if (_hasElite) then {
- _friendlyStrength = _friendlyStrength * 1.15;
- };
- };
- };
- // Calculate force ratio
- private _forceRatio = if (_enemyStrength > 0) then {
- _friendlyStrength / _enemyStrength
- } else {
- 999
- };
- // Predict success probability (0 to 1)
- private _successProbability = 0;
- if (_forceRatio >= 2.5) then {
- _successProbability = 0.95;
- } else {
- if (_forceRatio >= 2.0) then {
- _successProbability = 0.85;
- } else {
- if (_forceRatio >= 1.5) then {
- _successProbability = 0.7;
- } else {
- if (_forceRatio >= 1.0) then {
- _successProbability = 0.5;
- } else {
- if (_forceRatio >= 0.7) then {
- _successProbability = 0.3;
- } else {
- _successProbability = 0.1;
- };
- };
- };
- };
- };
- // Predict expected casualties (as percentage of friendly force)
- private _expectedCasualties = 0;
- if (_forceRatio >= 2.5) then {
- _expectedCasualties = 0.05;
- } else {
- if (_forceRatio >= 2.0) then {
- _expectedCasualties = 0.1;
- } else {
- if (_forceRatio >= 1.5) then {
- _expectedCasualties = 0.2;
- } else {
- if (_forceRatio >= 1.0) then {
- _expectedCasualties = 0.35;
- } else {
- if (_forceRatio >= 0.7) then {
- _expectedCasualties = 0.5;
- } else {
- _expectedCasualties = 0.7;
- };
- };
- };
- };
- };
- // Adjust for tactical advantages
- if (_hasArmor && !(_objectiveType in ["ENEMY_BASE", "HIGH_GROUND"])) then {
- _successProbability = _successProbability + 0.1;
- _expectedCasualties = _expectedCasualties * 0.8;
- };
- if (_hasAir) then {
- _successProbability = _successProbability + 0.05;
- _expectedCasualties = _expectedCasualties * 0.9;
- };
- // Reinforcement bonus
- if (count _reinforcements > 0) then {
- _successProbability = _successProbability + 0.1;
- _expectedCasualties = _expectedCasualties * 0.85;
- };
- // Clamp values
- _successProbability = (_successProbability min 0.95) max 0.05;
- _expectedCasualties = (_expectedCasualties min 0.9) max 0.02;
- // Return prediction
- private _prediction = createHashMap;
- _prediction set ["successProbability", _successProbability];
- _prediction set ["expectedCasualties", _expectedCasualties];
- _prediction set ["expectedCasualtyCount", round (_friendlyUnits * _expectedCasualties)];
- _prediction set ["forceRatio", _forceRatio];
- _prediction set ["friendlyStrength", _friendlyStrength];
- _prediction set ["enemyStrength", _enemyStrength];
- _prediction set ["hasReinforcements", count _reinforcements > 0];
- _prediction set ["reinforcementCount", count _reinforcements];
- _prediction
- };
- // Evaluate risk vs reward for an objective
- fnc_evaluateObjectiveValue = {
- params ["_side", "_objective", "_prediction"];
- private _objectiveType = _objective get "type";
- private _successProb = _prediction get "successProbability";
- private _expectedCasualties = _prediction get "expectedCasualties";
- private _hasReinforcements = _prediction get "hasReinforcements";
- // Base value by objective type
- private _baseValue = 0;
- switch (_objectiveType) do {
- case "ENEMY_BASE": { _baseValue = 100; };
- case "BASE_DEFENSE": { _baseValue = 95; }; // Critical
- case "HIGH_GROUND": { _baseValue = 70; }; // Very valuable
- case "DEFENSIVE_LINE": { _baseValue = 60; };
- case "ENEMY_REAR": { _baseValue = 55; }; // Disruptive
- case "ENEMY_CONCENTRATION": { _baseValue = 50; };
- default { _baseValue = 30; };
- };
- // Calculate expected value (probability of success * base value)
- private _expectedValue = _baseValue * _successProb;
- // Calculate risk cost (casualties are bad)
- private _riskCost = 50 * _expectedCasualties;
- // Net value = expected benefit - risk cost
- private _netValue = _expectedValue - _riskCost;
- // Urgency modifier
- private _urgency = 1.0;
- switch (_objectiveType) do {
- case "BASE_DEFENSE": {
- // Critical urgency for base defense
- private _enemiesNearBase = [_side, 800] call fnc_enemiesNearBase;
- if (_enemiesNearBase) then {
- _urgency = 5.0; // Extreme priority
- } else {
- _urgency = 3.0; // Still important
- };
- };
- case "HIGH_GROUND": {
- // Good to have, not critical
- _urgency = 1.2;
- };
- case "DEFENSIVE_LINE": {
- // More urgent if enemies are pushing
- private _intel = if (_side == west) then {BLUFOR_INTEL} else {OPFOR_INTEL};
- if (count _intel > 10) then {
- _urgency = 1.5;
- } else {
- _urgency = 1.0;
- };
- };
- case "ENEMY_REAR": {
- // Opportunistic
- _urgency = 1.3;
- };
- };
- // Reinforcement availability bonus
- if (_hasReinforcements) then {
- _netValue = _netValue * 1.15;
- };
- private _finalScore = _netValue * _urgency;
- // Return evaluation
- private _evaluation = createHashMap;
- _evaluation set ["score", _finalScore];
- _evaluation set ["baseValue", _baseValue];
- _evaluation set ["expectedValue", _expectedValue];
- _evaluation set ["riskCost", _riskCost];
- _evaluation set ["urgency", _urgency];
- _evaluation
- };
- // Select best groups for an objective type
- fnc_selectGroupsForObjective = {
- params ["_objective", "_availableGroups", "_count"];
- private _objectiveType = _objective get "type";
- private _objectivePos = _objective get "position";
- private _selectedGroups = [];
- // For specialized objectives, filter for appropriate group types
- switch (_objectiveType) do {
- case "CONTAINMENT": {
- // Prefer infantry and mechanized for holding positions
- // Avoid using all elite/specops for containment - save them for other tasks
- private _holdingGroups = _availableGroups select {
- private _type = [_x] call fnc_classifyGroup;
- _type in ["INFANTRY", "MECHANIZED", "UPGRADED", "SPECOPS"]
- };
- if (count _holdingGroups > 0) then {
- // Sort by distance to position
- _holdingGroups = [_holdingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups = _holdingGroups select [0, _count min count _holdingGroups];
- };
- // Fill remaining slots if needed
- if (count _selectedGroups < _count) then {
- private _remainingGroups = _availableGroups - _selectedGroups;
- private _needed = _count - count _selectedGroups;
- _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
- };
- };
- case "HIGH_GROUND": {
- // Prefer snipers and elite for high ground
- private _specializedGroups = _availableGroups select {
- private _type = [_x] call fnc_classifyGroup;
- _type in ["SNIPER", "ELITE", "SPECOPS"]
- };
- if (count _specializedGroups > 0) then {
- // Sort by distance to position
- _specializedGroups = [_specializedGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups = _specializedGroups select [0, _count min count _specializedGroups];
- };
- // Fill remaining slots with regular infantry if needed
- if (count _selectedGroups < _count) then {
- private _remainingGroups = _availableGroups - _selectedGroups;
- private _needed = _count - count _selectedGroups;
- _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
- };
- };
- case "ENEMY_REAR": {
- // Prefer elite/specops for behind enemy lines
- private _stealthGroups = _availableGroups select {
- private _type = [_x] call fnc_classifyGroup;
- _type in ["ELITE", "SPECOPS", "SNIPER"]
- };
- if (count _stealthGroups > 0) then {
- _stealthGroups = [_stealthGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups = _stealthGroups select [0, _count min count _stealthGroups];
- };
- if (count _selectedGroups < _count) then {
- private _remainingGroups = _availableGroups - _selectedGroups;
- private _needed = _count - count _selectedGroups;
- _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
- };
- };
- case "BASE_DEFENSE": {
- // Prefer groups already close to base
- private _sortedGroups = [_availableGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups = _sortedGroups select [0, _count min count _sortedGroups];
- };
- case "ENEMY_BASE": {
- // Prefer armor and mechanized for base assault
- private _heavyGroups = _availableGroups select {
- private _type = [_x] call fnc_classifyGroup;
- _type in ["ARMOR", "MECHANIZED", "AIR"]
- };
- if (count _heavyGroups > 0) then {
- _selectedGroups = _heavyGroups select [0, (_count / 2) min count _heavyGroups];
- };
- if (count _selectedGroups < _count) then {
- private _remainingGroups = _availableGroups - _selectedGroups;
- private _needed = _count - count _selectedGroups;
- _remainingGroups = [_remainingGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups append (_remainingGroups select [0, _needed min count _remainingGroups]);
- };
- };
- default {
- // Default: pick closest groups
- private _sortedGroups = [_availableGroups, [], {(leader _x) distance2D _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- _selectedGroups = _sortedGroups select [0, _count min count _sortedGroups];
- };
- };
- _selectedGroups
- };
- // Create strategic plan by evaluating all possible objectives
- fnc_createStrategicPlan = {
- params ["_side", "_availableGroups"];
- if (count _availableGroups == 0) exitWith { createHashMap };
- private _objectives = [_side] call fnc_identifyPotentialObjectives;
- private _scoredObjectives = [];
- // Evaluate each objective
- {
- private _objective = _x;
- private _objectivePos = _objective get "position";
- private _objectiveType = _objective get "type";
- // Handle fixed-requirement objectives (like base defense)
- if (_objectiveType == "BASE_DEFENSE") then {
- private _requiredGroups = _objective get "requiredGroups";
- private _selectedGroups = [_objective, _availableGroups, _requiredGroups] call fnc_selectGroupsForObjective;
- if (count _selectedGroups > 0) then {
- private _prediction = [_side, _objective, _selectedGroups] call fnc_predictBattleOutcome;
- private _evaluation = [_side, _objective, _prediction] call fnc_evaluateObjectiveValue;
- private _scoredObjective = createHashMap;
- _scoredObjective set ["objective", _objective];
- _scoredObjective set ["score", _evaluation get "score"];
- _scoredObjective set ["recommendedGroupCount", count _selectedGroups];
- _scoredObjective set ["selectedGroups", _selectedGroups];
- _scoredObjective set ["prediction", _prediction];
- _scoredObjective set ["evaluation", _evaluation];
- _scoredObjectives pushBack _scoredObjective;
- };
- } else {
- // For other objectives, test different force allocations
- private _minForce = 1;
- private _maxForce = (count _availableGroups) min 8; // Don't commit everything
- // For high ground, only need 1-2 groups
- if (_objectiveType == "HIGH_GROUND") then {
- _maxForce = 2;
- };
- private _bestScore = -999999;
- private _bestAllocation = _minForce;
- private _bestPrediction = createHashMap;
- private _bestEvaluation = createHashMap;
- private _bestGroups = [];
- for "_groupCount" from _minForce to _maxForce step 1 do {
- // Select appropriate groups for this objective
- private _testGroups = [_objective, _availableGroups, _groupCount] call fnc_selectGroupsForObjective;
- if (count _testGroups > 0) then {
- // Check for potential reinforcements
- private _reinforcements = [_side, _objectivePos, _testGroups, 1500] call fnc_findNearbyReinforcements;
- private _reinforcementGroups = [];
- // Take up to 2 nearest reinforcement groups
- if (count _reinforcements > 0) then {
- for "_i" from 0 to ((2 min count _reinforcements) - 1) do {
- _reinforcementGroups pushBack ((_reinforcements select _i) get "group");
- };
- };
- // Predict outcome with reinforcements
- private _prediction = [_side, _objective, _testGroups, _reinforcementGroups] call fnc_predictBattleOutcome;
- // Evaluate value
- private _evaluation = [_side, _objective, _prediction] call fnc_evaluateObjectiveValue;
- private _score = _evaluation get "score";
- // Efficiency penalty for using too many groups
- private _efficiencyPenalty = (_groupCount / count _availableGroups) * 8;
- _score = _score - _efficiencyPenalty;
- if (_score > _bestScore) then {
- _bestScore = _score;
- _bestAllocation = _groupCount;
- _bestPrediction = _prediction;
- _bestEvaluation = _evaluation;
- _bestGroups = _testGroups;
- };
- };
- };
- // Store objective with its best score and allocation
- if (count _bestGroups > 0) then {
- private _scoredObjective = createHashMap;
- _scoredObjective set ["objective", _objective];
- _scoredObjective set ["score", _bestScore];
- _scoredObjective set ["recommendedGroupCount", _bestAllocation];
- _scoredObjective set ["selectedGroups", _bestGroups];
- _scoredObjective set ["prediction", _bestPrediction];
- _scoredObjective set ["evaluation", _bestEvaluation];
- _scoredObjectives pushBack _scoredObjective;
- };
- };
- } forEach _objectives;
- // Sort objectives by score (highest first)
- _scoredObjectives = [_scoredObjectives, [], {_x get "score"}, "DESCEND"] call BIS_fnc_sortBy;
- // Create final strategic plan
- private _strategicPlan = createHashMap;
- _strategicPlan set ["objectives", _scoredObjectives];
- _strategicPlan set ["timestamp", time];
- _strategicPlan
- };
- // Get strategic recommendation for group assignment
- fnc_getStrategicRecommendation = {
- params ["_side", "_availableGroups"];
- if (count _availableGroups == 0) exitWith { createHashMap };
- // Create or update strategic plan
- private _strategicPlan = [_side, _availableGroups] call fnc_createStrategicPlan;
- // Store plan for this side
- if (_side == west) then {
- BLUFOR_STRATEGIC_PLAN = _strategicPlan;
- } else {
- OPFOR_STRATEGIC_PLAN = _strategicPlan;
- };
- private _objectives = _strategicPlan get "objectives";
- if (count _objectives == 0) exitWith { createHashMap };
- // Get top priority objective
- private _topObjective = _objectives select 0;
- private _score = _topObjective get "score";
- // Only recommend if score is positive
- if (_score <= 0) exitWith { createHashMap };
- private _objective = _topObjective get "objective";
- private _objectiveType = _objective get "type";
- // Special handling for high ground - track held positions
- if (_objectiveType == "HIGH_GROUND") then {
- private _heldPositions = if (_side == west) then {BLUFOR_HELD_POSITIONS} else {OPFOR_HELD_POSITIONS};
- _heldPositions pushBackUnique (_objective get "position");
- if (_side == west) then {
- BLUFOR_HELD_POSITIONS = _heldPositions;
- } else {
- OPFOR_HELD_POSITIONS = _heldPositions;
- };
- };
- private _recommendation = createHashMap;
- _recommendation set ["objective", _objective];
- _recommendation set ["groupCount", _topObjective get "recommendedGroupCount"];
- _recommendation set ["selectedGroups", _topObjective get "selectedGroups"];
- _recommendation set ["prediction", _topObjective get "prediction"];
- _recommendation set ["evaluation", _topObjective get "evaluation"];
- _recommendation set ["score", _score];
- _recommendation
- };
- // Apply special tactics for objective types
- fnc_applySpecialTactics = {
- params ["_group", "_objective"];
- private _objectiveType = _objective get "type";
- private _objectivePos = _objective get "position";
- switch (_objectiveType) do {
- case "CONTAINMENT": {
- // Lock group to containment position - critical for victory
- _group setVariable ["HC_FORCED_LOCK", true, true];
- _group setVariable ["HC_ROLE", "CONTAINMENT", true];
- _group setVariable ["HC_HOLD_POSITION", true, true];
- // Create defensive waypoints to hold the containment position
- [_group, _objectivePos] call fnc_createDefenseWaypoints;
- // Track this position as held by this side
- private _side = side _group;
- private _heldPositions = if (_side == west) then {BLUFOR_HELD_POSITIONS} else {OPFOR_HELD_POSITIONS};
- _heldPositions pushBackUnique _objectivePos;
- if (_side == west) then {
- BLUFOR_HELD_POSITIONS = _heldPositions;
- } else {
- OPFOR_HELD_POSITIONS = _heldPositions;
- };
- };
- case "HIGH_GROUND": {
- // Set group to hold position on high ground
- _group setVariable ["HC_HOLD_POSITION", true, true];
- _group setVariable ["HC_ROLE", "HIGH_GROUND", true];
- // Create defensive waypoints around the high ground
- [_group, _objectivePos] call fnc_createDefenseWaypoints;
- };
- case "ENEMY_REAR": {
- // Set up defensive perimeter for recon
- _group setVariable ["HC_ROLE", "DEEP_RECON", true];
- [_group, _objectivePos, 150] call fnc_createReconPerimeter;
- };
- case "BASE_DEFENSE": {
- // Lock group to base defense
- _group setVariable ["HC_FORCED_LOCK", true, true];
- _group setVariable ["HC_ROLE", "BASE_DEFENSE", true];
- [_group, _objectivePos] call fnc_createDefenseWaypoints;
- };
- case "DEFENSIVE_LINE": {
- // Create defensive positions
- _group setVariable ["HC_ROLE", "DEFENSIVE_LINE", true];
- [_group, _objectivePos] call fnc_createDefenseWaypoints;
- };
- };
- };
- ==================== END OF: strategy.sqf ====================
Add Comment
Please, Sign In to add comment