2025-07-24 17:55:01 +02:00

247 lines
12 KiB
Lua

-- ====================================================================================
-- TUM.ENEMYAIRDEFENSE - HANDLES THE OPFOR AIR DEFENSE
-- ====================================================================================
-- (local) addSAMCoverage(point2)
-- (local) isPointCoveredBySAM(point2)
-- (local) createEnemyStrategicAirDefense()
-- (local) createEnemyScatteredAirDefense()
-- TUM.enemyAirDefense.onStartUp()
-- ====================================================================================
TUM.enemyAirDefense = {}
do
local AIR_DEFENSE_RANGE = { -- in meters
[DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS] = 750,
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_STATIC] = 1500,
[DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE] = 1500,
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR] = 6000,
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT] = 8000,
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM] = 35000,
[DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG] = 70000,
}
local airDefenseGroups = {} -- Stores info about all air defense groups (groupID, point2, range and unitFamily)
local function getPointDefenseFamily(forceSHORAD)
local airDefenseLevel = TUM.settings.getValue(TUM.settings.id.ENEMY_AIR_DEFENSE) - 1
if airDefenseLevel > 1 and forceSHORAD then
return DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT
end
if airDefenseLevel <= 1 then return DCSEx.table.getRandom({DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE, DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR})
elseif airDefenseLevel == 2 then return DCSEx.table.getRandom({DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE, DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT})
elseif airDefenseLevel == 3 then return DCSEx.table.getRandom({DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT})
else return DCSEx.table.getRandom({DCSEx.enums.unitFamily.AIRDEFENSE_AAA_MOBILE, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT_IR, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_SHORT})
end
end
local function getSkillLevel()
local airDefenseLevel = TUM.settings.getValue(TUM.settings.id.ENEMY_AIR_DEFENSE) - 1
if airDefenseLevel <= 1 then return "Average"
elseif airDefenseLevel == 2 then return DCSEx.table.getRandom({"Average", "Good", "Good", "High"})
elseif airDefenseLevel == 3 then return DCSEx.table.getRandom({"High", "Excellent"})
else return "Excellent"
end
end
local function getEnemyPointUnitsProtectingPoint(point2)
local count = 0
for _,adg in ipairs(airDefenseGroups) do
if DCSEx.math.getDistance2D(adg.point2, point2) < adg.range then
count = count + 1
end
end
return count
end
local function addAirDefenseGroup(side, faction, unitFamily, point)
if not point then return end
-- Make sure no air defense unit is spawned in range of an allied player
for _,p in ipairs(coalition.getPlayers(TUM.settings.getPlayerCoalition())) do
if DCSEx.math.getDistance2D(point, DCSEx.math.vec3ToVec2(p:getPoint())) < AIR_DEFENSE_RANGE[unitFamily] then
return false
end
end
local units = Library.factions.getUnits(faction, unitFamily, 1)
if not units or #units == 0 then return false end -- No valid units found
local skill = getSkillLevel()
if unitFamily == DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS then skill = "Average" end -- MANPADS are too good in DCS, nerf them a little
local groupInfo = DCSEx.unitGroupMaker.create(side, Group.Category.GROUND, point, units, { skill = skill })
if not groupInfo then return false end -- Failed to create group
local adGroup = {
groupID = groupInfo.groupID,
point2 = point,
range = AIR_DEFENSE_RANGE[unitFamily],
unitFamily = unitFamily
}
table.insert(airDefenseGroups, adGroup)
return true
end
local function createPointAirDefense(airDefenseLevel, side, faction)
-- Add point air defense near all objectives
for i=1,TUM.objectives.getCount() do
local objPoint2 = TUM.objectives.getObjective(i).point2 -- Objective location
local desiredCount = math.random(math.floor(airDefenseLevel / 3), math.ceil(airDefenseLevel / 1.5)) -- Number of desired point air defense groups for this objective
-- local desiredCount = math.random(math.floor(airDefenseLevel / 2), math.ceil(airDefenseLevel / 1.25)) -- Number of desired point air defense groups for this objective
local adCount = desiredCount - getEnemyPointUnitsProtectingPoint(objPoint2) -- Number of point air defense groups already defending this objective
local realCount = 0
if adCount > 0 then -- Not enough groups? Create a few to reach the desired number
for j=1,adCount do
local forceSHORAD = false
-- if j == 1 then forceSHORAD = (math.random(1, 2) == 1) end
if j == 1 then forceSHORAD = (math.random(1, 5) <= 2) end
local unitFamily = getPointDefenseFamily(forceSHORAD)
local point = DCSEx.math.randomPointInCircle(objPoint2, AIR_DEFENSE_RANGE[unitFamily], AIR_DEFENSE_RANGE[unitFamily] / 2, land.SurfaceType.LAND)
if addAirDefenseGroup(side, faction, unitFamily, point) then
realCount = realCount + 1
else
TUM.log("Failed to add point air defense group near objective "..TUM.objectives.getObjective(i).name..".", TUM.logLevel.WARNING)
end
end
end
TUM.log(string.format("Spawned %d air defense unit(s) near objective %s.", realCount, TUM.objectives.getObjective(i).name))
end
end
local function createLocalAirDefense(airDefenseLevel, side, faction, objectivesCenter, objectivesRadius)
-- local count = math.ceil(math.random(2, 3) * math.max(1, math.sqrt(objectivesRadius) / 200) * math.sqrt(airDefenseLevel))
local count = math.ceil(math.random(2, 3) * math.max(1, math.sqrt(objectivesRadius) / 300) * math.sqrt(airDefenseLevel))
if count <= 0 then return end
local realCount = 0
for i=1,count do
local forceSHORAD = false
-- if i <= math.max(1, count / 4) then forceSHORAD = true end
if i <= math.max(1, count / 6) then forceSHORAD = true end
local unitFamily = getPointDefenseFamily(forceSHORAD)
local point = DCSEx.math.randomPointInCircle(objectivesCenter, objectivesRadius, 0, land.SurfaceType.LAND)
if addAirDefenseGroup(side, faction, unitFamily, point) then
realCount = realCount + 1
else
TUM.log("Failed to add local air defense group.", TUM.logLevel.WARNING)
end
end
TUM.log(string.format("Spawned %d air defense unit(s) around the objectives.", realCount))
end
local function createMANPADs(airDefenseLevel, side, faction, objectivesCenter, objectivesRadius)
-- local count = math.ceil(math.random(2, 3) * math.max(1, math.sqrt(objectivesRadius) / 120) * math.sqrt(airDefenseLevel))
local count = math.ceil(math.random(2, 3) * math.max(1, math.sqrt(objectivesRadius) / 250) * math.sqrt(airDefenseLevel))
if count <= 0 then return end
local realCount = 0
for _=1,count do
local point = DCSEx.math.randomPointInCircle(objectivesCenter, objectivesRadius, 0, land.SurfaceType.LAND)
if addAirDefenseGroup(side, faction, DCSEx.enums.unitFamily.AIRDEFENSE_MANPADS, point) then
realCount = realCount + 1
else
TUM.log("Failed to add local MANPADS group.", TUM.logLevel.WARNING)
end
end
TUM.log(string.format("Spawned %d MANPADS around the objectives.", realCount))
end
local function addStrategicSAMSite(side, faction, unitFamily, objectivesCenter, objectivesRadius)
local point = DCSEx.math.randomPointInCircle(objectivesCenter, objectivesRadius)
if point then
local zoneCenter = TUM.territories.getRandomPointInTerritory(side, land.SurfaceType.LAND)
if not zoneCenter then zoneCenter = TUM.territories.getRandomPointInTerritory(side) end
if not zoneCenter then zoneCenter = DCSEx.table.getRandom(TUM.territories.getTerritoryZones(side)) end
local vector = { x = zoneCenter.x - point.x, y = zoneCenter.y - point.y }
vector = DCSEx.math.normalizeVec2(vector)
local distance = math.floor(DCSEx.math.getDistance2D(zoneCenter, point) * 2)
local step = math.min(distance / 10, 2500)
for __=0,distance,step do
point.x = point.x + vector.x * step
point.y = point.y + vector.y * step
if TUM.territories.getPointOwner(point) == side and land.getSurfaceType(point) == land.SurfaceType.LAND then
local distanceToObjectives = DCSEx.math.getDistance2D(objectivesCenter, point)
if distanceToObjectives > AIR_DEFENSE_RANGE[unitFamily] * 2 then return false end -- Went too far, no need to spawn a SAM site here, it will never engage the players and only eat up CPU
if distanceToObjectives > AIR_DEFENSE_RANGE[unitFamily] / 2 then
return addAirDefenseGroup(side, faction, unitFamily, point)
end
end
end
end
return false
end
local function createStrategicAirDefense(airDefenseLevel, side, faction, objectivesCenter, objectivesRadius)
if airDefenseLevel <= 1 then return end
local count = math.random(1, math.ceil(airDefenseLevel / 1.5))
local realCount = 0
local rerollsLeft = 5
for i=1,count do
local unitFamily = DCSEx.table.getRandom({DCSEx.enums.unitFamily.AIRDEFENSE_SAM_MEDIUM, DCSEx.enums.unitFamily.AIRDEFENSE_SAM_LONG})
if addStrategicSAMSite(side, faction, unitFamily, objectivesCenter, objectivesRadius) then
realCount = realCount + 1
elseif rerollsLeft > 0 then -- Small chance to retry if couldn't spawn the SAM site last time
if math.random(1, 3) == 1 then
i = i - 1
rerollsLeft = rerollsLeft - 1
end
end
end
TUM.log(string.format("Spawned %d strategic SAM(s) on enemy territory.", realCount))
end
function TUM.enemyAirDefense.create()
TUM.enemyAirDefense.removeAll() -- Destroy all pre-existing air defense
TUM.log("Creating enemy air defense...")
if TUM.objectives.getCount() == 0 then return end -- No objectives, no air defense
local airDefenseLevel = TUM.settings.getValue(TUM.settings.id.ENEMY_AIR_DEFENSE) - 1
if airDefenseLevel <= 0 then return end -- No surface-to-air defense at all
if TUM.settings.getValue(TUM.settings.id.TASKING) == DCSEx.enums.taskFamily.ANTISHIP then return end -- No ground air defense during antiship strikes
local side = TUM.settings.getEnemyCoalition()
local faction = TUM.settings.getEnemyFaction()
local objectivesCenter = TUM.objectives.getCenter()
local objectivesRadius = TUM.objectives.getRadius()
createPointAirDefense(airDefenseLevel, side, faction) -- Must be created before the other layers, else it may think objective sites are already protected and fail to generate point defense
createLocalAirDefense(airDefenseLevel, side, faction, objectivesCenter, objectivesRadius)
createMANPADs(airDefenseLevel, side, faction, objectivesCenter, objectivesRadius)
createStrategicAirDefense(airDefenseLevel, side, faction, objectivesCenter, objectivesRadius)
end
function TUM.enemyAirDefense.removeAll()
if #airDefenseGroups > 0 then TUM.log("Removing all enemy air defense...") end
for _,g in ipairs(airDefenseGroups) do
DCSEx.world.destroyGroupByID(g.groupID)
end
airDefenseGroups = {}
end
end