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 patrol and hunt in their assigned location
- fnc_setAAFHuntPatrol = {
- params ["_group", "_centerPos"];
- // Clear any existing waypoints from the group
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- private _patrolRadius = 500; // Patrol radius around the location center
- // Find a random safe position within the patrol radius
- private _patrolPos = [_centerPos, 50, _patrolRadius, 10, 0, 0.3, 0] call BIS_fnc_findSafePos;
- if (count _patrolPos == 0) then { _patrolPos = _centerPos; }; // Fallback to center if no safe pos found
- // Create a "Search and Destroy" waypoint
- private _wp = _group addWaypoint [_patrolPos, 0];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "COMBAT";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointCompletionRadius 100;
- // When the waypoint is completed, call this function again to get a new random patrol point
- // This makes the group patrol its assigned area indefinitely.
- _wp setWaypointStatements ["true", format["
- private _group = group this;
- if (!isNull _group && count (units _group) > 0) then {
- [_group, %1] call fnc_setAAFHuntPatrol;
- };
- ", _centerPos]];
- };
- // ==================== CHANGE START ====================
- // 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;
- };
- // Make the newly created group patrol the central city area.
- [_group, _centerPos] call fnc_setAAFHuntPatrol;
- _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;
- };
- // ===================== CHANGE END =====================
- 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;
- // If view is obstructed, reduce the AI's knowledge of the target.
- // This prevents them from shooting through dense bushes and trees.
- if (_obstructionLevel > 0.1) then {
- (group _unit) reveal [_target, 1.5]; // 1.5 = Aware, but not 100% sure, won't fire
- } else {
- // If view is clear, ensure the AI has full knowledge to engage.
- (group _unit) reveal [_target, 4]; // 4 = Knows for sure, will fire
- };
- };
- } 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_01_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;
- [_group, getPos opforSpawnObj] call fnc_createStealthPatrol;
- _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;
- [_group, getPos bluforSpawnObj] call fnc_createStealthPatrol;
- _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";
- };
- };
- // ==================== CHANGE START: DYNAMIC & REWORKED FOLIAGE DETECTION ====================
- // This function has been completely reworked for more accurate foliage detection.
- // It now uses a multi-point "shotgun" check with a dynamically scaled range based on server performance.
- fnc_checkFoliageObstruction = {
- params ["_unit", "_target"];
- // Use cached result if less than 2 seconds old to reduce performance impact.
- 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;
- // --- Phase 1: Basic Engine Checks (Fast) ---
- private _visibility = [_unit, "VIEW", _target] checkVisibility [_unitEyePos, eyePos _target];
- private _terrainBlocked = terrainIntersectASL [_unitEyePos, eyePos _target];
- // If the engine is certain the view is blocked by terrain, exit early.
- if (_terrainBlocked && _visibility < 0.1) exitWith {
- _unit setVariable ["foliageCache_" + _cacheKey, [1, time]];
- 1
- };
- // --- Phase 2: Multi-Point "Shotgun" Check (Accurate, but dynamically ranged) ---
- private _intersectionsCount = 0;
- // Define the maximum range for the expensive check based on server performance.
- private _maxCheckDistance = 150; // Default safe distance
- switch (PERF_STATE) do {
- case "EXCELLENT": { _maxCheckDistance = 1000; };
- case "GOOD": { _maxCheckDistance = 800; };
- case "MODERATE": { _maxCheckDistance = 400; };
- case "POOR": { _maxCheckDistance = 200; };
- // On CRITICAL/EMERGENCY, we stick to the 150m default.
- };
- // This expensive check only runs for targets within the dynamically determined range.
- if ((_unit distance2D _target) < _maxCheckDistance) then {
- // ==================== CHANGE START: Robust Type Validation ====================
- private _torsoPos = _target aimedAtTarget [_unit, "Torso"];
- // Validate the result. Check if it's NOT an array OR if it's the known failure position [0,0,0].
- if (typeName _torsoPos != "ARRAY" || {_torsoPos isEqualTo [0,0,0]}) then {
- _torsoPos = getPosATL _target; // Fallback to the unit's center position.
- };
- // We check three points on the target: Head, Torso, and Legs.
- private _targetPositions = [
- eyePos _target, // Head
- _torsoPos, // Torso
- getPosATL _target // Legs/Feet
- ];
- // ===================== CHANGE END =====================
- // Count how many of the three lines are blocked by any object surface.
- {
- if (count (lineIntersectsSurfaces [_unitEyePos, _x, _unit, _target, true, 1]) > 0) then {
- _intersectionsCount = _intersectionsCount + 1;
- };
- } forEach _targetPositions;
- };
- // --- Phase 3: Calculate Final Obstruction Score ---
- private _foliagePenalty = 0;
- // Add a significant penalty for each line that was blocked.
- // 1 blocked line = partial cover, 3 blocked lines = heavy cover.
- switch (_intersectionsCount) do {
- case 1: { _foliagePenalty = 0.3; }; // Partially obscured
- case 2: { _foliagePenalty = 0.6; }; // Mostly obscured
- case 3: { _foliagePenalty = 0.9; }; // Fully obscured by foliage
- };
- // Also check for smoke, which is a separate type of obstruction.
- private _smokeObstruction = 0;
- {
- if (_unit distance2D _x < (_unit distance2D _target)) then {
- _smokeObstruction = _smokeObstruction + 0.5;
- };
- } forEach (_unit nearObjects ["SmokeShell", 40]);
- // Combine all factors:
- // 1. Start with the inverse of the engine's visibility score.
- // 2. Add the heavy penalty from our multi-point foliage check.
- // 3. Add penalties for smoke and terrain.
- private _totalObstruction = (1 - _visibility) + _foliagePenalty + _smokeObstruction;
- if (_terrainBlocked) then { _totalObstruction = _totalObstruction + 0.5; };
- // Clamp the final value between 0 (clear) and 1 (fully obstructed).
- _totalObstruction = _totalObstruction min 1;
- // Cache the result before returning.
- _unit setVariable ["foliageCache_" + _cacheKey, [_totalObstruction, time]];
- _totalObstruction
- };
- // 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 avoid breaking formation
- private _group = group _unit;
- if (_unit != leader _group) exitWith {};
- private _lastVehicleEvadeCheck = _group getVariable ["lastVehicleEvadeCheck", 0];
- // Check every 5-8 seconds scaled by performance
- if (time - _lastVehicleEvadeCheck < (5 * PERF_COOLDOWN_MULTIPLIER)) exitWith {};
- _group setVariable ["lastVehicleEvadeCheck", time];
- // Check if group has AT capability
- private _hasAT = [_group] call fnc_groupHasAT;
- if (_hasAT) exitWith {}; // Group can handle vehicles, no need to evade
- private _enemySide = if (side _unit == west) then {east} else {west};
- private _unitPos = getPosATL _unit;
- // Search for enemy vehicles within 300m - scaled by performance
- private _searchRadius = 300 * PERF_SEARCH_RADIUS_MULTIPLIER;
- private _nearbyVehicles = _unitPos nearEntities [["Tank", "Wheeled_APC_F", "Tracked_APC_F"], _searchRadius];
- private _threatVehicles = _nearbyVehicles select {
- side _x == _enemySide &&
- alive _x &&
- (_x isKindOf "Tank" || _x isKindOf "Wheeled_APC_F" || _x isKindOf "Tracked_APC_F")
- };
- if (count _threatVehicles == 0) exitWith {};
- // Find closest threat
- private _closestVehicle = _threatVehicles select 0;
- private _closestDistance = _unit distance2D _closestVehicle;
- {
- private _dist = _unit distance2D _x;
- if (_dist < _closestDistance) then {
- _closestVehicle = _x;
- _closestDistance = _dist;
- };
- } forEach _threatVehicles;
- // Threat assessment based on distance
- private _shouldEvade = false;
- private _isUrgent = false;
- if (_closestDistance < 100) then {
- _shouldEvade = true;
- _isUrgent = true; // Very close, urgent evasion
- } else {
- if (_closestDistance < 200) then {
- // Check if vehicle is moving towards us
- private _vehicleVelocity = velocity _closestVehicle;
- private _vehicleSpeed = sqrt((_vehicleVelocity select 0)^2 + (_vehicleVelocity select 1)^2);
- if (_vehicleSpeed > 2) then {
- private _vehicleDir = direction _closestVehicle;
- private _dirToUnit = _closestVehicle getDir _unit;
- private _angleDiff = abs(_vehicleDir - _dirToUnit);
- // If vehicle heading towards us (within 45 degrees)
- if (_angleDiff < 45 || _angleDiff > 315) then {
- _shouldEvade = true;
- };
- };
- };
- };
- if (!_shouldEvade) exitWith {};
- // Check if already evading
- if (_group getVariable ["evadingVehicle", false]) exitWith {};
- // Mark group as evading
- _group setVariable ["evadingVehicle", true];
- _group setVariable ["evadeStartTime", time];
- // Find cover away from the vehicle
- private _dirAwayFromVehicle = _closestVehicle getDir _unit;
- private _coverSearchPos = _unitPos getPos [100, _dirAwayFromVehicle];
- // Try to find hard cover (buildings/walls)
- private _coverPos = [];
- private _nearBuildings = _coverSearchPos nearObjects ["House", 150];
- if (count _nearBuildings > 0) then {
- private _closestBuilding = _nearBuildings select 0;
- private _minDist = 9999;
- {
- private _dist = _coverSearchPos distance _x;
- if (_dist < _minDist) then {
- _minDist = _dist;
- _closestBuilding = _x;
- };
- } forEach _nearBuildings;
- _coverPos = getPosATL _closestBuilding;
- };
- // Fallback to terrain-based cover if no buildings
- if (count _coverPos == 0) then {
- _coverPos = [_unit, 150] call fnc_findNearestCover;
- };
- // Final fallback: just move away from vehicle
- if (count _coverPos == 0) then {
- _coverPos = _unitPos getPos [150, _dirAwayFromVehicle];
- };
- if (count _coverPos > 0 && !surfaceIsWater _coverPos) then {
- // Clear existing waypoints to prevent conflicts
- while {count (waypoints _group) > 0} do {
- deleteWaypoint ((waypoints _group) select 0);
- };
- // Order entire group to cover using waypoint system
- private _wp = _group addWaypoint [_coverPos, 20];
- _wp setWaypointType "MOVE";
- _wp setWaypointCompletionRadius 30;
- if (_isUrgent) then {
- _wp setWaypointSpeed "FULL";
- _wp setWaypointBehaviour "AWARE";
- {_x setUnitPos "DOWN";} forEach (units _group);
- } else {
- _wp setWaypointSpeed "FULL";
- _wp setWaypointBehaviour "AWARE";
- {_x setUnitPos "MIDDLE";} forEach (units _group);
- };
- // Spawn monitoring script
- [_group, _coverPos, _closestVehicle] spawn {
- params ["_evadingGroup", "_targetCover", "_threatVehicle"];
- private _timeout = time + 30; // 30 second timeout
- private _leader = leader _evadingGroup;
- while {!isNull _evadingGroup && count (units _evadingGroup) > 0 && time < _timeout} do {
- if (isNull _leader || !alive _leader) then {
- _leader = leader _evadingGroup;
- };
- // Check if reached cover or threat is gone
- if (!isNull _leader && (_leader distance2D _targetCover) < 40) exitWith {};
- if (!alive _threatVehicle || (!isNull _leader && (_leader distance2D _threatVehicle) > 350)) exitWith {};
- sleep 1;
- };
- // Clear evading flag and restore AUTO stance
- 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; // Scan up to 75m for hard cover.
- // Search for nearest buildings first, as they offer the best cover.
- private _nearBuildings = nearestObjects [_unitPos, ["House"], _maxScanRadius];
- if (count _nearBuildings > 0) then {
- // Find the closest building that is not the one the unit is already in.
- private _closestBuilding = objNull;
- private _minDist = 9999;
- {
- private _dist = _unit distance _x;
- if (_dist < _minDist && !(_unit nearObjects [_x, 1])) then {
- _minDist = _dist;
- _closestBuilding = _x;
- };
- } forEach _nearBuildings;
- if (!isNull _closestBuilding) then {
- // Find the nearest position inside the building.
- _bestCoverPos = _closestBuilding buildingPos -1;
- };
- };
- // If no building was found, look for nearby 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;
- // Position the AI on the side of the wall facing away from the known threat or just behind it.
- _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: 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 15-20 seconds per group
- if (time < (_group getVariable ["lastATScavengeCheck", 0]) + (15 + 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", "Armored"], 300];
- private _targetVehicle = objNull;
- {
- if (side _x == _enemySide && alive _x) exitWith {
- _targetVehicle = _x;
- };
- } forEach _threats;
- // If no vehicle threat, exit.
- if (isNull _targetVehicle) exitWith {};
- // 2. Check for living AT soldiers in the group. If any exist, they should handle it.
- private _livingUnits = units _group select {alive _x};
- private _hasLivingAT = false;
- {
- private _unitWeaponry = secondaryWeaponItems _x;
- if (count _unitWeaponry > 0) exitWith { _hasLivingAT = true; };
- } forEach _livingUnits;
- if (_hasLivingAT) exitWith {};
- // 3. Find a dead AT soldier in the group who has a launcher
- private _deadBody = objNull;
- private _launcherClass = "";
- private _launcherMags = [];
- {
- if (!alive _x) then {
- private _bodyWeaponry = secondaryWeaponItems _x;
- if (count _bodyWeaponry > 0) then {
- _deadBody = _x;
- _launcherClass = _bodyWeaponry # 0; // Get the launcher classname
- _launcherMags = magazines _deadBody select {
- _x in (getArray (configFile >> "CfgWeapons" >> _launcherClass >> "magazines"))
- };
- if (count _launcherMags > 0) then { break; }; // Found a body with a launcher and ammo
- };
- };
- } forEach (units _group);
- // If no suitable body with a launcher and ammo was found, exit.
- if (isNull _deadBody || _launcherClass == "" || count _launcherMags == 0) exitWith {};
- // 4. Find the closest living non-AT soldier to the body to be the "scavenger"
- private _scavenger = objNull;
- private _minDist = 9999;
- {
- if (count (secondaryWeaponItems _x) == 0) then { // Ensure they don't already have an AT weapon
- 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]; // Flag the group
- _scavenger setVariable ["isScavenger", true, true]; // Flag the unit
- // Spawn the process to handle the movement and weapon swap
- [_scavenger, _deadBody, _launcherClass, _launcherMags, _targetVehicle] spawn {
- params ["_scavenger", "_deadBody", "_launcherClass", "_launcherMags", "_targetVehicle"];
- // Order the unit to move to the body
- _scavenger doMove (getPos _deadBody);
- // Wait until the unit is close, dies, or the body is gone
- waitUntil {sleep 1; !alive _scavenger || isNull _deadBody || _scavenger distance2D _deadBody < 4};
- if (alive _scavenger && !isNull _deadBody) then {
- // Transfer weapon and magazines
- _scavenger addWeapon _launcherClass;
- { _scavenger addMagazine _x; } forEach _launcherMags;
- // Immediately command the unit to engage the threat
- _scavenger selectWeapon (secondaryWeapon _scavenger);
- _scavenger doTarget _targetVehicle;
- _scavenger doFire _targetVehicle;
- };
- // Clean up flags
- (group _scavenger) setVariable ["isScavengingAT", false, true];
- _scavenger setVariable ["isScavenger", 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 {
- 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: 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: 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;
- };
- // REWORKED: No more PERF_SKIP checks. Functions will just run less often or with smaller ranges.
- // 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]);
- };
- };
- };
- // ==================== CHANGE START ====================
- if (secondaryWeapon _unit != "") then {
- private _lastATCheck = _unit getVariable ["lastATCheck", 0];
- if (time > _lastATCheck + (8 * PERF_COOLDOWN_MULTIPLIER)) then {
- _unit setVariable ["lastATCheck", time];
- private _nearVehicles = _unit nearEntities [["LandVehicle", "Air"], (400 * PERF_SEARCH_RADIUS_MULTIPLIER)];
- private _enemyVehicle = _nearVehicles findIf {
- side _x == _enemySide && alive _x && _unit knowsAbout _x > 0
- };
- if (_enemyVehicle != -1) then {
- private _targetVehicle = _nearVehicles select _enemyVehicle;
- // Relaxed foliage check for AT units
- private _obstructionLevel = [_unit, _targetVehicle] call fnc_checkFoliageObstruction;
- // If the vehicle is less than 85% obscured, the AI is cleared to fire.
- if (_obstructionLevel < 0.85) then {
- (group _unit) reveal [_targetVehicle, 4]; // Force reveal to allow firing
- _unit selectWeapon (secondaryWeapon _unit);
- _unit doTarget _targetVehicle;
- _unit doFire _targetVehicle;
- } else {
- // Still partially reveal so the AI tracks the target, but won't waste a shot
- (group _unit) reveal [_targetVehicle, 1.5];
- };
- };
- };
- };
- // ===================== CHANGE 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 {
- (group _unit) reveal [_target, 1.5];
- _unit doWatch objNull;
- } else {
- if (_obstructionLevel > 0.15) then {
- if (_isBeingShotAt) then {
- (group _unit) reveal [_target, 4];
- } else {
- (group _unit) reveal [_target, 1.5];
- _unit doWatch (getPos _target);
- };
- } else {
- (group _unit) reveal [_target, 4];
- };
- };
- };
- };
- };
- 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];
- _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;
- };
- };
- };
- // 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;
- // Only apply spacing for the first 10 minutes (600s) of the mission.
- // This encourages spreading out for initial recon. After that, groups will be more focused.
- if (time < 600) then {
- // Assign a random offset within a 250m radius of the main target.
- _targetPos = _centerPos getPos [50 + random 200, random 360];
- };
- // 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
- };
- // 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;
- switch (_role) do {
- case "ATTACK": {
- // Direct assault on battle center
- private _attackPos = [_battleCenter, "FORWARD", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- [_group, _attackPos, _groupType] call fnc_createTacticalPath;
- _group setVariable ["HC_ROLE", "ATTACKER", true];
- };
- case "DEFEND": {
- // Defend own base
- private _defendPos = [_battleCenter, "DEFENSIVE", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- [_group, _defendPos] call fnc_createDefenseWaypoints;
- _group setVariable ["HC_ROLE", "DEFENDER", true];
- };
- case "FLANK_LEFT": {
- // Flank from the left
- private _flankPos = [_battleCenter, "FLANK_LEFT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- [_group, "FLANK_LEFT", _flankPos, _groupType] call fnc_createAdvancedWaypoints;
- _group setVariable ["HC_ROLE", "FLANKER", true];
- };
- case "FLANK_RIGHT": {
- // Flank from the right
- private _flankPos = [_battleCenter, "FLANK_RIGHT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- [_group, "FLANK_RIGHT", _flankPos, _groupType] call fnc_createAdvancedWaypoints;
- _group setVariable ["HC_ROLE", "FLANKER", true];
- };
- case "RESERVE": {
- // Hold position between base and battle
- private _reservePos = [_battleCenter, "SUPPORT", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- [_group, _reservePos] call fnc_createDefenseWaypoints;
- _group setVariable ["HC_ROLE", "RESERVE", true];
- };
- case "SUPPORT": {
- // Air or long-range support
- if (_groupType == "AIR") then {
- [_group, _battleCenter] call fnc_createHelicopterAttackWaypoints;
- } else {
- private _supportPos = [_battleCenter, "HIGH_GROUND", _ownBase, _enemyBase] call fnc_findTacticalPosition;
- [_group, "OVERWATCH", _supportPos, _groupType] call fnc_createAdvancedWaypoints;
- };
- _group setVariable ["HC_ROLE", "SUPPORT", true];
- };
- };
- [_group, _role, _battleCenter] 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"]];
- // Validate targetPos is a proper position array
- 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];
- switch (_orderType) do {
- case "ATTACK": {
- [_group, _targetPos, _groupType] call fnc_createTacticalPath;
- };
- case "DEFEND": {
- [_group, _targetPos] call fnc_createDefenseWaypoints;
- };
- 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";
- };
- 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;
- };
- };
- };
- };
- // Combine stance and movement
- private _finalDetectability = _stanceMultiplier * _movementMultiplier;
- // Clamp between 0.15 and 1.3
- _finalDetectability = (_finalDetectability max 0.15) 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; };
- // Get spotter's skill modifier
- private _spotterSkill = [_unit] call fnc_calculateSpotterSkill;
- // 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;
- // Check basic visibility first
- private _visibility = [_unit, "VIEW", _enemy] checkVisibility [eyePos _unit, eyePos _enemy];
- if (_visibility > 0.1) then {
- // Calculate target detectability based on movement and stance
- private _targetDetectability = [_enemy] call fnc_calculateTargetDetectability;
- // Combine visibility, spotter skill, and target detectability
- private _detectionChance = _visibility * _spotterSkill * _targetDetectability;
- // Random roll to see if detection succeeds
- private _randomRoll = random 1;
- if (_randomRoll < _detectionChance) 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 ["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;
- };
- // 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 _directionVector = _targetPos vectorDiff _startPos;
- private _isVehicleGroup = vehicle _leader != _leader;
- if (_distance < 500) then {
- private _wp = _group addWaypoint [_targetPos, 20];
- _wp setWaypointType "SAD";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "FULL";
- _wp setWaypointCombatMode "RED";
- _wp setWaypointCompletionRadius 100;
- } else {
- private _segmentLength = 350;
- private _numWaypoints = floor (_distance / _segmentLength);
- _numWaypoints = ((_numWaypoints max 2) min 8);
- for "_i" from 1 to _numWaypoints do {
- private _multiplier = _i / (_numWaypoints + 1);
- private _scaledVector = _directionVector vectorMultiply _multiplier;
- private _directLinePos = _startPos vectorAdd _scaledVector;
- private _searchRadius = 300;
- private _tacticalPos = [];
- if (_isVehicleGroup) then {
- _tacticalPos = [_directLinePos, 50, _searchRadius, 10, 2, 0, 0] call BIS_fnc_findSafePos;
- } else {
- _tacticalPos = [_directLinePos, 50, _searchRadius, 10, 0, 0.5, 0] call BIS_fnc_findSafePos;
- };
- // MODIFIED: Only add the waypoint if a safe, non-water position was found.
- if (count _tacticalPos > 0) then {
- private _wp = _group addWaypoint [_tacticalPos, 50];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- _wp setWaypointFormation "LINE";
- _wp setWaypointCombatMode "YELLOW";
- _wp setWaypointCompletionRadius 75;
- };
- };
- private _finalWp = _group addWaypoint [_targetPos, 20];
- _finalWp setWaypointType "SAD";
- _finalWp setWaypointBehaviour "AWARE";
- _finalWp setWaypointSpeed "FULL";
- _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);
- };
- switch (_orderType) do {
- case "ASSAULT": {
- [_group, _targetPos, _groupType] call fnc_createTacticalPath;
- };
- case "FLANK_LEFT": {
- private _angle = [position leader _group, _targetPos] call BIS_fnc_dirTo;
- private _flankAngle = _angle - 90 + (random 40 - 20);
- private _flankDist1 = 400 + (random 400);
- private _flankDist2 = 200 + (random 300);
- private _flankPos1 = _targetPos getPos [_flankDist1, _flankAngle];
- private _flankPos2 = _targetPos getPos [_flankDist2, _flankAngle - 45];
- private _wp1 = _group addWaypoint [_flankPos1, 50];
- _wp1 setWaypointType "MOVE";
- _wp1 setWaypointBehaviour "AWARE";
- _wp1 setWaypointSpeed "FULL";
- private _wp2 = _group addWaypoint [_flankPos2, 30];
- _wp2 setWaypointType "MOVE";
- _wp2 setWaypointBehaviour "COMBAT";
- private _wp3 = _group addWaypoint [_targetPos, 50];
- _wp3 setWaypointType "SAD";
- _wp3 setWaypointBehaviour "COMBAT";
- };
- case "FLANK_RIGHT": {
- private _angle = [position leader _group, _targetPos] call BIS_fnc_dirTo;
- private _flankAngle = _angle + 90 + (random 40 - 20);
- private _flankDist1 = 400 + (random 400);
- private _flankDist2 = 200 + (random 300);
- private _flankPos1 = _targetPos getPos [_flankDist1, _flankAngle];
- private _flankPos2 = _targetPos getPos [_flankDist2, _flankAngle + 45];
- private _wp1 = _group addWaypoint [_flankPos1, 50];
- _wp1 setWaypointType "MOVE";
- _wp1 setWaypointBehaviour "AWARE";
- _wp1 setWaypointSpeed "FULL";
- private _wp2 = _group addWaypoint [_flankPos2, 30];
- _wp2 setWaypointType "MOVE";
- _wp2 setWaypointBehaviour "COMBAT";
- private _wp3 = _group addWaypoint [_targetPos, 50];
- _wp3 setWaypointType "SAD";
- _wp3 setWaypointBehaviour "COMBAT";
- };
- case "OVERWATCH": {
- private _overwatchPos = [_targetPos, 300, 600, 10, 0, 2, 0] call BIS_fnc_findSafePos;
- if (count _overwatchPos == 0) then {
- _overwatchPos = _targetPos getPos [400, random 360];
- };
- private _wp = _group addWaypoint [_overwatchPos, 10];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "AWARE";
- _wp setWaypointSpeed "NORMAL";
- private _patrolWp = _group addWaypoint [_overwatchPos, 30];
- _patrolWp setWaypointType "SAD";
- _patrolWp setWaypointBehaviour "AWARE";
- _patrolWp setWaypointSpeed "LIMITED";
- _patrolWp setWaypointCompletionRadius 150;
- };
- case "DEEP_FLANK": {
- private _angleBack = [_targetPos, position leader _group] call BIS_fnc_dirTo;
- private _flankPos = _targetPos getPos [600, _angleBack + (random 60 - 30)];
- private _wp1 = _group addWaypoint [_flankPos, 60];
- _wp1 setWaypointType "MOVE";
- _wp1 setWaypointBehaviour "STEALTH";
- _wp1 setWaypointSpeed "LIMITED";
- 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];
- private _wp = _group addWaypoint [_reconPos, 30];
- _wp setWaypointType "MOVE";
- _wp setWaypointBehaviour "STEALTH";
- _wp setWaypointSpeed "LIMITED";
- };
- 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
- };
- // 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};
- private _enemySide = if (_side == west) then {east} else {west};
- private _friendlyState = [_side] call HC_fnc_getSideState;
- private _availableGroups = _friendlyState get "groups";
- if (count _availableGroups == 0) exitWith {};
- // Emergency base defense takes priority over strategy system
- 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 {};
- // Use strategy system if enabled
- if (STRATEGY_ENABLED) then {
- private _strategicRecommendation = [_side, _availableGroups] 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";
- // Only execute if score is good and casualties are acceptable
- if (_score >= STRATEGY_MIN_SCORE && _expectedCasualties <= STRATEGY_CASUALTY_TOLERANCE) then {
- private _objectivePos = _objective get "position";
- private _objectiveType = _objective get "type";
- // Sort groups by distance to objective
- private _sortedGroups = [_availableGroups, [], {(leader _x) distance _objectivePos}, "ASCEND"] call BIS_fnc_sortBy;
- // Select recommended number of groups
- private _groupsToAssign = _sortedGroups select [0, _recommendedGroupCount min count _sortedGroups];
- // Create battle plan for these groups
- private _battlePlan = [_side, _groupsToAssign, _objectivePos, _ownBase] call fnc_createBattlePlan;
- private _battleCenter = _battlePlan get "battleCenter";
- // Execute battle plan roles
- 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";
- // ==================== CHANGE START ====================
- // Use the new spacing function to assign roles
- [_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;
- // ===================== CHANGE END =====================
- // Update strategy tracking
- if (_side == west) then {
- BLUFOR_STRATEGY = _objectiveType;
- } else {
- OPFOR_STRATEGY = _objectiveType;
- };
- // Exit - strategy system handled decision
- } else {
- // Strategy says it's not worth it - use fallback defensive behavior
- private _battleCenter = [_side] call fnc_getBattleHotspot;
- {
- [_x, _battleCenter, "PATROL"] call fnc_assignGroupObjective;
- } forEach _availableGroups;
- };
- } else {
- // No strategic recommendation - use old system
- private _battlePlan = [_side, _availableGroups, _enemyBase, _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";
- // ==================== CHANGE START ====================
- // Use the new spacing function to assign roles
- [_attackers, "ATTACK", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_defenders, "DEFEND", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_reserve, "RESERVE", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_support, "SUPPORT", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- // ===================== CHANGE END =====================
- if (_side == west) then {
- BLUFOR_STRATEGY = "RTS_COMBINED_ARMS";
- } else {
- OPFOR_STRATEGY = "RTS_COMBINED_ARMS";
- };
- };
- } else {
- // Strategy system disabled - use original behavior
- private _battlePlan = [_side, _availableGroups, _enemyBase, _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";
- // ==================== CHANGE START ====================
- // Use the new spacing function to assign roles
- [_attackers, "ATTACK", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_defenders, "DEFEND", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_leftFlank, "FLANK_LEFT", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_rightFlank, "FLANK_RIGHT", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_reserve, "RESERVE", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- [_support, "SUPPORT", _battleCenter, _ownBase, _enemyBase] call fnc_HC_assignGroupWithSpacing;
- // ===================== CHANGE END =====================
- if (_side == west) then {
- BLUFOR_STRATEGY = "RTS_COMBINED_ARMS";
- } else {
- OPFOR_STRATEGY = "RTS_COMBINED_ARMS";
- };
- };
- };
- // 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 _enemyPositions = [];
- { _enemyPositions pushBack (_x select 2); } forEach _recentReports;
- private _bestTarget = [0,0,0];
- private _bestScore = 0;
- private _enemyBaseMarker = if (_side == west) then {"opfor_base_area"} else {"blufor_base_area"};
- private _enemyBasePos = markerPos _enemyBaseMarker;
- private _enemyBaseSize = (markerSize _enemyBaseMarker) select 0;
- {
- private _pos = _x;
- if (_pos distance2D _enemyBasePos >= _enemyBaseSize) then {
- private _nearbyFriendlies = _pos nearEntities [["CAManBase", "LandVehicle"], 30];
- private _friendliesInArea = {side _x == _side && alive _x} count _nearbyFriendlies;
- if (_friendliesInArea == 0) then {
- private _clusterScore = 1 + ({_x distance _pos < 100 && !(_x isEqualTo _pos)} count _enemyPositions);
- if (_clusterScore > _bestScore) then {
- _bestScore = _clusterScore;
- _bestTarget = _pos;
- };
- };
- };
- } forEach _enemyPositions;
- _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 3;
- [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.sqm ====================
- version=54;
- class EditorData
- {
- moveGridStep=1;
- angleGridStep=0.2617994;
- scaleGridStep=1;
- autoGroupingDist=10;
- toggles=1;
- class ItemIDProvider
- {
- nextID=728;
- };
- class Camera
- {
- pos[]={8469.1787,96.641701,25282.076};
- dir[]={0.46490091,-0.29877436,-0.83342725};
- up[]={0.14554858,0.95432377,-0.26092476};
- aside[]={-0.87331706,0,-0.48715216};
- };
- };
- binarizationWanted=0;
- sourceName="altis-test";
- addons[]=
- {
- "A3_Ui_F",
- "A3_Structures_F_EPC_Civ_Camping",
- "A3_Characters_F"
- };
- class AddonsMetaData
- {
- class List
- {
- items=3;
- class Item0
- {
- className="A3_Ui_F";
- name="Arma 3 - User Interface";
- author="Bohemia Interactive";
- url="https://www.arma3.com";
- };
- class Item1
- {
- className="A3_Structures_F_EPC";
- name="Arma 3 Win Episode - Buildings and Structures";
- author="Bohemia Interactive";
- url="https://www.arma3.com";
- };
- class Item2
- {
- className="A3_Characters_F";
- name="Arma 3 Alpha - Characters and Clothing";
- author="Bohemia Interactive";
- url="https://www.arma3.com";
- };
- };
- };
- randomSeed=14934283;
- class ScenarioData
- {
- author="chinese communist death vans";
- };
- class Mission
- {
- class Intel
- {
- resistanceWest=0;
- timeOfChanges=1800.0002;
- startWeather=0.19925441;
- startWind=0.1;
- startWaves=0.1;
- forecastWeather=0.25008479;
- forecastWind=0.1;
- forecastWaves=0.1;
- forecastLightnings=0.1;
- year=2035;
- month=10;
- day=16;
- hour=11;
- minute=13;
- startFogDecay=0.014;
- forecastFogDecay=0.014;
- };
- class Entities
- {
- items=59;
- class Item0
- {
- dataType="Marker";
- position[]={12283.546,42.890999,18734.664};
- name="basemarker1";
- type="Empty";
- angle=354.22101;
- id=201;
- atlOffset=-0.00024414063;
- };
- class Item1
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={14475.708,32.810844,20277.342};
- };
- side="Empty";
- class Attributes
- {
- name="custom_capture_21";
- };
- id=250;
- type="Land_Sunshade_01_F";
- atlOffset=0.33879089;
- };
- class Item2
- {
- dataType="Marker";
- position[]={14259.634,76.668999,22040.857};
- name="basemarker2";
- type="Empty";
- id=251;
- atlOffset=-0.0001373291;
- };
- class Item3
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={13974.829,50.862137,20782.016};
- angles[]={6.2551923,0,0.0026744273};
- };
- side="Empty";
- flags=4;
- class Attributes
- {
- name="custom_capture_22";
- };
- id=300;
- type="Land_Sunshade_01_F";
- };
- class Item4
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={14653.132,48.692924,20776.793};
- angles[]={6.1716504,0,6.2818484};
- };
- side="Empty";
- flags=4;
- class Attributes
- {
- name="custom_capture_23";
- };
- id=301;
- type="Land_Sunshade_01_F";
- };
- class Item5
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={14190.832,78.276566,21228.215};
- angles[]={6.211309,0,6.0313168};
- };
- side="Empty";
- flags=4;
- class Attributes
- {
- name="custom_capture_24";
- };
- id=302;
- type="Land_Sunshade_01_F";
- };
- class Item6
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={13332.5,37.353886,20158.957};
- angles[]={6.2618537,0,6.2645183};
- };
- side="Empty";
- flags=4;
- class Attributes
- {
- name="custom_capture_25";
- };
- id=303;
- type="Land_Sunshade_01_F";
- atlOffset=3.8146973e-06;
- };
- class Item7
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={12826.544,45.03352,19649.203};
- angles[]={6.2325621,0,0.0093350215};
- };
- side="Empty";
- flags=4;
- class Attributes
- {
- name="custom_capture_27";
- };
- id=305;
- type="Land_Sunshade_01_F";
- atlOffset=3.8146973e-06;
- };
- class Item8
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={14033.105,28.411243,18715.609};
- angles[]={0.027993103,0,0.014664836};
- };
- side="Empty";
- flags=4;
- class Attributes
- {
- name="custom_capture_28";
- };
- id=306;
- type="Land_Sunshade_01_F";
- atlOffset=3.8146973e-06;
- };
- class Item9
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8487.0635,88.450882,25255.645};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- isPlayer=1;
- };
- id=412;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male04GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=1.05;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=411;
- atlOffset=-7.6293945e-06;
- };
- class Item10
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8493.6631,87.965904,25258.918};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=416;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=415;
- atlOffset=-7.6293945e-06;
- };
- class Item11
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8493.4658,87.665306,25261.18};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=531;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=530;
- atlOffset=-7.6293945e-06;
- };
- class Item12
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8491.8467,88.023788,25259.121};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=533;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=532;
- atlOffset=-7.6293945e-06;
- };
- class Item13
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8491.5908,87.778473,25261.01};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=535;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=534;
- atlOffset=-7.6293945e-06;
- };
- class Item14
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8492.9512,87.519699,25262.789};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=537;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=536;
- atlOffset=-7.6293945e-06;
- };
- class Item15
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8491.293,87.591988,25262.898};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=539;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=538;
- atlOffset=-7.6293945e-06;
- };
- class Item16
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8492.7686,87.380341,25264.182};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=541;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=540;
- atlOffset=-7.6293945e-06;
- };
- class Item17
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8490.9512,87.428673,25264.592};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=543;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=542;
- atlOffset=-7.6293945e-06;
- };
- class Item18
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8492.5508,87.158485,25265.615};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=545;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=544;
- atlOffset=-7.6293945e-06;
- };
- class Item19
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8491.0137,87.284843,25265.91};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=547;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=546;
- atlOffset=-7.6293945e-06;
- };
- class Item20
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8487.9951,87.905685,25261.072};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=549;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=548;
- atlOffset=-7.6293945e-06;
- };
- class Item21
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8487.7979,87.602234,25263.334};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=551;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=550;
- atlOffset=-7.6293945e-06;
- };
- class Item22
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8486.1787,87.977829,25261.275};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=553;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=552;
- atlOffset=-7.6293945e-06;
- };
- class Item23
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8485.9229,87.673195,25263.164};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=555;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=554;
- atlOffset=-7.6293945e-06;
- };
- class Item24
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8487.2832,87.428505,25264.943};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=557;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=556;
- atlOffset=-7.6293945e-06;
- };
- class Item25
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8485.625,87.410339,25265.053};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=559;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=558;
- atlOffset=-7.6293945e-06;
- };
- class Item26
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8487.1006,87.279335,25266.336};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=561;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=560;
- atlOffset=-7.6293945e-06;
- };
- class Item27
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8485.2832,87.22831,25266.746};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=563;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=562;
- atlOffset=-7.6293945e-06;
- };
- class Item28
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8486.8828,87.122017,25267.77};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=565;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=564;
- atlOffset=-7.6293945e-06;
- };
- class Item29
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8485.3457,87.079704,25268.064};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=567;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=566;
- atlOffset=-7.6293945e-06;
- };
- class Item30
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8483.2041,88.020248,25262.057};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=569;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=568;
- atlOffset=-7.6293945e-06;
- };
- class Item31
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8483.0068,87.648933,25264.318};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=571;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=570;
- atlOffset=-7.6293945e-06;
- };
- class Item32
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8481.3877,88.06424,25262.26};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=573;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=572;
- atlOffset=-7.6293945e-06;
- };
- class Item33
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8481.1318,87.752808,25264.148};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=575;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=574;
- atlOffset=-7.6293945e-06;
- };
- class Item34
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8482.4922,87.406387,25265.928};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=577;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=576;
- atlOffset=-7.6293945e-06;
- };
- class Item35
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8480.834,87.443054,25266.037};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=579;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=578;
- atlOffset=-7.6293945e-06;
- };
- class Item36
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8482.3096,87.176537,25267.32};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=581;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=580;
- atlOffset=-7.6293945e-06;
- };
- class Item37
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8480.4922,87.173218,25267.73};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=583;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=582;
- atlOffset=-7.6293945e-06;
- };
- class Item38
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8482.0918,86.990707,25268.754};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=585;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=584;
- atlOffset=-7.6293945e-06;
- };
- class Item39
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8480.5547,86.988304,25269.049};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=587;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=586;
- atlOffset=-7.6293945e-06;
- };
- class Item40
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8478.3203,88.260071,25261.908};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=589;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=588;
- atlOffset=-7.6293945e-06;
- };
- class Item41
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8478.123,87.853439,25264.17};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=591;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=590;
- atlOffset=-7.6293945e-06;
- };
- class Item42
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8476.5039,88.309868,25262.111};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=593;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=592;
- atlOffset=-7.6293945e-06;
- };
- class Item43
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8476.248,87.974426,25264};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=595;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=594;
- atlOffset=-7.6293945e-06;
- };
- class Item44
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8477.6084,87.594284,25265.779};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=597;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=596;
- atlOffset=-7.6293945e-06;
- };
- class Item45
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8475.9502,87.641396,25265.889};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=599;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=598;
- atlOffset=-7.6293945e-06;
- };
- class Item46
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8477.4258,87.364433,25267.172};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=601;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=600;
- atlOffset=-7.6293945e-06;
- };
- class Item47
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8475.6084,87.356567,25267.582};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=603;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=602;
- atlOffset=-7.6293945e-06;
- };
- class Item48
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8477.208,87.161316,25268.605};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=605;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=604;
- atlOffset=-7.6293945e-06;
- };
- class Item49
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8475.6709,87.171677,25268.9};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=607;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=606;
- atlOffset=-7.6293945e-06;
- };
- class Item50
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8473.959,88.366005,25261.775};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=609;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=608;
- atlOffset=-7.6293945e-06;
- };
- class Item51
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8473.7617,87.935211,25264.037};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=611;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=610;
- atlOffset=-7.6293945e-06;
- };
- class Item52
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8472.1426,88.180611,25261.979};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=613;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=612;
- atlOffset=-7.6293945e-06;
- };
- class Item53
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8471.8867,87.826546,25263.867};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=615;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=614;
- atlOffset=-7.6293945e-06;
- };
- class Item54
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8473.2471,87.600647,25265.646};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=617;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=616;
- atlOffset=-7.6293945e-06;
- };
- class Item55
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8471.5889,87.45697,25265.756};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=619;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=618;
- atlOffset=-7.6293945e-06;
- };
- class Item56
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8473.0645,87.330925,25267.039};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=621;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=620;
- atlOffset=-7.6293945e-06;
- };
- class Item57
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8471.2471,87.119759,25267.449};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=623;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=622;
- atlOffset=-7.6293945e-06;
- };
- class Item58
- {
- dataType="Group";
- side="Civilian";
- class Entities
- {
- items=1;
- class Item0
- {
- dataType="Object";
- class PositionInfo
- {
- position[]={8472.8467,87.124672,25268.473};
- };
- side="Civilian";
- flags=7;
- class Attributes
- {
- skill=1;
- init="if (!isPlayer this) then { this hideObjectGlobal true; this enableSimulationGlobal false; };";
- isPlayable=1;
- };
- id=625;
- type="C_man_p_beggar_F";
- atlOffset=-7.6293945e-06;
- class CustomAttributes
- {
- class Attribute0
- {
- property="speaker";
- expression="_this setspeaker _value;";
- class Value
- {
- class data
- {
- singleType="STRING";
- value="Male03GRE";
- };
- };
- };
- class Attribute1
- {
- property="pitch";
- expression="_this setpitch _value;";
- class Value
- {
- class data
- {
- singleType="SCALAR";
- value=0.97000003;
- };
- };
- };
- nAttributes=2;
- };
- };
- };
- class Attributes
- {
- };
- id=624;
- atlOffset=-7.6293945e-06;
- };
- };
- };
- ==================== END OF: mission.sqm ====================
- ==================== 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
- ];
- // 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;
- diag_log format ["Play Area Created - Center: %1, Radius: %2m", _playAreaCenter, round _playAreaRadius];
- // 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;
- if (count _wpPos >= 2 && !(_wpPos isEqualTo [0,0,0])) then {
- private _wpDistance = _wpPos distance2D _center;
- if (_wpDistance > _radius) then {
- // Waypoint is outside, delete it
- deleteWaypoint _x;
- };
- };
- } forEach _waypoints;
- // If group leader is outside, teleport them back
- if (!isNull _leader && alive _leader) then {
- private _leaderDistance = _leader distance2D _center;
- if (_leaderDistance > _radius) then {
- // Find a position back inside the play area
- private _directionToCenter = _leader getDir _center;
- private _safeDistance = _radius - 50;
- private _safePos = _center getPos [_safeDistance, _directionToCenter + 180];
- // Teleport entire group
- {
- if (alive _x) then {
- _x setPos _safePos;
- };
- } forEach (units _group);
- // NEW: Clear old tasks and give a new one to prevent repeated wandering.
- 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 "SAD"; // Search and Destroy
- _wp setWaypointBehaviour "COMBAT";
- _wp setWaypointCompletionRadius 200;
- // Apply a 60-second HC lock to this new task.
- _group setVariable ["HC_FORCED_LOCK", true];
- [_group] spawn {
- params ["_grp"];
- sleep 60;
- if (!isNull _grp) then {
- _grp setVariable ["HC_FORCED_LOCK", false];
- };
- };
- };
- };
- };
- };
- } 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];
- };
- // ===================== CHANGE END =====================
- 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 to move towards the center of the map.
- [] spawn {
- sleep 15;
- waitUntil {!isNil "mission_centralPoint"};
- private _centerPos = missionNamespace getVariable "mission_centralPoint";
- for "_i" from 0 to 2 do {
- if (count allUnits >= maxAI) exitWith {};
- // BLUFOR INFANTRY WAVE
- private _bluforGroup = ["infantry"] call fnc_spawnBluforWave;
- if (!isNull _bluforGroup) then {
- [_bluforGroup, _centerPos] call fnc_createPatrolWaypoints;
- };
- sleep 1;
- if (count allUnits >= maxAI) exitWith {};
- // OPFOR INFANTRY WAVE
- private _opforGroup = ["infantry"] call fnc_spawnOpforWave;
- if (!isNull _opforGroup) then {
- [_opforGroup, _centerPos] 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
- // REWORKED: Centralized performance monitoring and dynamic scaling system.
- // This system no longer disables features, but instead scales their intensity and frequency.
- // --- Global Performance Variables ---
- if (isNil "PERF_STATE") then { PERF_STATE = "EXCELLENT"; };
- if (isNil "PERF_MAIN_LOOP_MULTIPLIER") then { PERF_MAIN_LOOP_MULTIPLIER = 1.0; };
- if (isNil "PERF_AI_BATCH_SIZE") then { PERF_AI_BATCH_SIZE = 8; };
- if (isNil "PERF_SEARCH_RADIUS_MULTIPLIER") then { PERF_SEARCH_RADIUS_MULTIPLIER = 1.0; };
- if (isNil "PERF_COOLDOWN_MULTIPLIER") then { PERF_COOLDOWN_MULTIPLIER = 1.0; };
- // Monitor server performance and adjust intervals dynamically
- fnc_monitorPerformance = {
- private _deltaHistory = [];
- private _execTimeHistory = [];
- private _historySize = 4;
- while {true} do {
- private _schedulerDelta = diag_deltaTime;
- private _lastExecTime = missionNamespace getVariable ["BOTAI_lastExecTime", 0];
- _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;
- 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;
- };
- };
- };
- };
- };
- 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;
- };
- };
- };
- };
- };
- private _oldState = PERF_STATE;
- private _worstMultiplier = _deltaMultiplier max _execMultiplier;
- if (_worstMultiplier == _deltaMultiplier) then {
- PERF_STATE = _deltaState;
- } else {
- PERF_STATE = _execState;
- };
- if (_oldState != PERF_STATE) then {
- diag_log format ["[PERF] State Change: %1 -> %2", _oldState, PERF_STATE];
- [] call fnc_adjustPerformanceScales;
- };
- sleep 2.5;
- };
- };
- // Helper function to get adaptive sleep time
- fnc_getAdaptiveSleep = {
- params ["_baseSleep"];
- (_baseSleep * PERF_MAIN_LOOP_MULTIPLIER)
- };
- // REWORKED: Centralized function to adjust all game scales based on performance state.
- fnc_adjustPerformanceScales = {
- switch (PERF_STATE) do {
- case "EXCELLENT": {
- PERF_MAIN_LOOP_MULTIPLIER = 1.0;
- PERF_AI_BATCH_SIZE = 8;
- PERF_SEARCH_RADIUS_MULTIPLIER = 1.0; // 100% search distance
- PERF_COOLDOWN_MULTIPLIER = 1.0; // 100% cooldown time
- };
- case "GOOD": {
- PERF_MAIN_LOOP_MULTIPLIER = 1.5;
- PERF_AI_BATCH_SIZE = 6;
- PERF_SEARCH_RADIUS_MULTIPLIER = 0.9; // 90% search distance
- PERF_COOLDOWN_MULTIPLIER = 1.25; // 125% cooldown time
- };
- case "MODERATE": {
- PERF_MAIN_LOOP_MULTIPLIER = 2.5;
- PERF_AI_BATCH_SIZE = 4;
- PERF_SEARCH_RADIUS_MULTIPLIER = 0.8; // 80% search distance
- PERF_COOLDOWN_MULTIPLIER = 1.75; // 175% cooldown time
- };
- case "POOR": {
- PERF_MAIN_LOOP_MULTIPLIER = 4.0;
- PERF_AI_BATCH_SIZE = 3;
- PERF_SEARCH_RADIUS_MULTIPLIER = 0.7; // 70% search distance
- PERF_COOLDOWN_MULTIPLIER = 2.5; // 250% cooldown time
- };
- case "CRITICAL": {
- PERF_MAIN_LOOP_MULTIPLIER = 6.0;
- PERF_AI_BATCH_SIZE = 2;
- PERF_SEARCH_RADIUS_MULTIPLIER = 0.6; // 60% search distance
- PERF_COOLDOWN_MULTIPLIER = 3.0; // 300% cooldown time
- };
- case "EMERGENCY": {
- PERF_MAIN_LOOP_MULTIPLIER = 10.0;
- PERF_AI_BATCH_SIZE = 1;
- PERF_SEARCH_RADIUS_MULTIPLIER = 0.6; // 60% search distance
- PERF_COOLDOWN_MULTIPLIER = 4.0; // 400% cooldown time
- };
- };
- };
- if (isServer) then {
- [] spawn {
- sleep 5;
- [] 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 - 1 max 0, 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 - 1 max 0, 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