mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
2964 lines
87 KiB
Lua
2964 lines
87 KiB
Lua
env.info("--- SKYNET VERSION: 1.1.3 | BUILD TIME: 30.09.2020 1816Z ---")
|
|
do
|
|
--this file contains the required units per sam type
|
|
samTypesDB = {
|
|
['S-300'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['S-300PS 40B6MD sr'] = {
|
|
},
|
|
['S-300PS 64H6E sr'] = {
|
|
},
|
|
},
|
|
['trackingRadar'] = {
|
|
['S-300PS 40B6M tr'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['S-300PS 5P85D ln'] = {
|
|
},
|
|
['S-300PS 5P85C ln'] = {
|
|
},
|
|
},
|
|
['misc'] = {
|
|
['S-300PS 54K6 cp'] = {
|
|
['required'] = true,
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-10 Grumble',
|
|
},
|
|
['harm_detection_chance'] = 90
|
|
},
|
|
['Buk'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['SA-11 Buk SR 9S18M1'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['SA-11 Buk LN 9A310M1'] = {
|
|
},
|
|
},
|
|
['misc'] = {
|
|
['SA-11 Buk CC 9S470M1'] = {
|
|
['required'] = true,
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-11 Gadfly',
|
|
},
|
|
['harm_detection_chance'] = 70
|
|
},
|
|
['s-125'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['p-19 s-125 sr'] = {
|
|
},
|
|
},
|
|
['trackingRadar'] = {
|
|
['snr s-125 tr'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['5p73 s-125 ln'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-3 Goa',
|
|
},
|
|
['harm_detection_chance'] = 40
|
|
},
|
|
['s-75'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['p-19 s-125 sr'] = {
|
|
},
|
|
},
|
|
['trackingRadar'] = {
|
|
['SNR_75V'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['S_75M_Volhov'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-2 Guideline',
|
|
},
|
|
['harm_detection_chance'] = 30
|
|
},
|
|
['Kub'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['Kub 1S91 str'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Kub 2P25 ln'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-6 Gainful',
|
|
},
|
|
['harm_detection_chance'] = 40
|
|
},
|
|
['Patriot'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['Patriot str'] = {
|
|
},
|
|
},
|
|
|
|
['launchers'] = {
|
|
['Patriot ln'] = {
|
|
},
|
|
},
|
|
['misc'] = {
|
|
['Patriot cp'] = {
|
|
['required'] = false,
|
|
},
|
|
['Patriot EPP'] = {
|
|
['required'] = false,
|
|
},
|
|
['Patriot ECS'] = {
|
|
['required'] = true,
|
|
},
|
|
['Patriot AMG'] = {
|
|
['required'] = false,
|
|
},
|
|
},
|
|
|
|
|
|
['name'] = {
|
|
['NATO'] = 'Patriot',
|
|
},
|
|
['harm_detection_chance'] = 90
|
|
},
|
|
['Hawk'] = {
|
|
['type'] = 'complex',
|
|
['searchRadar'] = {
|
|
['Hawk sr'] = {
|
|
},
|
|
},
|
|
['trackingRadar'] = {
|
|
['Hawk tr'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Hawk ln'] = {
|
|
},
|
|
},
|
|
|
|
['name'] = {
|
|
['NATO'] = 'Hawk',
|
|
},
|
|
['harm_detection_chance'] = 40
|
|
|
|
},
|
|
['Roland ADS'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['Roland ADS'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Roland ADS'] = {
|
|
},
|
|
},
|
|
|
|
['name'] = {
|
|
['NATO'] = 'Roland ADS',
|
|
},
|
|
['harm_detection_chance'] = 60
|
|
},
|
|
['2S6 Tunguska'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['2S6 Tunguska'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['2S6 Tunguska'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-19 Grison',
|
|
},
|
|
},
|
|
['Osa'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['Osa 9A33 ln'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Osa 9A33 ln'] = {
|
|
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-8 Gecko',
|
|
},
|
|
['harm_detection_chance'] = 20
|
|
},
|
|
['Strela-10M3'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['Strela-10M3'] = {
|
|
['trackingRadar'] = true,
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Strela-10M3'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-13 Gopher',
|
|
},
|
|
},
|
|
['Strela-1 9P31'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['Strela-1 9P31'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Strela-1 9P31'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-9 Gaskin',
|
|
},
|
|
['harm_detection_chance'] = 20
|
|
},
|
|
['Tor'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['Tor 9A331'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Tor 9A331'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'SA-15 Gauntlet',
|
|
},
|
|
},
|
|
['Gepard'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['Gepard'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['Gepard'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Gepard',
|
|
},
|
|
['harm_detection_chance'] = 10
|
|
},
|
|
['Rapier'] = {
|
|
['searchRadar'] = {
|
|
['rapier_fsa_blindfire_radar'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['rapier_fsa_launcher'] = {
|
|
['trackingRadar'] = true,
|
|
},
|
|
},
|
|
['misc'] = {
|
|
['rapier_fsa_optical_tracker_unit'] = {
|
|
['required'] = true,
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Rapier',
|
|
},
|
|
['harm_detection_chance'] = 10
|
|
},
|
|
['ZSU-23-4 Shilka'] = {
|
|
['type'] = 'single',
|
|
['searchRadar'] = {
|
|
['ZSU-23-4 Shilka'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['ZSU-23-4 Shilka'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Zues',
|
|
},
|
|
['harm_detection_chance'] = 10
|
|
},
|
|
['HQ-7'] = {
|
|
['searchRadar'] = {
|
|
['HQ-7_STR_SP'] = {
|
|
},
|
|
},
|
|
['launchers'] = {
|
|
['HQ-7_LN_SP'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'CSA-4',
|
|
},
|
|
['harm_detection_chance'] = 30
|
|
},
|
|
--- Start of EW radars:
|
|
['1L13 EWR'] = {
|
|
['type'] = 'ewr',
|
|
['searchRadar'] = {
|
|
['1L13 EWR'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = '1L13 EWR',
|
|
},
|
|
['harm_detection_chance'] = 60
|
|
},
|
|
['55G6 EWR'] = {
|
|
['type'] = 'ewr',
|
|
['searchRadar'] = {
|
|
['55G6 EWR'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = '55G6 EWR',
|
|
},
|
|
['harm_detection_chance'] = 60
|
|
},
|
|
['Dog Ear'] = {
|
|
['type'] = 'ewr',
|
|
['searchRadar'] = {
|
|
['Dog Ear radar'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Dog Ear',
|
|
},
|
|
['harm_detection_chance'] = 20
|
|
},
|
|
['Roland Radar'] = {
|
|
['type'] = 'ewr',
|
|
['searchRadar'] = {
|
|
['Roland Radar'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Roland EWR',
|
|
},
|
|
['harm_detection_chance'] = 60
|
|
},
|
|
['p-19 s-125 sr'] = {
|
|
['searchRadar'] = {
|
|
['p-19 s-125 sr'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Flat Face',
|
|
},
|
|
['harm_detection_chance'] = 40
|
|
},
|
|
['Patriot str'] = {
|
|
['searchRadar'] = {
|
|
['Patriot str'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Patriot str',
|
|
},
|
|
['harm_detection_chance'] = 80
|
|
},
|
|
['EW S-300'] = {
|
|
['searchRadar'] = {
|
|
['S-300PS 40B6MD sr'] = {
|
|
},
|
|
['S-300PS 64H6E sr'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Big Bird',
|
|
},
|
|
['harm_detection_chance'] = 90
|
|
},
|
|
['SA-11 Buk SR 9S18M1'] = {
|
|
['searchRadar'] = {
|
|
['SA-11 Buk SR 9S18M1'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Snow Drift',
|
|
},
|
|
['harm_detection_chance'] = 70
|
|
},
|
|
['Kub 1S91 str'] = {
|
|
['searchRadar'] = {
|
|
['Kub 1S91 str'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Straight Flush',
|
|
},
|
|
['harm_detection_chance'] = 40
|
|
},
|
|
['Hawk str'] = {
|
|
['searchRadar'] = {
|
|
['Hawk sr'] = {
|
|
},
|
|
},
|
|
['name'] = {
|
|
['NATO'] = 'Hawk str',
|
|
},
|
|
['harm_detection_chance'] = 40
|
|
},
|
|
}
|
|
end
|
|
do
|
|
|
|
SkynetIADS = {}
|
|
SkynetIADS.__index = SkynetIADS
|
|
|
|
SkynetIADS.database = samTypesDB
|
|
|
|
function SkynetIADS:create(name)
|
|
local iads = {}
|
|
setmetatable(iads, SkynetIADS)
|
|
iads.radioMenu = nil
|
|
iads.earlyWarningRadars = {}
|
|
iads.samSites = {}
|
|
iads.commandCenters = {}
|
|
iads.ewRadarScanMistTaskID = nil
|
|
iads.samSetupMistTaskID = nil
|
|
iads.coalition = nil
|
|
iads.contacts = {}
|
|
iads.maxTargetAge = 32
|
|
iads.name = name
|
|
if iads.name == nil then
|
|
iads.name = ""
|
|
end
|
|
iads.contactUpdateInterval = 5
|
|
iads.samSetupTime = 60
|
|
iads.destroyedUnitResponsibleForUpdateAutonomousStateOfSAMSite = nil
|
|
iads.debugOutput = {}
|
|
iads.debugOutput.IADSStatus = false
|
|
iads.debugOutput.samWentDark = false
|
|
iads.debugOutput.contacts = false
|
|
iads.debugOutput.radarWentLive = false
|
|
iads.debugOutput.ewRadarNoConnection = false
|
|
iads.debugOutput.samNoConnection = false
|
|
iads.debugOutput.jammerProbability = false
|
|
iads.debugOutput.addedEWRadar = false
|
|
iads.debugOutput.hasNoPower = false
|
|
iads.debugOutput.addedSAMSite = false
|
|
iads.debugOutput.warnings = true
|
|
iads.debugOutput.harmDefence = false
|
|
iads.debugOutput.samSiteStatusEnvOutput = false
|
|
iads.debugOutput.earlyWarningRadarStatusEnvOutput = false
|
|
return iads
|
|
end
|
|
|
|
function SkynetIADS:setUpdateInterval(interval)
|
|
self.contactUpdateInterval = interval
|
|
end
|
|
|
|
function SkynetIADS:setCoalition(item)
|
|
if item then
|
|
local coalitionID = item:getCoalition()
|
|
if self.coalitionID == nil then
|
|
self.coalitionID = coalitionID
|
|
end
|
|
if self.coalitionID ~= coalitionID then
|
|
self:printOutput("element: "..item:getName().." has a different coalition than the IADS", true)
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:addJammer(jammer)
|
|
table.insert(self.jammers, jammer)
|
|
end
|
|
|
|
function SkynetIADS:getCoalition()
|
|
return self.coalitionID
|
|
end
|
|
|
|
function SkynetIADS:getDestroyedEarlyWarningRadars()
|
|
local destroyedSites = {}
|
|
for i = 1, #self.earlyWarningRadars do
|
|
local ewSite = self.earlyWarningRadars[i]
|
|
if ewSite:isDestroyed() then
|
|
table.insert(destroyedSites, ewSite)
|
|
end
|
|
end
|
|
return destroyedSites
|
|
end
|
|
|
|
function SkynetIADS:getUsableAbstractRadarElemtentsOfTable(abstractRadarTable)
|
|
local usable = {}
|
|
for i = 1, #abstractRadarTable do
|
|
local abstractRadarElement = abstractRadarTable[i]
|
|
if abstractRadarElement:hasActiveConnectionNode() and abstractRadarElement:hasWorkingPowerSource() and abstractRadarElement:isDestroyed() == false then
|
|
table.insert(usable, abstractRadarElement)
|
|
end
|
|
end
|
|
return usable
|
|
end
|
|
|
|
function SkynetIADS:getUsableEarlyWarningRadars()
|
|
return self:getUsableAbstractRadarElemtentsOfTable(self.earlyWarningRadars)
|
|
end
|
|
|
|
function SkynetIADS:createTableDelegator(units)
|
|
local sites = SkynetIADSTableDelegator:create()
|
|
for i = 1, #units do
|
|
local site = units[i]
|
|
table.insert(sites, site)
|
|
end
|
|
return sites
|
|
end
|
|
|
|
function SkynetIADS:addEarlyWarningRadarsByPrefix(prefix)
|
|
self:deactivateEarlyWarningRadars()
|
|
self.earlyWarningRadars = {}
|
|
for unitName, unit in pairs(mist.DBs.unitsByName) do
|
|
local pos = self:findSubString(unitName, prefix)
|
|
--somehow the MIST unit db contains StaticObject, we check to see we only add Units
|
|
local unit = Unit.getByName(unitName)
|
|
if pos and pos == 1 and unit then
|
|
self:addEarlyWarningRadar(unitName)
|
|
end
|
|
end
|
|
return self:createTableDelegator(self.earlyWarningRadars)
|
|
end
|
|
|
|
function SkynetIADS:addEarlyWarningRadar(earlyWarningRadarUnitName)
|
|
local earlyWarningRadarUnit = Unit.getByName(earlyWarningRadarUnitName)
|
|
if earlyWarningRadarUnit == nil then
|
|
self:printOutput("you have added an EW Radar that does not exist, check name of Unit in Setup and Mission editor: "..earlyWarningRadarUnitName, true)
|
|
return
|
|
end
|
|
self:setCoalition(earlyWarningRadarUnit)
|
|
local ewRadar = nil
|
|
local category = earlyWarningRadarUnit:getDesc().category
|
|
if category == Unit.Category.AIRPLANE or category == Unit.Category.SHIP then
|
|
ewRadar = SkynetIADSAWACSRadar:create(earlyWarningRadarUnit, self)
|
|
else
|
|
ewRadar = SkynetIADSEWRadar:create(earlyWarningRadarUnit, self)
|
|
end
|
|
ewRadar:setupElements()
|
|
ewRadar:setCachedTargetsMaxAge(self:getCachedTargetsMaxAge())
|
|
-- for performance improvement, if iads is not scanning no update coverage update needs to be done, will be executed once when iads activates
|
|
if self.ewRadarScanMistTaskID ~= nil then
|
|
self:updateIADSCoverage()
|
|
end
|
|
ewRadar:goLive()
|
|
table.insert(self.earlyWarningRadars, ewRadar)
|
|
if self:getDebugSettings().addedEWRadar then
|
|
self:printOutput(ewRadar:getDescription().." added to IADS")
|
|
end
|
|
return ewRadar
|
|
end
|
|
|
|
function SkynetIADS:getCachedTargetsMaxAge()
|
|
return self.contactUpdateInterval
|
|
end
|
|
|
|
function SkynetIADS:getEarlyWarningRadars()
|
|
return self:createTableDelegator(self.earlyWarningRadars)
|
|
end
|
|
|
|
function SkynetIADS:getEarlyWarningRadarByUnitName(unitName)
|
|
for i = 1, #self.earlyWarningRadars do
|
|
local ewRadar = self.earlyWarningRadars[i]
|
|
if ewRadar:getDCSName() == unitName then
|
|
return ewRadar
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:findSubString(haystack, needle)
|
|
return string.find(haystack, needle, 1, true)
|
|
end
|
|
|
|
function SkynetIADS:addSAMSitesByPrefix(prefix)
|
|
self:deativateSAMSites()
|
|
self.samSites = {}
|
|
for groupName, groupData in pairs(mist.DBs.groupsByName) do
|
|
local pos = self:findSubString(groupName, prefix)
|
|
if pos and pos == 1 then
|
|
--mist returns groups, units and, StaticObjects
|
|
local dcsObject = Group.getByName(groupName)
|
|
if dcsObject then
|
|
self:addSAMSite(groupName)
|
|
end
|
|
end
|
|
end
|
|
return self:createTableDelegator(self.samSites)
|
|
end
|
|
|
|
function SkynetIADS:getSAMSitesByPrefix(prefix)
|
|
local returnSams = {}
|
|
for i = 1, #self.samSites do
|
|
local samSite = self.samSites[i]
|
|
local groupName = samSite:getDCSName()
|
|
local pos = self:findSubString(groupName, prefix)
|
|
if pos and pos == 1 then
|
|
table.insert(returnSams, samSite)
|
|
end
|
|
end
|
|
return self:createTableDelegator(returnSams)
|
|
end
|
|
|
|
function SkynetIADS:addSAMSite(samSiteName)
|
|
local samSiteDCS = Group.getByName(samSiteName)
|
|
if samSiteDCS == nil then
|
|
self:printOutput("you have added an SAM Site that does not exist, check name of Group in Setup and Mission editor: "..tostring(samSiteName), true)
|
|
return
|
|
end
|
|
self:setCoalition(samSiteDCS)
|
|
local samSite = SkynetIADSSamSite:create(samSiteDCS, self)
|
|
samSite:setupElements()
|
|
-- for performance improvement, if iads is not scanning no update coverage update needs to be done, will be executed once when iads activates
|
|
if self.ewRadarScanMistTaskID ~= nil then
|
|
self:updateIADSCoverage()
|
|
end
|
|
samSite:setCachedTargetsMaxAge(self:getCachedTargetsMaxAge())
|
|
samSite:goLive()
|
|
if samSite:getNatoName() == "UNKNOWN" then
|
|
self:printOutput("you have added an SAM Site that Skynet IADS can not handle: "..samSite:getDCSName(), true)
|
|
samSite:cleanUp()
|
|
else
|
|
samSite:goDark()
|
|
table.insert(self.samSites, samSite)
|
|
if self:getDebugSettings().addedSAMSite then
|
|
self:printOutput(samSite:getDescription().." added to IADS")
|
|
end
|
|
return samSite
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:getUsableSAMSites()
|
|
return self:getUsableAbstractRadarElemtentsOfTable(self.samSites)
|
|
end
|
|
|
|
function SkynetIADS:getDestroyedSAMSites()
|
|
local destroyedSites = {}
|
|
for i = 1, #self.samSites do
|
|
local samSite = self.samSites[i]
|
|
if samSite:isDestroyed() then
|
|
table.insert(destroyedSites, samSite)
|
|
end
|
|
end
|
|
return destroyedSites
|
|
end
|
|
|
|
function SkynetIADS:getSAMSites()
|
|
return self:createTableDelegator(self.samSites)
|
|
end
|
|
|
|
function SkynetIADS:getActiveSAMSites()
|
|
local activeSAMSites = {}
|
|
for i = 1, #self.samSites do
|
|
if self.samSites[i]:isActive() then
|
|
table.insert(activeSAMSites, self.samSites[i])
|
|
end
|
|
end
|
|
return activeSAMSites
|
|
end
|
|
|
|
function SkynetIADS:getSAMSiteByGroupName(groupName)
|
|
for i = 1, #self.samSites do
|
|
local samSite = self.samSites[i]
|
|
if samSite:getDCSName() == groupName then
|
|
return samSite
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:getSAMSitesByNatoName(natoName)
|
|
local selectedSAMSites = SkynetIADSTableDelegator:create()
|
|
for i = 1, #self.samSites do
|
|
local samSite = self.samSites[i]
|
|
if samSite:getNatoName() == natoName then
|
|
table.insert(selectedSAMSites, samSite)
|
|
end
|
|
end
|
|
return selectedSAMSites
|
|
end
|
|
|
|
function SkynetIADS:addCommandCenter(commandCenter)
|
|
self:setCoalition(commandCenter)
|
|
local comCenter = SkynetIADSCommandCenter:create(commandCenter, self)
|
|
table.insert(self.commandCenters, comCenter)
|
|
return comCenter
|
|
end
|
|
|
|
function SkynetIADS:isCommandCenterUsable()
|
|
local hasWorkingCommandCenter = (#self.commandCenters == 0)
|
|
for i = 1, #self.commandCenters do
|
|
local comCenter = self.commandCenters[i]
|
|
if comCenter:isDestroyed() == false and comCenter:hasWorkingPowerSource() then
|
|
hasWorkingCommandCenter = true
|
|
break
|
|
else
|
|
hasWorkingCommandCenter = false
|
|
end
|
|
end
|
|
return hasWorkingCommandCenter
|
|
end
|
|
|
|
function SkynetIADS:getCommandCenters()
|
|
return self.commandCenters
|
|
end
|
|
|
|
function SkynetIADS:setSAMSitesToAutonomousMode()
|
|
for i= 1, #self.samSites do
|
|
samSite = self.samSites[i]
|
|
samSite:goAutonomous()
|
|
end
|
|
end
|
|
|
|
function SkynetIADS.evaluateContacts(self)
|
|
if self:isCommandCenterUsable() == false then
|
|
if self:getDebugSettings().noWorkingCommmandCenter then
|
|
self:printOutput("No Working Command Center")
|
|
end
|
|
self:setSAMSitesToAutonomousMode()
|
|
return
|
|
end
|
|
|
|
local ewRadars = self:getUsableEarlyWarningRadars()
|
|
local samSites = self:getUsableSAMSites()
|
|
|
|
-- rewrote this part of the code to keep loops to a minimum
|
|
|
|
--will add SAM Sites acting as EW Rardars to the ewRadars array:
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
--We inform SAM sites that a target update is about to happen. If they have no targets in range after the cycle they go dark
|
|
samSite:targetCycleUpdateStart()
|
|
if samSite:getActAsEW() then
|
|
table.insert(ewRadars, samSite)
|
|
end
|
|
--if the sam site is not in ew mode and active we grab the detected targets right here
|
|
if samSite:isActive() and samSite:getActAsEW() == false then
|
|
local contacts = samSite:getDetectedTargets()
|
|
for j = 1, #contacts do
|
|
local contact = contacts[j]
|
|
self:mergeContact(contact)
|
|
end
|
|
end
|
|
end
|
|
|
|
local samSitesToTrigger = {}
|
|
|
|
for i = 1, #ewRadars do
|
|
local ewRadar = ewRadars[i]
|
|
--call go live in case ewRadar had to shut down (HARM attack)
|
|
ewRadar:goLive()
|
|
-- if an awacs has traveled more than a predeterminded distance we update the autonomous state of the sams
|
|
if getmetatable(ewRadar) == SkynetIADSAWACSRadar and ewRadar:isUpdateOfAutonomousStateOfSAMSitesRequired() then
|
|
self:updateAutonomousStatesOfSAMSites()
|
|
end
|
|
local ewContacts = ewRadar:getDetectedTargets()
|
|
if #ewContacts > 0 then
|
|
local samSitesUnderCoverage = ewRadar:getSAMSitesInCoveredArea()
|
|
for j = 1, #samSitesUnderCoverage do
|
|
local samSiteUnterCoverage = samSitesUnderCoverage[j]
|
|
-- only if a SAM site is not active we add it to the hash of SAM sites to be iterated later on
|
|
if samSiteUnterCoverage:isActive() == false then
|
|
--we add them to a hash to make sure each SAM site is in the collection only once, reducing the number of loops we conduct later on
|
|
samSitesToTrigger[samSiteUnterCoverage:getDCSName()] = samSiteUnterCoverage
|
|
end
|
|
end
|
|
for j = 1, #ewContacts do
|
|
local contact = ewContacts[j]
|
|
self:mergeContact(contact)
|
|
end
|
|
end
|
|
end
|
|
|
|
self:cleanAgedTargets()
|
|
|
|
for samName, samToTrigger in pairs(samSitesToTrigger) do
|
|
for j = 1, #self.contacts do
|
|
local contact = self.contacts[j]
|
|
-- the DCS Radar only returns enemy aircraft, if that should change a coalition check will be required
|
|
-- currently every type of object in the air is handed of to the SAM site, including missiles
|
|
local description = contact:getDesc()
|
|
local category = description.category
|
|
if category and category ~= Unit.Category.GROUND_UNIT and category ~= Unit.Category.SHIP and category ~= Unit.Category.STRUCTURE then
|
|
samToTrigger:informOfContact(contact)
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
samSite:targetCycleUpdateEnd()
|
|
end
|
|
|
|
self:printSystemStatus()
|
|
end
|
|
|
|
function SkynetIADS:cleanAgedTargets()
|
|
local contactsToKeep = {}
|
|
for i = 1, #self.contacts do
|
|
local contact = self.contacts[i]
|
|
if contact:getAge() < self.maxTargetAge then
|
|
table.insert(contactsToKeep, contact)
|
|
end
|
|
end
|
|
self.contacts = contactsToKeep
|
|
end
|
|
|
|
function SkynetIADS:buildSAMSitesInCoveredArea()
|
|
local samSites = self:getUsableSAMSites()
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
samSite:updateSAMSitesInCoveredArea()
|
|
end
|
|
|
|
local ewRadars = self:getUsableEarlyWarningRadars()
|
|
for i = 1, #ewRadars do
|
|
local ewRadar = ewRadars[i]
|
|
ewRadar:updateSAMSitesInCoveredArea()
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:updateIADSCoverage()
|
|
self:buildSAMSitesInCoveredArea()
|
|
self:enforceRebuildAutonomousStateOfSAMSites()
|
|
--update moose connector with radar group names Skynet is able to use
|
|
self:getMooseConnector():update()
|
|
end
|
|
|
|
function SkynetIADS:updateAutonomousStatesOfSAMSites(deadUnit)
|
|
--deat unit is to prevent multiple calls via the event handling of SkynetIADSAbstractElement when a units power source or connection node is destroyed
|
|
if deadUnit == nil or self.destroyedUnitResponsibleForUpdateAutonomousStateOfSAMSite ~= deadUnit then
|
|
self:updateIADSCoverage()
|
|
self.destroyedUnitResponsibleForUpdateAutonomousStateOfSAMSite = deadUnit
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:enforceRebuildAutonomousStateOfSAMSites()
|
|
local ewRadars = self:getUsableEarlyWarningRadars()
|
|
local samSites = self:getUsableSAMSites()
|
|
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
if samSite:getActAsEW() then
|
|
table.insert(ewRadars, samSite)
|
|
end
|
|
end
|
|
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
local inRange = false
|
|
for j = 1, #ewRadars do
|
|
if samSite:isInRadarDetectionRangeOf(ewRadars[j]) then
|
|
inRange = true
|
|
end
|
|
end
|
|
if inRange == false then
|
|
samSite:goAutonomous()
|
|
else
|
|
samSite:resetAutonomousState()
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:mergeContact(contact)
|
|
local existingContact = false
|
|
for i = 1, #self.contacts do
|
|
local iadsContact = self.contacts[i]
|
|
if iadsContact:getName() == contact:getName() then
|
|
iadsContact:refresh()
|
|
existingContact = true
|
|
end
|
|
end
|
|
if existingContact == false then
|
|
table.insert(self.contacts, contact)
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:getContacts()
|
|
return self.contacts
|
|
end
|
|
|
|
function SkynetIADS:printOutput(output, typeWarning)
|
|
if typeWarning == true and self.debugOutput.warnings or typeWarning == nil then
|
|
if typeWarning == true then
|
|
output = "WARNING: "..output
|
|
end
|
|
trigger.action.outText(output, 4)
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:getDebugSettings()
|
|
return self.debugOutput
|
|
end
|
|
|
|
-- will start going through the Early Warning Radars and SAM sites to check what targets they have detected
|
|
function SkynetIADS.activate(self)
|
|
mist.removeFunction(self.ewRadarScanMistTaskID)
|
|
mist.removeFunction(self.samSetupMistTaskID)
|
|
self.ewRadarScanMistTaskID = mist.scheduleFunction(SkynetIADS.evaluateContacts, {self}, 1, self.contactUpdateInterval)
|
|
self:updateIADSCoverage()
|
|
end
|
|
|
|
function SkynetIADS:setupSAMSitesAndThenActivate(setupTime)
|
|
if setupTime then
|
|
self.samSetupTime = setupTime
|
|
end
|
|
local samSites = self:getSAMSites()
|
|
for i = 1, #samSites do
|
|
local sam = samSites[i]
|
|
sam:goLive()
|
|
--stop harm scan, because this function will shut down point defences
|
|
sam:stopScanningForHARMs()
|
|
--point defences will go dark after sam:goLive() call on the SAM they are protecting, so we load them and call a separate goLive call here, some SAMs will therefore receive 2 goLive calls
|
|
-- this should not have a negative impact on performance
|
|
local pointDefences = sam:getPointDefences()
|
|
for j = 1, #pointDefences do
|
|
pointDefence = pointDefences[j]
|
|
pointDefence:goLive()
|
|
end
|
|
end
|
|
self.samSetupMistTaskID = mist.scheduleFunction(SkynetIADS.postSetupSAMSites, {self}, timer.getTime() + self.samSetupTime)
|
|
end
|
|
|
|
function SkynetIADS.postSetupSAMSites(self)
|
|
local samSites = self:getSAMSites()
|
|
for i = 1, #samSites do
|
|
local sam = samSites[i]
|
|
--turn on the scan again otherwise SAMs that fired a missile while in setup will not turn off anymore
|
|
sam:scanForHarms()
|
|
end
|
|
self:activate()
|
|
end
|
|
|
|
function SkynetIADS:deactivate()
|
|
mist.removeFunction(self.ewRadarScanMistTaskID)
|
|
self:deativateSAMSites()
|
|
self:deactivateEarlyWarningRadars()
|
|
self:deactivateCommandCenters()
|
|
end
|
|
|
|
function SkynetIADS:deactivateCommandCenters()
|
|
for i = 1, #self.commandCenters do
|
|
local comCenter = self.commandCenters[i]
|
|
comCenter:cleanUp()
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:deativateSAMSites()
|
|
for i = 1, #self.samSites do
|
|
local samSite = self.samSites[i]
|
|
samSite:cleanUp()
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:deactivateEarlyWarningRadars()
|
|
for i = 1, #self.earlyWarningRadars do
|
|
local ewRadar = self.earlyWarningRadars[i]
|
|
ewRadar:cleanUp()
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:addRadioMenu()
|
|
self.radioMenu = missionCommands.addSubMenu('SKYNET IADS '..self:getCoalitionString())
|
|
local displayIADSStatus = missionCommands.addCommand('show IADS Status', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = true, option = 'IADSStatus'})
|
|
local displayIADSStatus = missionCommands.addCommand('hide IADS Status', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = false, option = 'IADSStatus'})
|
|
local displayIADSStatus = missionCommands.addCommand('show contacts', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = true, option = 'contacts'})
|
|
local displayIADSStatus = missionCommands.addCommand('hide contacts', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = false, option = 'contacts'})
|
|
end
|
|
|
|
function SkynetIADS:removeRadioMenu()
|
|
missionCommands.removeItem(self.radioMenu)
|
|
end
|
|
|
|
function SkynetIADS.updateDisplay(params)
|
|
local option = params.option
|
|
local self = params.self
|
|
local value = params.value
|
|
if option == 'IADSStatus' then
|
|
self:getDebugSettings()[option] = value
|
|
elseif option == 'contacts' then
|
|
self:getDebugSettings()[option] = value
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:getCoalitionString()
|
|
local coalitionStr = "RED"
|
|
if self.coalitionID == coalition.side.BLUE then
|
|
coalitionStr = "BLUE"
|
|
elseif self.coalitionID == coalition.side.NEUTRAL then
|
|
coalitionStr = "NEUTRAL"
|
|
end
|
|
|
|
if self.name then
|
|
coalitionStr = coalitionStr.." "..self.name
|
|
end
|
|
|
|
return coalitionStr
|
|
end
|
|
|
|
function SkynetIADS:getMooseConnector()
|
|
if self.mooseConnector == nil then
|
|
self.mooseConnector = SkynetMooseA2ADispatcherConnector:create(self)
|
|
end
|
|
return self.mooseConnector
|
|
end
|
|
|
|
function SkynetIADS:addMooseSetGroup(mooseSetGroup)
|
|
self:getMooseConnector():addMooseSetGroup(mooseSetGroup)
|
|
end
|
|
|
|
function SkynetIADS:printDetailedEarlyWarningRadarStatus()
|
|
local ewRadars = self:getEarlyWarningRadars()
|
|
env.info("------------------------------------------ EW RADAR STATUS: "..self:getCoalitionString().." -------------------------------")
|
|
for i = 1, #ewRadars do
|
|
local ewRadar = ewRadars[i]
|
|
local numConnectionNodes = #ewRadar:getConnectionNodes()
|
|
local numPowerSources = #ewRadar:getPowerSources()
|
|
local isActive = ewRadar:isActive()
|
|
local connectionNodes = ewRadar:getConnectionNodes()
|
|
local firstRadar = nil
|
|
local radars = ewRadar:getRadars()
|
|
|
|
--get the first existing radar to prevent issues in calculating the distance later on:
|
|
for i = 1, #radars do
|
|
if radars[i]:isExist() then
|
|
firstRadar = radars[i]
|
|
break
|
|
end
|
|
|
|
end
|
|
local numDamagedConnectionNodes = 0
|
|
|
|
|
|
for j = 1, #connectionNodes do
|
|
local connectionNode = connectionNodes[j]
|
|
if connectionNode:isExist() == false then
|
|
numDamagedConnectionNodes = numDamagedConnectionNodes + 1
|
|
end
|
|
end
|
|
local intactConnectionNodes = numConnectionNodes - numDamagedConnectionNodes
|
|
|
|
local powerSources = ewRadar:getPowerSources()
|
|
local numDamagedPowerSources = 0
|
|
for j = 1, #powerSources do
|
|
local powerSource = powerSources[j]
|
|
if powerSource:isExist() == false then
|
|
numDamagedPowerSources = numDamagedPowerSources + 1
|
|
end
|
|
end
|
|
local intactPowerSources = numPowerSources - numDamagedPowerSources
|
|
|
|
local detectedTargets = ewRadar:getDetectedTargets()
|
|
local samSitesInCoveredArea = ewRadar:getSAMSitesInCoveredArea()
|
|
|
|
local unitName = "DESTROYED"
|
|
|
|
if ewRadar:getDCSRepresentation():isExist() then
|
|
unitName = ewRadar:getDCSName()
|
|
end
|
|
|
|
env.info("UNIT: "..unitName.." | TYPE: "..ewRadar:getNatoName())
|
|
env.info("ACTIVE: "..tostring(isActive).."| DETECTED TARGETS: "..#detectedTargets.." | DEFENDING HARM: "..tostring(ewRadar:isDefendingHARM()))
|
|
if numConnectionNodes > 0 then
|
|
env.info("CONNECTION NODES: "..numConnectionNodes.." | DAMAGED: "..numDamagedConnectionNodes.." | INTACT: "..intactConnectionNodes)
|
|
else
|
|
env.info("NO CONNECTION NODES SET")
|
|
end
|
|
if numPowerSources > 0 then
|
|
env.info("POWER SOURCES : "..numPowerSources.." | DAMAGED:"..numDamagedPowerSources.." | INTACT: "..intactPowerSources)
|
|
else
|
|
env.info("NO POWER SOURCES SET")
|
|
end
|
|
|
|
env.info("SAM SITES IN COVERED AREA: "..#samSitesInCoveredArea)
|
|
for j = 1, #samSitesInCoveredArea do
|
|
local samSiteCovered = samSitesInCoveredArea[j]
|
|
env.info(samSiteCovered:getDCSName())
|
|
end
|
|
|
|
for j = 1, #detectedTargets do
|
|
local contact = detectedTargets[j]
|
|
if firstRadar ~= nil and firstRadar:isExist() then
|
|
local distance = mist.utils.round(mist.utils.metersToNM(ewRadar:getDistanceInMetersToContact(firstRadar:getDCSRepresentation(), contact:getPosition().p)), 2)
|
|
env.info("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | DISTANCE NM: "..distance)
|
|
end
|
|
end
|
|
|
|
env.info("---------------------------------------------------")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
function SkynetIADS:printDetailedSAMSiteStatus()
|
|
local samSites = self:getSAMSites()
|
|
|
|
env.info("------------------------------------------ SAM STATUS: "..self:getCoalitionString().." -------------------------------")
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
local numConnectionNodes = #samSite:getConnectionNodes()
|
|
local numPowerSources = #samSite:getPowerSources()
|
|
local isAutonomous = samSite:getAutonomousState()
|
|
local isActive = samSite:isActive()
|
|
|
|
local connectionNodes = samSite:getConnectionNodes()
|
|
local firstRadar = samSite:getRadars()[1]
|
|
local numDamagedConnectionNodes = 0
|
|
for j = 1, #connectionNodes do
|
|
local connectionNode = connectionNodes[j]
|
|
if connectionNode:isExist() == false then
|
|
numDamagedConnectionNodes = numDamagedConnectionNodes + 1
|
|
end
|
|
end
|
|
local intactConnectionNodes = numConnectionNodes - numDamagedConnectionNodes
|
|
|
|
local powerSources = samSite:getPowerSources()
|
|
local numDamagedPowerSources = 0
|
|
for j = 1, #powerSources do
|
|
local powerSource = powerSources[j]
|
|
if powerSource:isExist() == false then
|
|
numDamagedPowerSources = numDamagedPowerSources + 1
|
|
end
|
|
end
|
|
local intactPowerSources = numPowerSources - numDamagedPowerSources
|
|
|
|
local detectedTargets = samSite:getDetectedTargets()
|
|
|
|
local samSitesInCoveredArea = samSite:getSAMSitesInCoveredArea()
|
|
|
|
env.info("GROUP: "..samSite:getDCSName().." | TYPE: "..samSite:getNatoName())
|
|
env.info("ACTIVE: "..tostring(isActive).." | AUTONOMOUS: "..tostring(isAutonomous).." | IS ACTING AS EW: "..tostring(samSite:getActAsEW()).." | DETECTED TARGETS: "..#detectedTargets.." | DEFENDING HARM: "..tostring(samSite:isDefendingHARM()).." | MISSILES IN FLIGHT:"..tostring(samSite:getNumberOfMissilesInFlight()))
|
|
|
|
if numConnectionNodes > 0 then
|
|
env.info("CONNECTION NODES: "..numConnectionNodes.." | DAMAGED: "..numDamagedConnectionNodes.." | INTACT: "..intactConnectionNodes)
|
|
else
|
|
env.info("NO CONNECTION NODES SET")
|
|
end
|
|
if numPowerSources > 0 then
|
|
env.info("POWER SOURCES : "..numPowerSources.." | DAMAGED:"..numDamagedPowerSources.." | INTACT: "..intactPowerSources)
|
|
else
|
|
env.info("NO POWER SOURCES SET")
|
|
end
|
|
|
|
env.info("SAM SITES IN COVERED AREA: "..#samSitesInCoveredArea)
|
|
for j = 1, #samSitesInCoveredArea do
|
|
local samSiteCovered = samSitesInCoveredArea[j]
|
|
env.info(samSiteCovered:getDCSName())
|
|
end
|
|
|
|
for j = 1, #detectedTargets do
|
|
local contact = detectedTargets[j]
|
|
if firstRadar ~= nil and firstRadar:isExist() then
|
|
local distance = mist.utils.round(mist.utils.metersToNM(samSite:getDistanceInMetersToContact(firstRadar:getDCSRepresentation(), contact:getPosition().p)), 2)
|
|
env.info("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | DISTANCE NM: "..distance)
|
|
end
|
|
end
|
|
|
|
env.info("---------------------------------------------------")
|
|
end
|
|
end
|
|
|
|
function SkynetIADS:printSystemStatus()
|
|
|
|
if self:getDebugSettings().IADSStatus or self:getDebugSettings().contacts then
|
|
local coalitionStr = self:getCoalitionString()
|
|
self:printOutput("---- IADS: "..coalitionStr.." ------")
|
|
end
|
|
|
|
if self:getDebugSettings().IADSStatus then
|
|
|
|
local numComCenters = #self.commandCenters
|
|
local numIntactComCenters = 0
|
|
local numDestroyedComCenters = 0
|
|
local numComCentersNoPower = 0
|
|
local numComCentersServingIADS = 0
|
|
for i = 1, #self.commandCenters do
|
|
local commandCenter = self.commandCenters[i]
|
|
if commandCenter:hasWorkingPowerSource() == false then
|
|
numComCentersNoPower = numComCentersNoPower + 1
|
|
end
|
|
if commandCenter:isDestroyed() == false then
|
|
numIntactComCenters = numIntactComCenters + 1
|
|
end
|
|
if commandCenter:isDestroyed() == false and commandCenter:hasWorkingPowerSource() then
|
|
numComCentersServingIADS = numComCentersServingIADS + 1
|
|
end
|
|
end
|
|
|
|
numDestroyedComCenters = numComCenters - numIntactComCenters
|
|
|
|
|
|
self:printOutput("COMMAND CENTERS: Serving IADS: "..numComCentersServingIADS.." | Total: "..numComCenters.." | Intact: "..numIntactComCenters.." | Destroyed: "..numDestroyedComCenters.." | NoPower: "..numComCentersNoPower)
|
|
|
|
local ewNoPower = 0
|
|
local ewTotal = #self:getEarlyWarningRadars()
|
|
local ewNoConnectionNode = 0
|
|
local ewActive = 0
|
|
local ewRadarsInactive = 0
|
|
|
|
for i = 1, #self.earlyWarningRadars do
|
|
local ewRadar = self.earlyWarningRadars[i]
|
|
if ewRadar:hasWorkingPowerSource() == false then
|
|
ewNoPower = ewNoPower + 1
|
|
end
|
|
if ewRadar:hasActiveConnectionNode() == false then
|
|
ewNoConnectionNode = ewNoConnectionNode + 1
|
|
end
|
|
if ewRadar:isActive() then
|
|
ewActive = ewActive + 1
|
|
end
|
|
end
|
|
|
|
ewRadarsInactive = ewTotal - ewActive
|
|
local numEWRadarsDestroyed = #self:getDestroyedEarlyWarningRadars()
|
|
self:printOutput("EW: "..ewTotal.." | Act: "..ewActive.." | Inact: "..ewRadarsInactive.." | Destroyed: "..numEWRadarsDestroyed.." | NoPowr: "..ewNoPower.." | NoCon: "..ewNoConnectionNode)
|
|
|
|
local samSitesInactive = 0
|
|
local samSitesActive = 0
|
|
local samSitesTotal = #self:getSAMSites()
|
|
local samSitesNoPower = 0
|
|
local samSitesNoConnectionNode = 0
|
|
local samSitesOutOfAmmo = 0
|
|
local samSiteAutonomous = 0
|
|
local samSiteRadarDestroyed = 0
|
|
for i = 1, #self.samSites do
|
|
local samSite = self.samSites[i]
|
|
if samSite:hasWorkingPowerSource() == false then
|
|
samSitesNoPower = samSitesNoPower + 1
|
|
end
|
|
if samSite:hasActiveConnectionNode() == false then
|
|
samSitesNoConnectionNode = samSitesNoConnectionNode + 1
|
|
end
|
|
if samSite:isActive() then
|
|
samSitesActive = samSitesActive + 1
|
|
end
|
|
if samSite:hasRemainingAmmo() == false then
|
|
samSitesOutOfAmmo = samSitesOutOfAmmo + 1
|
|
end
|
|
if samSite:getAutonomousState() == true then
|
|
samSiteAutonomous = samSiteAutonomous + 1
|
|
end
|
|
if samSite:hasWorkingRadar() == false then
|
|
samSiteRadarDestroyed = samSiteRadarDestroyed + 1
|
|
end
|
|
end
|
|
|
|
samSitesInactive = samSitesTotal - samSitesActive
|
|
self:printOutput("SAM: "..samSitesTotal.." | Act: "..samSitesActive.." | Inact: "..samSitesInactive.." | Autonm: "..samSiteAutonomous.." | Raddest: "..samSiteRadarDestroyed.." | NoPowr: "..samSitesNoPower.." | NoCon: "..samSitesNoConnectionNode.." | NoAmmo: "..samSitesOutOfAmmo)
|
|
end
|
|
if self:getDebugSettings().contacts then
|
|
for i = 1, #self.contacts do
|
|
local contact = self.contacts[i]
|
|
self:printOutput("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | GS: "..tostring(contact:getGroundSpeedInKnots()).." | LAST SEEN: "..contact:getAge())
|
|
end
|
|
end
|
|
|
|
if self:getDebugSettings().earlyWarningRadarStatusEnvOutput then
|
|
self:printDetailedEarlyWarningRadarStatus()
|
|
end
|
|
|
|
if self:getDebugSettings().samSiteStatusEnvOutput then
|
|
self:printDetailedSAMSiteStatus()
|
|
end
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetMooseA2ADispatcherConnector = {}
|
|
|
|
function SkynetMooseA2ADispatcherConnector:create(iads)
|
|
local instance = {}
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.iadsCollection = {}
|
|
instance.mooseGroups = {}
|
|
instance.ewRadarGroupNames = {}
|
|
instance.samSiteGroupNames = {}
|
|
table.insert(instance.iadsCollection, iads)
|
|
return instance
|
|
end
|
|
|
|
function SkynetMooseA2ADispatcherConnector:addIADS(iads)
|
|
table.insert(self.iadsCollection, iads)
|
|
end
|
|
|
|
function SkynetMooseA2ADispatcherConnector:addMooseSetGroup(mooseSetGroup)
|
|
table.insert(self.mooseGroups, mooseSetGroup)
|
|
self:update()
|
|
end
|
|
|
|
function SkynetMooseA2ADispatcherConnector:getEarlyWarningRadarGroupNames()
|
|
self.ewRadarGroupNames = {}
|
|
for i = 1, #self.iadsCollection do
|
|
local ewRadars = self.iadsCollection[i]:getUsableEarlyWarningRadars()
|
|
for j = 1, #ewRadars do
|
|
local ewRadar = ewRadars[j]
|
|
table.insert(self.ewRadarGroupNames, ewRadar:getDCSRepresentation():getGroup():getName())
|
|
end
|
|
end
|
|
return self.ewRadarGroupNames
|
|
end
|
|
|
|
function SkynetMooseA2ADispatcherConnector:getSAMSiteGroupNames()
|
|
self.samSiteGroupNames = {}
|
|
for i = 1, #self.iadsCollection do
|
|
local samSites = self.iadsCollection[i]:getUsableSAMSites()
|
|
for j = 1, #samSites do
|
|
local samSite = samSites[j]
|
|
table.insert(self.samSiteGroupNames, samSite:getDCSName())
|
|
end
|
|
end
|
|
return self.samSiteGroupNames
|
|
end
|
|
|
|
function SkynetMooseA2ADispatcherConnector:update()
|
|
|
|
--mooseGroup elements are type of:
|
|
--https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Set.html##(SET_GROUP)
|
|
|
|
--remove previously set group names:
|
|
for i = 1, #self.mooseGroups do
|
|
local mooseGroup = self.mooseGroups[i]
|
|
mooseGroup:RemoveGroupsByName(self.ewRadarGroupNames)
|
|
mooseGroup:RemoveGroupsByName(self.samSiteGroupNames)
|
|
end
|
|
|
|
--add group names of IADS radars that are currently usable by the IADS:
|
|
for i = 1, #self.mooseGroups do
|
|
local mooseGroup = self.mooseGroups[i]
|
|
mooseGroup:AddGroupsByName(self:getEarlyWarningRadarGroupNames())
|
|
mooseGroup:AddGroupsByName(self:getSAMSiteGroupNames())
|
|
end
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
|
|
SkynetIADSTableDelegator = {}
|
|
|
|
function SkynetIADSTableDelegator:create()
|
|
local instance = {}
|
|
local forwarder = {}
|
|
forwarder.__index = function(tbl, name)
|
|
tbl[name] = function(self, ...)
|
|
for i = 1, #self do
|
|
self[i][name](self[i], ...)
|
|
end
|
|
return self
|
|
end
|
|
return tbl[name]
|
|
end
|
|
setmetatable(instance, forwarder)
|
|
instance.__index = forwarder
|
|
return instance
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSAbstractDCSObjectWrapper = {}
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:create(dcsObject)
|
|
local instance = {}
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.dcsObject = dcsObject
|
|
if dcsObject and dcsObject:isExist() and getmetatable(dcsObject) == Unit then
|
|
--we store inital life here, because getLife0() returs a value that is lower that getLife() when no damage has happened...
|
|
instance.initialLife = dcsObject:getLife()
|
|
end
|
|
return instance
|
|
end
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:getName()
|
|
return self.dcsObject:getName()
|
|
end
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:getTypeName()
|
|
return self.dcsObject:getTypeName()
|
|
end
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:getPosition()
|
|
return self.dcsObject:getPosition()
|
|
end
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:isExist()
|
|
if self.dcsObject then
|
|
return self.dcsObject:isExist()
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:getLifePercentage()
|
|
if self.dcsObject and self.dcsObject:isExist() then
|
|
return self.dcsObject:getLife() / self.initialLife * 100
|
|
else
|
|
return 0
|
|
end
|
|
|
|
end
|
|
|
|
function SkynetIADSAbstractDCSObjectWrapper:getDCSRepresentation()
|
|
return self.dcsObject
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSAbstractElement = {}
|
|
|
|
function SkynetIADSAbstractElement:create(dcsRepresentation, iads)
|
|
local instance = {}
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.connectionNodes = {}
|
|
instance.powerSources = {}
|
|
instance.iads = iads
|
|
instance.natoName = "UNKNOWN"
|
|
instance.dcsName = ""
|
|
instance:setDCSRepresentation(dcsRepresentation)
|
|
world.addEventHandler(instance)
|
|
return instance
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:removeEventHandlers()
|
|
world.removeEventHandler(self)
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:cleanUp()
|
|
self:removeEventHandlers()
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:isDestroyed()
|
|
return self:getDCSRepresentation():isExist() == false
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:addPowerSource(powerSource)
|
|
table.insert(self.powerSources, powerSource)
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:getPowerSources()
|
|
return self.powerSources
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:addConnectionNode(connectionNode)
|
|
table.insert(self.connectionNodes, connectionNode)
|
|
self.iads:updateAutonomousStatesOfSAMSites()
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:getConnectionNodes()
|
|
return self.connectionNodes
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:hasActiveConnectionNode()
|
|
local connectionNode = self:genericCheckOneObjectIsAlive(self.connectionNodes)
|
|
if connectionNode == false and self.iads:getDebugSettings().samNoConnection then
|
|
self.iads:printOutput(self:getDescription().." no connection to Command Center")
|
|
end
|
|
return connectionNode
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:hasWorkingPowerSource()
|
|
local power = self:genericCheckOneObjectIsAlive(self.powerSources)
|
|
if power == false and self.iads:getDebugSettings().hasNoPower then
|
|
self.iads:printOutput(self:getDescription().." has no power")
|
|
end
|
|
return power
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:getDCSName()
|
|
return self.dcsName
|
|
end
|
|
|
|
-- generic function to theck if power plants, command centers, connection nodes are still alive
|
|
function SkynetIADSAbstractElement:genericCheckOneObjectIsAlive(objects)
|
|
local isAlive = (#objects == 0)
|
|
for i = 1, #objects do
|
|
local object = objects[i]
|
|
--if we find one object that is not fully destroyed we assume the IADS is still working
|
|
if object:isExist() then
|
|
isAlive = true
|
|
break
|
|
end
|
|
end
|
|
return isAlive
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:setDCSRepresentation(representation)
|
|
self.dcsRepresentation = representation
|
|
if self.dcsRepresentation then
|
|
self.dcsName = self:getDCSRepresentation():getName()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:getDCSRepresentation()
|
|
return self.dcsRepresentation
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:getNatoName()
|
|
return self.natoName
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:getDescription()
|
|
return "IADS ELEMENT: "..self:getDCSName().." | Type : "..tostring(self:getNatoName())
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:onEvent(event)
|
|
--if a unit is destroyed we check to see if its a power plant powering the unit or a connection node
|
|
if event.id == world.event.S_EVENT_DEAD then
|
|
if self:hasWorkingPowerSource() == false or self:isDestroyed() then
|
|
self:goDark()
|
|
self.iads:updateAutonomousStatesOfSAMSites(event.initiator)
|
|
end
|
|
if self:hasActiveConnectionNode() == false then
|
|
self:goAutonomous()
|
|
self.iads:updateAutonomousStatesOfSAMSites(event.initiator)
|
|
end
|
|
end
|
|
if event.id == world.event.S_EVENT_SHOT then
|
|
self:weaponFired(event)
|
|
end
|
|
end
|
|
|
|
--placeholder method, can be implemented by subclasses
|
|
function SkynetIADSAbstractElement:weaponFired(event)
|
|
|
|
end
|
|
|
|
--placeholder method, can be implemented by subclasses
|
|
function SkynetIADSAbstractElement:goDark()
|
|
|
|
end
|
|
|
|
--placeholder method, can be implemented by subclasses
|
|
function SkynetIADSAbstractElement:goAutonomous()
|
|
|
|
end
|
|
|
|
-- helper code for class inheritance
|
|
function inheritsFrom( baseClass )
|
|
|
|
local new_class = {}
|
|
local class_mt = { __index = new_class }
|
|
|
|
function new_class:create()
|
|
local newinst = {}
|
|
setmetatable( newinst, class_mt )
|
|
return newinst
|
|
end
|
|
|
|
if nil ~= baseClass then
|
|
setmetatable( new_class, { __index = baseClass } )
|
|
end
|
|
|
|
-- Implementation of additional OO properties starts here --
|
|
|
|
-- Return the class object of the instance
|
|
function new_class:class()
|
|
return new_class
|
|
end
|
|
|
|
-- Return the super class object of the instance
|
|
function new_class:superClass()
|
|
return baseClass
|
|
end
|
|
|
|
-- Return true if the caller is an instance of theClass
|
|
function new_class:isa( theClass )
|
|
local b_isa = false
|
|
|
|
local cur_class = new_class
|
|
|
|
while ( nil ~= cur_class ) and ( false == b_isa ) do
|
|
if cur_class == theClass then
|
|
b_isa = true
|
|
else
|
|
cur_class = cur_class:superClass()
|
|
end
|
|
end
|
|
|
|
return b_isa
|
|
end
|
|
|
|
return new_class
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSAbstractRadarElement = {}
|
|
SkynetIADSAbstractRadarElement = inheritsFrom(SkynetIADSAbstractElement)
|
|
|
|
SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI = 1
|
|
SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK = 2
|
|
|
|
SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE = 1
|
|
SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE = 2
|
|
|
|
function SkynetIADSAbstractRadarElement:create(dcsElementWithRadar, iads)
|
|
local instance = self:superClass():create(dcsElementWithRadar, iads)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.aiState = false
|
|
instance.harmScanID = nil
|
|
instance.harmSilenceID = nil
|
|
instance.lastJammerUpdate = 0
|
|
instance.objectsIdentifiedAsHarms = {}
|
|
instance.objectsIdentifiedAsHarmsMaxTargetAge = 60
|
|
instance.launchers = {}
|
|
instance.trackingRadars = {}
|
|
instance.searchRadars = {}
|
|
instance.samSitesInCoveredArea = {}
|
|
instance.missilesInFlight = {}
|
|
instance.pointDefences = {}
|
|
instance.ingnoreHARMSWhilePointDefencesHaveAmmo = false
|
|
instance.autonomousBehaviour = SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI
|
|
instance.goLiveRange = SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE
|
|
instance.isAutonomous = false
|
|
instance.harmDetectionChance = 0
|
|
instance.minHarmShutdownTime = 0
|
|
instance.maxHarmShutDownTime = 0
|
|
instance.minHarmPresetShutdownTime = 30
|
|
instance.maxHarmPresetShutdownTime = 180
|
|
instance.firingRangePercent = 100
|
|
instance.actAsEW = false
|
|
instance.cachedTargets = {}
|
|
instance.cachedTargetsMaxAge = 1
|
|
instance.cachedTargetsCurrentAge = 0
|
|
instance.goLiveTime = 0
|
|
-- 5 seconds seems to be a good value for the sam site to find the target with its organic radar
|
|
instance.noCacheActiveForSecondsAfterGoLive = 5
|
|
return instance
|
|
end
|
|
|
|
--TODO: this method could be updated to only return Radar weapons fired, this way a SAM firing an IR weapon could go dark faster in the goDark() method
|
|
function SkynetIADSAbstractRadarElement:weaponFired(event)
|
|
if event.id == world.event.S_EVENT_SHOT then
|
|
local weapon = event.weapon
|
|
local launcherFired = event.initiator
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
if launcher:getDCSRepresentation() == launcherFired then
|
|
table.insert(self.missilesInFlight, weapon)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setCachedTargetsMaxAge(maxAge)
|
|
self.cachedTargetsMaxAge = maxAge
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:cleanUp()
|
|
for i = 1, #self.pointDefences do
|
|
local pointDefence = self.pointDefences[i]
|
|
pointDefence:cleanUp()
|
|
end
|
|
mist.removeFunction(self.harmScanID)
|
|
mist.removeFunction(self.harmSilenceID)
|
|
--call method from super class
|
|
self:removeEventHandlers()
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:addPointDefence(pointDefence)
|
|
table.insert(self.pointDefences, pointDefence)
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getPointDefences()
|
|
return self.pointDefences
|
|
end
|
|
|
|
|
|
function SkynetIADSAbstractRadarElement:updateSAMSitesInCoveredArea()
|
|
local samSites = self.iads:getUsableSAMSites()
|
|
self.samSitesInCoveredArea = {}
|
|
for i = 1, #samSites do
|
|
local samSite = samSites[i]
|
|
if samSite:isInRadarDetectionRangeOf(self) and samSite ~= self then
|
|
table.insert(self.samSitesInCoveredArea, samSite)
|
|
end
|
|
end
|
|
return self.samSitesInCoveredArea
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getSAMSitesInCoveredArea()
|
|
return self.samSitesInCoveredArea
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:pointDefencesHaveRemainingAmmo(minNumberOfMissiles)
|
|
local remainingMissiles = 0
|
|
for i = 1, #self.pointDefences do
|
|
local pointDefence = self.pointDefences[i]
|
|
remainingMissiles = remainingMissiles + pointDefence:getRemainingNumberOfMissiles()
|
|
end
|
|
local returnValue = false
|
|
if ( remainingMissiles > 0 and remainingMissiles >= minNumberOfMissiles ) then
|
|
returnValue = true
|
|
end
|
|
return returnValue
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:pointDefencesHaveEnoughLaunchers(minNumberOfLaunchers)
|
|
local numOfLaunchers = 0
|
|
for i = 1, #self.pointDefences do
|
|
local pointDefence = self.pointDefences[i]
|
|
numOfLaunchers = numOfLaunchers + #pointDefence:getLaunchers()
|
|
end
|
|
local returnValue = false
|
|
if ( numOfLaunchers > 0 and numOfLaunchers >= minNumberOfLaunchers ) then
|
|
returnValue = true
|
|
end
|
|
return returnValue
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:setIgnoreHARMSWhilePointDefencesHaveAmmo(state)
|
|
if state == true or state == false then
|
|
self.ingnoreHARMSWhilePointDefencesHaveAmmo = state
|
|
end
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:hasMissilesInFlight()
|
|
return #self.missilesInFlight > 0
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getNumberOfMissilesInFlight()
|
|
return #self.missilesInFlight
|
|
end
|
|
|
|
-- DCS does not send an event, when a missile is destroyed, so this method needs to be polled so that the missiles in flight are current, polling is done in the HARM Search call: evaluateIfTargetsContainHARMs
|
|
function SkynetIADSAbstractRadarElement:updateMissilesInFlight()
|
|
local missilesInFlight = {}
|
|
for i = 1, #self.missilesInFlight do
|
|
local missile = self.missilesInFlight[i]
|
|
if missile:isExist() then
|
|
table.insert(missilesInFlight, missile)
|
|
end
|
|
end
|
|
self.missilesInFlight = missilesInFlight
|
|
self:goDarkIfOutOfAmmo()
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:goDarkIfOutOfAmmo()
|
|
if self:hasRemainingAmmo() == false and self:getActAsEW() == false then
|
|
self:goDark()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getActAsEW()
|
|
return self.actAsEW
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setActAsEW(ewState)
|
|
if ewState == true or ewState == false then
|
|
self.actAsEW = ewState
|
|
end
|
|
if self.actAsEW == true then
|
|
self:goLive()
|
|
else
|
|
self:goDark()
|
|
end
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getUnitsToAnalyse()
|
|
local units = {}
|
|
table.insert(units, self:getDCSRepresentation())
|
|
if getmetatable(self:getDCSRepresentation()) == Group then
|
|
units = self:getDCSRepresentation():getUnits()
|
|
end
|
|
return units
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getRemainingNumberOfMissiles()
|
|
local remainingNumberOfMissiles = 0
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
remainingNumberOfMissiles = remainingNumberOfMissiles + launcher:getRemainingNumberOfMissiles()
|
|
end
|
|
return remainingNumberOfMissiles
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getInitialNumberOfMissiles()
|
|
local initalNumberOfMissiles = 0
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
initalNumberOfMissiles = launcher:getInitialNumberOfMissiles() + initalNumberOfMissiles
|
|
end
|
|
return initalNumberOfMissiles
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getRemainingNumberOfShells()
|
|
local remainingNumberOfShells = 0
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
remainingNumberOfShells = remainingNumberOfShells + launcher:getRemainingNumberOfShells()
|
|
end
|
|
return remainingNumberOfShells
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getInitialNumberOfShells()
|
|
local initialNumberOfShells = 0
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
initialNumberOfShells = initialNumberOfShells + launcher:getInitialNumberOfShells()
|
|
end
|
|
return initialNumberOfShells
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:hasRemainingAmmo()
|
|
--the launcher check is due to ew radars they have no launcher and no ammo and therefore are never out of ammo
|
|
return ( #self.launchers == 0 ) or ((self:getRemainingNumberOfMissiles() > 0 ) or ( self:getRemainingNumberOfShells() > 0 ) )
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getHARMDetectionChance()
|
|
return self.harmDetectionChance
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setHARMDetectionChance(chance)
|
|
self.harmDetectionChance = chance
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setupElements()
|
|
local numUnits = #self:getUnitsToAnalyse()
|
|
for typeName, dataType in pairs(SkynetIADS.database) do
|
|
local hasSearchRadar = false
|
|
local hasTrackingRadar = false
|
|
local hasLauncher = false
|
|
self.searchRadars = {}
|
|
self.trackingRadars = {}
|
|
self.launchers = {}
|
|
for entry, unitData in pairs(dataType) do
|
|
if entry == 'searchRadar' then
|
|
self:analyseAndAddUnit(SkynetIADSSAMSearchRadar, self.searchRadars, unitData)
|
|
hasSearchRadar = true
|
|
end
|
|
if entry == 'launchers' then
|
|
self:analyseAndAddUnit(SkynetIADSSAMLauncher, self.launchers, unitData)
|
|
hasLauncher = true
|
|
end
|
|
if entry == 'trackingRadar' then
|
|
self:analyseAndAddUnit(SkynetIADSSAMTrackingRadar, self.trackingRadars, unitData)
|
|
hasTrackingRadar = true
|
|
end
|
|
end
|
|
|
|
local numElementsCreated = #self.searchRadars + #self.trackingRadars + #self.launchers
|
|
--this check ensures a unit or group has all required elements for the specific sam or ew type:
|
|
if (hasLauncher and hasSearchRadar and hasTrackingRadar and #self.launchers > 0 and #self.searchRadars > 0 and #self.trackingRadars > 0 )
|
|
or (hasSearchRadar and hasLauncher and #self.searchRadars > 0 and #self.launchers > 0)
|
|
or (hasSearchRadar and hasLauncher == false and hasTrackingRadar == false and #self.searchRadars > 0 and numUnits == 1) then
|
|
local harmDetection = dataType['harm_detection_chance']
|
|
if harmDetection then
|
|
self.harmDetectionChance = harmDetection
|
|
end
|
|
local natoName = dataType['name']['NATO']
|
|
--we shorten the SA-XX names and don't return their code names eg goa, gainful..
|
|
local pos = natoName:find(" ")
|
|
local prefix = natoName:sub(1, 2)
|
|
if string.lower(prefix) == 'sa' and pos ~= nil then
|
|
self.natoName = natoName:sub(1, (pos-1))
|
|
else
|
|
self.natoName = natoName
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:analyseAndAddUnit(class, tableToAdd, unitData)
|
|
local units = self:getUnitsToAnalyse()
|
|
for i = 1, #units do
|
|
local unit = units[i]
|
|
local unitTypeName = unit:getTypeName()
|
|
for unitName, unitPerformanceData in pairs(unitData) do
|
|
if unitName == unitTypeName then
|
|
samElement = class:create(unit)
|
|
samElement:setupRangeData()
|
|
table.insert(tableToAdd, samElement)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getController()
|
|
local dcsRepresentation = self:getDCSRepresentation()
|
|
if dcsRepresentation:isExist() then
|
|
return dcsRepresentation:getController()
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getLaunchers()
|
|
return self.launchers
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getSearchRadars()
|
|
return self.searchRadars
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getTrackingRadars()
|
|
return self.trackingRadars
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getRadars()
|
|
local radarUnits = {}
|
|
for i = 1, #self.searchRadars do
|
|
table.insert(radarUnits, self.searchRadars[i])
|
|
end
|
|
for i = 1, #self.trackingRadars do
|
|
table.insert(radarUnits, self.trackingRadars[i])
|
|
end
|
|
return radarUnits
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setGoLiveRangeInPercent(percent)
|
|
if percent ~= nil then
|
|
self.firingRangePercent = percent
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
launcher:setFiringRangePercent(self.firingRangePercent)
|
|
end
|
|
for i = 1, #self.searchRadars do
|
|
local radar = self.searchRadars[i]
|
|
radar:setFiringRangePercent(self.firingRangePercent)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getGoLiveRangeInPercent()
|
|
return self.firingRangePercent
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setEngagementZone(engagementZone)
|
|
if engagementZone == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE then
|
|
self.goLiveRange = engagementZone
|
|
elseif engagementZone == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE then
|
|
self.goLiveRange = engagementZone
|
|
end
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getEngagementZone()
|
|
return self.goLiveRange
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:goLive()
|
|
if ( self.aiState == false and self:hasWorkingPowerSource() and self.harmSilenceID == nil)
|
|
and ( (self.isAutonomous == false) or (self.isAutonomous == true and self.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI ) )
|
|
and (self:hasRemainingAmmo() == true )
|
|
then
|
|
if self:isDestroyed() == false then
|
|
local cont = self:getController()
|
|
cont:setOnOff(true)
|
|
cont:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED)
|
|
cont:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE)
|
|
self.goLiveTime = timer.getTime()
|
|
end
|
|
self.aiState = true
|
|
self:pointDefencesStopActingAsEW()
|
|
if self.iads:getDebugSettings().radarWentLive then
|
|
self.iads:printOutput(self:getDescription().." going live")
|
|
end
|
|
self:scanForHarms()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:pointDefencesStopActingAsEW()
|
|
for i = 1, #self.pointDefences do
|
|
local pointDefence = self.pointDefences[i]
|
|
pointDefence:setActAsEW(false)
|
|
end
|
|
end
|
|
|
|
|
|
function SkynetIADSAbstractRadarElement:noDamageToRadars()
|
|
local radars = self:getRadars()
|
|
for i = 1, #radars do
|
|
local radar = radars[i]
|
|
if radar:getLifePercentage() < 100 then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:goDark()
|
|
if ( self.aiState == true )
|
|
and (self.harmSilenceID ~= nil or ( self.harmSilenceID == nil and #self:getDetectedTargets() == 0 and self:hasMissilesInFlight() == false) or ( self.harmSilenceID == nil and #self:getDetectedTargets() > 0 and self:hasMissilesInFlight() == false and self:hasRemainingAmmo() == false ) )
|
|
and ( self.isAutonomous == false or ( self.isAutonomous == true and self.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK ) )
|
|
then
|
|
if self:isDestroyed() == false then
|
|
local controller = self:getController()
|
|
-- if the SAM site still has ammo we turn off the controller, this prevents rearming, however this way the SAM site is frozen in a red state, on the next actication it will be up and running much faster, therefore it will instantaneously engage targets
|
|
-- also this is a better way to get the HARM to miss the target, if not set to false the HARM often sticks to the target
|
|
if self:hasRemainingAmmo() then
|
|
controller:setOnOff(false)
|
|
--if the SAM is out of ammo we set the state to green, and ROE to weapon hold, this way it will shut down its radar and it can be rearmed
|
|
else
|
|
controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN)
|
|
controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD)
|
|
end
|
|
end
|
|
-- point defence will only go live if the Radar Emitting site it is protecting goes dark and this is due to a it defending against a HARM
|
|
if (self.harmSilenceID ~= nil) then
|
|
self:pointDefencesGoLive()
|
|
end
|
|
self.aiState = false
|
|
self:stopScanningForHARMs()
|
|
if self.iads:getDebugSettings().samWentDark then
|
|
self.iads:printOutput(self:getDescription().." going dark")
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:pointDefencesGoLive()
|
|
for i = 1, #self.pointDefences do
|
|
local pointDefence = self.pointDefences[i]
|
|
pointDefence:setActAsEW(true)
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:isActive()
|
|
return self.aiState
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:isTargetInRange(target)
|
|
|
|
local isSearchRadarInRange = false
|
|
local isTrackingRadarInRange = false
|
|
local isLauncherInRange = false
|
|
|
|
local isSearchRadarInRange = ( #self.searchRadars == 0 )
|
|
for i = 1, #self.searchRadars do
|
|
local searchRadar = self.searchRadars[i]
|
|
if searchRadar:isInRange(target) then
|
|
isSearchRadarInRange = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if self.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE then
|
|
|
|
isLauncherInRange = ( #self.launchers == 0 )
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
if launcher:isInRange(target) then
|
|
isLauncherInRange = true
|
|
break
|
|
end
|
|
end
|
|
|
|
isTrackingRadarInRange = ( #self.trackingRadars == 0 )
|
|
for i = 1, #self.trackingRadars do
|
|
local trackingRadar = self.trackingRadars[i]
|
|
if trackingRadar:isInRange(target) then
|
|
isTrackingRadarInRange = true
|
|
break
|
|
end
|
|
end
|
|
else
|
|
isLauncherInRange = true
|
|
isTrackingRadarInRange = true
|
|
end
|
|
return (isSearchRadarInRange and isTrackingRadarInRange and isLauncherInRange )
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:isInRadarDetectionRangeOf(abstractRadarElement)
|
|
local radars = self:getRadars()
|
|
local abstractRadarElementRadars = abstractRadarElement:getRadars()
|
|
for i = 1, #radars do
|
|
local radar = radars[i]
|
|
for j = 1, #abstractRadarElementRadars do
|
|
local abstractRadarElementRadar = abstractRadarElementRadars[j]
|
|
if abstractRadarElementRadar:isExist() and radar:isExist() then
|
|
local distance = self:getDistanceToUnit(radar:getDCSRepresentation():getPosition().p, abstractRadarElementRadar:getDCSRepresentation():getPosition().p)
|
|
if abstractRadarElementRadar:getMaxRangeFindingTarget() >= distance then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getDistanceToUnit(unitPosA, unitPosB)
|
|
return mist.utils.round(mist.utils.get2DDist(unitPosA, unitPosB, 0))
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:setAutonomousBehaviour(mode)
|
|
if mode ~= nil then
|
|
self.autonomousBehaviour = mode
|
|
end
|
|
return self
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getAutonomousBehaviour()
|
|
return self.autonomousBehaviour
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:resetAutonomousState()
|
|
if self.isAutonomous == true then
|
|
self.isAutonomous = false
|
|
self:goDark()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:goAutonomous()
|
|
if self.isAutonomous == false then
|
|
self.isAutonomous = true
|
|
self:goDark()
|
|
self:goLive()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getAutonomousState()
|
|
return self.isAutonomous
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:hasWorkingRadar()
|
|
local radars = self:getRadars()
|
|
for i = 1, #radars do
|
|
local radar = radars[i]
|
|
if radar:isRadarWorking() == true then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:jam(successProbability)
|
|
if self:isDestroyed() == false then
|
|
local controller = self:getController()
|
|
local probability = math.random(1, 100)
|
|
if self.iads:getDebugSettings().jammerProbability then
|
|
self.iads:printOutput("JAMMER: "..self:getDescription()..": Probability: "..successProbability)
|
|
end
|
|
if successProbability > probability then
|
|
controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD)
|
|
if self.iads:getDebugSettings().jammerProbability then
|
|
self.iads:printOutput("JAMMER: "..self:getDescription()..": jammed, setting to weapon hold")
|
|
end
|
|
else
|
|
controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE)
|
|
if self.iads:getDebugSettings().jammerProbability then
|
|
self.iads:printOutput("Jammer: "..self:getDescription()..": jammed, setting to weapon free")
|
|
end
|
|
end
|
|
self.lastJammerUpdate = timer:getTime()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:scanForHarms()
|
|
self:stopScanningForHARMs()
|
|
self.harmScanID = mist.scheduleFunction(SkynetIADSAbstractRadarElement.evaluateIfTargetsContainHARMs, {self}, 1, 2)
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:isScanningForHARMs()
|
|
return self.harmScanID ~= nil
|
|
end
|
|
|
|
function SkynetIADSAbstractElement:isDefendingHARM()
|
|
return self.harmSilenceID ~= nil
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:stopScanningForHARMs()
|
|
mist.removeFunction(self.harmScanID)
|
|
self.harmScanID = nil
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:goSilentToEvadeHARM(timeToImpact)
|
|
self:finishHarmDefence(self)
|
|
self.objectsIdentifiedAsHarms = {}
|
|
local harmTime = self:getHarmShutDownTime()
|
|
if self.iads:getDebugSettings().harmDefence then
|
|
self.iads:printOutput("HARM DEFENCE: "..self:getDCSName().." shutting down | FOR: "..harmTime.." seconds | TTI: "..timeToImpact)
|
|
end
|
|
self.harmSilenceID = mist.scheduleFunction(SkynetIADSAbstractRadarElement.finishHarmDefence, {self}, timer.getTime() + harmTime, 1)
|
|
self:goDark()
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getHarmShutDownTime()
|
|
local shutDownTime = math.random(self.minHarmShutdownTime, self.maxHarmShutDownTime)
|
|
return shutDownTime
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement.finishHarmDefence(self)
|
|
mist.removeFunction(self.harmSilenceID)
|
|
self.harmSilenceID = nil
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getDetectedTargets()
|
|
if ( timer.getTime() - self.cachedTargetsCurrentAge > self.cachedTargetsMaxAge ) or ( timer.getTime() - self.goLiveTime < self.noCacheActiveForSecondsAfterGoLive ) then
|
|
self.cachedTargets = {}
|
|
self.cachedTargetsCurrentAge = timer.getTime()
|
|
if self:hasWorkingPowerSource() and self:isDestroyed() == false then
|
|
local targets = self:getController():getDetectedTargets(Controller.Detection.RADAR)
|
|
for i = 1, #targets do
|
|
local target = targets[i]
|
|
-- there are cases when a destroyed object is still visible as a target to the radar, don't add it, will cause errors everywhere the dcs object is accessed
|
|
if target.object then
|
|
local iadsTarget = SkynetIADSContact:create(target)
|
|
iadsTarget:refresh()
|
|
if self:isTargetInRange(iadsTarget) then
|
|
table.insert(self.cachedTargets, iadsTarget)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return self.cachedTargets
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getSecondsToImpact(distanceNM, speedKT)
|
|
local tti = 0
|
|
if speedKT > 0 then
|
|
tti = mist.utils.round((distanceNM / speedKT) * 3600, 0)
|
|
if tti < 0 then
|
|
tti = 0
|
|
end
|
|
end
|
|
return tti
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:getDistanceInMetersToContact(radarUnit, point)
|
|
return mist.utils.round(mist.utils.get3DDist(radarUnit:getPosition().p, point), 0)
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:calculateMinimalShutdownTimeInSeconds(timeToImpact)
|
|
return timeToImpact + self.minHarmPresetShutdownTime
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:calculateMaximalShutdownTimeInSeconds(minShutdownTime)
|
|
return minShutdownTime + mist.random(1, self.maxHarmPresetShutdownTime)
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:calculateImpactPoint(target, distanceInMeters)
|
|
-- distance needs to be incremented by a certain value for ip calculation to work, check why presumably due to rounding errors in the previous distance calculation
|
|
return land.getIP(target:getPosition().p, target:getPosition().x, distanceInMeters + 50)
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:shallReactToHARM()
|
|
return self.harmDetectionChance >= math.random(1, 100)
|
|
end
|
|
|
|
-- will only check for missiles, if DCS ads AAA than can engage HARMs then this code must be updated:
|
|
function SkynetIADSAbstractRadarElement:shallIgnoreHARMShutdown()
|
|
local numOfHarms = self:getNumberOfObjectsItentifiedAsHARMS()
|
|
return ( self:pointDefencesHaveRemainingAmmo(numOfHarms) and self:pointDefencesHaveEnoughLaunchers(numOfHarms) and self.ingnoreHARMSWhilePointDefencesHaveAmmo == true)
|
|
end
|
|
|
|
|
|
function SkynetIADSAbstractRadarElement:getNumberOfObjectsItentifiedAsHARMS()
|
|
local numFound = 0
|
|
for unitName, unit in pairs(self.objectsIdentifiedAsHarms) do
|
|
numFound = numFound + 1
|
|
end
|
|
return numFound
|
|
end
|
|
|
|
function SkynetIADSAbstractRadarElement:cleanUpOldObjectsIdentifiedAsHARMS()
|
|
local validObjects = {}
|
|
for unitName, unit in pairs(self.objectsIdentifiedAsHarms) do
|
|
local harm = unit['target']
|
|
if harm:getAge() <= self.objectsIdentifiedAsHarmsMaxTargetAge then
|
|
validObjects[harm:getName()] = {}
|
|
validObjects[harm:getName()]['target'] = harm
|
|
validObjects[harm:getName()]['count'] = unit['count']
|
|
end
|
|
end
|
|
self.objectsIdentifiedAsHarms = validObjects
|
|
|
|
--stop point defences acting as ew (always on), will occur of activated via shallIgnoreHARMShutdown() in evaluateIfTargetsContainHARMs
|
|
if self:getNumberOfObjectsItentifiedAsHARMS() == 0 then
|
|
self:pointDefencesStopActingAsEW()
|
|
end
|
|
end
|
|
|
|
|
|
function SkynetIADSAbstractRadarElement.evaluateIfTargetsContainHARMs(self)
|
|
|
|
--if an emitter dies the SAM site being jammed will revert back to normal operation:
|
|
if self.lastJammerUpdate > 0 and ( timer:getTime() - self.lastJammerUpdate ) > 10 then
|
|
self:jam(0)
|
|
self.lastJammerUpdate = 0
|
|
end
|
|
|
|
--we use the regular interval of this method to update to other states:
|
|
self:updateMissilesInFlight()
|
|
self:cleanUpOldObjectsIdentifiedAsHARMS()
|
|
|
|
|
|
local targets = self:getDetectedTargets()
|
|
for i = 1, #targets do
|
|
local target = targets[i]
|
|
local radars = self:getRadars()
|
|
for j = 1, #radars do
|
|
local radar = radars[j]
|
|
if radar:isExist() == true then
|
|
local distance = self:getDistanceInMetersToContact(radar, target:getPosition().p)
|
|
local impactPoint = self:calculateImpactPoint(target, distance)
|
|
if impactPoint then
|
|
local harmImpactPointDistanceToSAM = self:getDistanceInMetersToContact(radar, impactPoint)
|
|
if harmImpactPointDistanceToSAM <= 100 then
|
|
if self.objectsIdentifiedAsHarms[target:getName()] then
|
|
self.objectsIdentifiedAsHarms[target:getName()]['count'] = self.objectsIdentifiedAsHarms[target:getName()]['count'] + 1
|
|
else
|
|
self.objectsIdentifiedAsHarms[target:getName()] = {}
|
|
self.objectsIdentifiedAsHarms[target:getName()]['target'] = target
|
|
self.objectsIdentifiedAsHarms[target:getName()]['count'] = 1
|
|
end
|
|
local savedTarget = self.objectsIdentifiedAsHarms[target:getName()]['target']
|
|
savedTarget:refresh()
|
|
local numDetections = self.objectsIdentifiedAsHarms[target:getName()]['count']
|
|
local speed = savedTarget:getGroundSpeedInKnots()
|
|
local timeToImpact = self:getSecondsToImpact(mist.utils.metersToNM(distance), speed)
|
|
local shallReactToHarm = self:shallReactToHARM()
|
|
|
|
-- if self:getNumberOfObjectsItentifiedAsHARMS() > 0 then
|
|
-- env.info("detect as HARM: "..self:getDCSName().." "..self:getNumberOfObjectsItentifiedAsHARMS())
|
|
-- end
|
|
|
|
-- we use 2 detection cycles so a random object in the air pointing at the SAM site for a spilt second will not trigger a shutdown. shallReactToHarm adds some salt otherwise the SAM will always shut down 100% of the time.
|
|
if numDetections == 2 and shallReactToHarm then
|
|
if self:shallIgnoreHARMShutdown() == false then
|
|
self.minHarmShutdownTime = self:calculateMinimalShutdownTimeInSeconds(timeToImpact)
|
|
self.maxHarmShutDownTime = self:calculateMaximalShutdownTimeInSeconds(self.minHarmShutdownTime)
|
|
self:goSilentToEvadeHARM(timeToImpact)
|
|
else
|
|
self:pointDefencesGoLive()
|
|
end
|
|
end
|
|
if numDetections == 2 and shallReactToHarm == false then
|
|
if self.iads:getDebugSettings().harmDefence then
|
|
self.iads:printOutput("HARM DEFENCE: "..self:getDCSName().." will not react")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
do
|
|
--this class is currently used for AWACS and Ships, at a latter date a separate class for ships could be created, currently not needed
|
|
SkynetIADSAWACSRadar = {}
|
|
SkynetIADSAWACSRadar = inheritsFrom(SkynetIADSAbstractRadarElement)
|
|
|
|
function SkynetIADSAWACSRadar:create(radarUnit, iads)
|
|
local instance = self:superClass():create(radarUnit, iads)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.lastUpdatePosition = nil
|
|
return instance
|
|
end
|
|
|
|
function SkynetIADSAWACSRadar:setupElements()
|
|
local unit = self:getDCSRepresentation()
|
|
local radar = SkynetIADSSAMSearchRadar:create(unit)
|
|
radar:setupRangeData()
|
|
table.insert(self.searchRadars, radar)
|
|
end
|
|
|
|
function SkynetIADSAWACSRadar:getNatoName()
|
|
return self:getDCSRepresentation():getTypeName()
|
|
end
|
|
|
|
-- AWACs will not scan for HARMS
|
|
function SkynetIADSAWACSRadar:scanForHarms()
|
|
|
|
end
|
|
|
|
function SkynetIADSAWACSRadar:getMaxAllowedMovementForAutonomousUpdateInNM()
|
|
local radarRange = mist.utils.metersToNM(self.searchRadars[1]:getMaxRangeFindingTarget())
|
|
return mist.utils.round(radarRange / 10)
|
|
end
|
|
|
|
function SkynetIADSAWACSRadar:isUpdateOfAutonomousStateOfSAMSitesRequired()
|
|
return self:getDistanceTraveledSinceLastUpdate() > self:getMaxAllowedMovementForAutonomousUpdateInNM()
|
|
end
|
|
|
|
function SkynetIADSAWACSRadar:getDistanceTraveledSinceLastUpdate()
|
|
local currentPosition = nil
|
|
if self.lastUpdatePosition == nil and self:getDCSRepresentation():isExist() then
|
|
self.lastUpdatePosition = self:getDCSRepresentation():getPosition().p
|
|
end
|
|
if self:getDCSRepresentation():isExist() then
|
|
currentPosition = self:getDCSRepresentation():getPosition().p
|
|
end
|
|
return mist.utils.round(mist.utils.metersToNM(self:getDistanceToUnit(self.lastUpdatePosition, currentPosition)))
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
SkynetIADSCommandCenter = {}
|
|
SkynetIADSCommandCenter = inheritsFrom(SkynetIADSAbstractElement)
|
|
|
|
function SkynetIADSCommandCenter:create(commandCenter, iads)
|
|
local instance = self:superClass():create(commandCenter, iads)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.natoName = "Command Center"
|
|
return instance
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSContact = {}
|
|
SkynetIADSContact = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper)
|
|
|
|
function SkynetIADSContact:create(dcsRadarTarget)
|
|
local instance = self:superClass():create(dcsRadarTarget.object)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.firstContactTime = timer.getAbsTime()
|
|
instance.lastTimeSeen = 0
|
|
instance.dcsRadarTarget = dcsRadarTarget
|
|
instance.name = instance.dcsObject:getName()
|
|
instance.typeName = instance.dcsObject:getTypeName()
|
|
instance.position = instance.dcsObject:getPosition()
|
|
instance.numOfTimesRefreshed = 0
|
|
instance.speed = 0
|
|
return instance
|
|
end
|
|
|
|
function SkynetIADSContact:getName()
|
|
return self.name
|
|
end
|
|
|
|
function SkynetIADSContact:getTypeName()
|
|
return self.typeName
|
|
end
|
|
|
|
function SkynetIADSContact:isTypeKnown()
|
|
return self.dcsRadarTarget.type
|
|
end
|
|
|
|
function SkynetIADSContact:isDistanceKnown()
|
|
return self.dcsRadarTarget.distance
|
|
end
|
|
|
|
function SkynetIADSContact:getPosition()
|
|
return self.position
|
|
end
|
|
|
|
function SkynetIADSContact:getGroundSpeedInKnots(decimals)
|
|
if decimals == nil then
|
|
decimals = 2
|
|
end
|
|
return mist.utils.round(self.speed, decimals)
|
|
end
|
|
|
|
function SkynetIADSContact:getDesc()
|
|
if self.dcsObject:isExist() then
|
|
return self.dcsObject:getDesc()
|
|
else
|
|
return {}
|
|
end
|
|
end
|
|
|
|
function SkynetIADSContact:getNumberOfTimesHitByRadar()
|
|
return self.numOfTimesRefreshed
|
|
end
|
|
|
|
function SkynetIADSContact:refresh()
|
|
self.numOfTimesRefreshed = self.numOfTimesRefreshed + 1
|
|
if self.dcsObject and self.dcsObject:isExist() then
|
|
local distance = mist.utils.metersToNM(mist.utils.get2DDist(self.position.p, self.dcsObject:getPosition().p))
|
|
local timeDelta = (timer.getAbsTime() - self.lastTimeSeen)
|
|
if timeDelta > 0 then
|
|
local hours = timeDelta / 3600
|
|
self.speed = (distance / hours)
|
|
end
|
|
self.position = self.dcsObject:getPosition()
|
|
end
|
|
self.lastTimeSeen = timer.getAbsTime()
|
|
end
|
|
|
|
function SkynetIADSContact:getAge()
|
|
return mist.utils.round(timer.getAbsTime() - self.lastTimeSeen)
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
|
|
SkynetIADSEWRadar = {}
|
|
SkynetIADSEWRadar = inheritsFrom(SkynetIADSAbstractRadarElement)
|
|
|
|
function SkynetIADSEWRadar:create(radarUnit, iads)
|
|
local instance = self:superClass():create(radarUnit, iads)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.autonomousBehaviour = SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK
|
|
return instance
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSJammer = {}
|
|
SkynetIADSJammer.__index = SkynetIADSJammer
|
|
|
|
function SkynetIADSJammer:create(emitter, iads)
|
|
local jammer = {}
|
|
setmetatable(jammer, SkynetIADSJammer)
|
|
jammer.radioMenu = nil
|
|
jammer.emitter = emitter
|
|
jammer.jammerTaskID = nil
|
|
jammer.iads = {iads}
|
|
jammer.maximumEffectiveDistanceNM = 200
|
|
--jammer probability settings are stored here, visualisation, see: https://docs.google.com/spreadsheets/d/16rnaU49ZpOczPEsdGJ6nfD0SLPxYLEYKmmo4i2Vfoe0/edit#gid=0
|
|
jammer.jammerTable = {
|
|
['SA-2'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 90 end,
|
|
['canjam'] = true,
|
|
},
|
|
['SA-3'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 80 end,
|
|
['canjam'] = true,
|
|
},
|
|
['SA-6'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 23 end,
|
|
['canjam'] = true,
|
|
},
|
|
['SA-8'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.35 ^ distanceNauticalMiles ) + 30 end,
|
|
['canjam'] = true,
|
|
},
|
|
['SA-10'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.07 ^ (distanceNauticalMiles / 1.13) ) + 5 end,
|
|
['canjam'] = true,
|
|
},
|
|
['SA-11'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.25 ^ distanceNauticalMiles ) + 15 end,
|
|
['canjam'] = true,
|
|
},
|
|
['SA-15'] = {
|
|
['function'] = function(distanceNauticalMiles) return ( 1.15 ^ distanceNauticalMiles ) + 5 end,
|
|
['canjam'] = true,
|
|
},
|
|
}
|
|
return jammer
|
|
end
|
|
|
|
function SkynetIADSJammer:masterArmOn()
|
|
self:masterArmSafe()
|
|
self.jammerTaskID = mist.scheduleFunction(SkynetIADSJammer.runCycle, {self}, 1, 10)
|
|
end
|
|
|
|
function SkynetIADSJammer:addFunction(natoName, jammerFunction)
|
|
self.jammerTable[natoName] = {
|
|
['function'] = jammerFunction,
|
|
['canjam'] = true
|
|
}
|
|
end
|
|
|
|
function SkynetIADSJammer:setMaximumEffectiveDistance(distance)
|
|
self.maximumEffectiveDistanceNM = distance
|
|
end
|
|
|
|
function SkynetIADSJammer:disableFor(natoName)
|
|
self.jammerTable[natoName]['canjam'] = false
|
|
end
|
|
|
|
function SkynetIADSJammer:isKnownRadarEmitter(natoName)
|
|
local isActive = false
|
|
for unitName, unit in pairs(self.jammerTable) do
|
|
if unitName == natoName and unit['canjam'] == true then
|
|
isActive = true
|
|
end
|
|
end
|
|
return isActive
|
|
end
|
|
|
|
function SkynetIADSJammer:addIADS(iads)
|
|
table.insert(self.iads, iads)
|
|
end
|
|
|
|
function SkynetIADSJammer:getSuccessProbability(distanceNauticalMiles, natoName)
|
|
local probability = 0
|
|
local jammerSettings = self.jammerTable[natoName]
|
|
if jammerSettings ~= nil then
|
|
probability = jammerSettings['function'](distanceNauticalMiles)
|
|
end
|
|
return probability
|
|
end
|
|
|
|
function SkynetIADSJammer:getDistanceNMToRadarUnit(radarUnit)
|
|
return mist.utils.metersToNM(mist.utils.get3DDist(self.emitter:getPosition().p, radarUnit:getPosition().p))
|
|
end
|
|
|
|
function SkynetIADSJammer.runCycle(self)
|
|
|
|
if self.emitter:isExist() == false then
|
|
self:masterArmSafe()
|
|
return
|
|
end
|
|
|
|
for i = 1, #self.iads do
|
|
local iads = self.iads[i]
|
|
local samSites = iads:getActiveSAMSites()
|
|
for j = 1, #samSites do
|
|
local samSite = samSites[j]
|
|
local radars = samSite:getRadars()
|
|
local hasLOS = false
|
|
local distance = 0
|
|
local natoName = samSite:getNatoName()
|
|
for l = 1, #radars do
|
|
local radar = radars[l]
|
|
distance = self:getDistanceNMToRadarUnit(radar)
|
|
-- I try to emulate the system as it would work in real life, so a jammer can only jam a SAM site if has line of sight to at least one radar in the group
|
|
if self:isKnownRadarEmitter(natoName) and self:hasLineOfSightToRadar(radar) and distance <= self.maximumEffectiveDistanceNM then
|
|
if iads:getDebugSettings().jammerProbability then
|
|
iads:printOutput("JAMMER: Distance: "..distance)
|
|
end
|
|
samSite:jam(self:getSuccessProbability(distance, natoName))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSJammer:hasLineOfSightToRadar(radar)
|
|
local radarPos = radar:getPosition().p
|
|
--lift the radar 30 meters off the ground, some 3d models are dug in to the ground, creating issues in calculating LOS
|
|
radarPos.y = radarPos.y + 30
|
|
return land.isVisible(radarPos, self.emitter:getPosition().p)
|
|
end
|
|
|
|
function SkynetIADSJammer:masterArmSafe()
|
|
mist.removeFunction(self.jammerTaskID)
|
|
end
|
|
|
|
--TODO: Remove Menu when emitter dies:
|
|
function SkynetIADSJammer:addRadioMenu()
|
|
self.radioMenu = missionCommands.addSubMenu('Jammer: '..self.emitter:getName())
|
|
missionCommands.addCommand('Master Arm On', self.radioMenu, SkynetIADSJammer.updateMasterArm, {self = self, option = 'masterArmOn'})
|
|
missionCommands.addCommand('Master Arm Safe', self.radioMenu, SkynetIADSJammer.updateMasterArm, {self = self, option = 'masterArmSafe'})
|
|
end
|
|
|
|
function SkynetIADSJammer.updateMasterArm(params)
|
|
local option = params.option
|
|
local self = params.self
|
|
if option == 'masterArmOn' then
|
|
self:masterArmOn()
|
|
elseif option == 'masterArmSafe' then
|
|
self:masterArmSafe()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSJammer:removeRadioMenu()
|
|
missionCommands.removeItem(self.radioMenu)
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSSAMSearchRadar = {}
|
|
SkynetIADSSAMSearchRadar = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper)
|
|
|
|
function SkynetIADSSAMSearchRadar:create(unit)
|
|
local instance = self:superClass():create(unit)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.firingRangePercent = 100
|
|
instance.maximumRange = 0
|
|
instance.initialNumberOfMissiles = 0
|
|
instance.remainingNumberOfMissiles = 0
|
|
instance.initialNumberOfShells = 0
|
|
instance.remainingNumberOfShells = 0
|
|
instance.triedSensors = 0
|
|
return instance
|
|
end
|
|
|
|
--override in subclasses to match different datastructure of getSensors()
|
|
function SkynetIADSSAMSearchRadar:setupRangeData()
|
|
if self:isExist() then
|
|
local data = self:getDCSRepresentation():getSensors()
|
|
if data == nil then
|
|
--this is to prevent infinite calls between launcher and search radar
|
|
self.triedSensors = self.triedSensors + 1
|
|
--the SA-13 does not have any sensor data, but is has launcher data, so we use the stuff from the launcher for the radar range.
|
|
SkynetIADSSAMLauncher.setupRangeData(self)
|
|
return
|
|
end
|
|
for i = 1, #data do
|
|
local subEntries = data[i]
|
|
for j = 1, #subEntries do
|
|
local sensorInformation = subEntries[j]
|
|
-- some sam sites have IR and passive EWR detection, we are just interested in the radar data
|
|
-- investigate if upperHemisphere and headOn is ok, I guess it will work for most detection cases
|
|
if sensorInformation.type == Unit.SensorType.RADAR then
|
|
local upperHemisphere = sensorInformation['detectionDistanceAir']['upperHemisphere']['headOn']
|
|
local lowerHemisphere = sensorInformation['detectionDistanceAir']['lowerHemisphere']['headOn']
|
|
self.maximumRange = upperHemisphere
|
|
if lowerHemisphere > upperHemisphere then
|
|
self.maximumRange = lowerHemisphere
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:getMaxRangeFindingTarget()
|
|
return self.maximumRange
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:isRadarWorking()
|
|
-- the ammo check is for the SA-13 which does not return any sensor data:
|
|
return (self:isExist() == true and ( self:getDCSRepresentation():getSensors() ~= nil or self:getDCSRepresentation():getAmmo() ~= nil ) )
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:setFiringRangePercent(percent)
|
|
self.firingRangePercent = percent
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:getDistance(target)
|
|
return mist.utils.get2DDist(target:getPosition().p, self.dcsObject:getPosition().p)
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:getHeight(target)
|
|
local radarElevation = self:getDCSRepresentation():getPosition().p.y
|
|
local targetElevation = target:getPosition().p.y
|
|
return math.abs(targetElevation - radarElevation)
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:isInHorizontalRange(target)
|
|
return (self:getMaxRangeFindingTarget() / 100 * self.firingRangePercent) >= self:getDistance(target)
|
|
end
|
|
|
|
function SkynetIADSSAMSearchRadar:isInRange(target)
|
|
if self:isExist() == false then
|
|
return false
|
|
end
|
|
return self:isInHorizontalRange(target)
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
|
|
SkynetIADSSamSite = {}
|
|
SkynetIADSSamSite = inheritsFrom(SkynetIADSAbstractRadarElement)
|
|
|
|
function SkynetIADSSamSite:create(samGroup, iads)
|
|
local sam = self:superClass():create(samGroup, iads)
|
|
setmetatable(sam, self)
|
|
self.__index = self
|
|
sam.targetsInRange = false
|
|
return sam
|
|
end
|
|
|
|
function SkynetIADSSamSite:isDestroyed()
|
|
local isDestroyed = true
|
|
for i = 1, #self.launchers do
|
|
local launcher = self.launchers[i]
|
|
if launcher:isExist() == true then
|
|
isDestroyed = false
|
|
end
|
|
end
|
|
local radars = self:getRadars()
|
|
for i = 1, #radars do
|
|
local radar = radars[i]
|
|
if radar:isExist() == true then
|
|
isDestroyed = false
|
|
end
|
|
end
|
|
return isDestroyed
|
|
end
|
|
|
|
function SkynetIADSSamSite:targetCycleUpdateStart()
|
|
self.targetsInRange = false
|
|
end
|
|
|
|
function SkynetIADSSamSite:targetCycleUpdateEnd()
|
|
if self.targetsInRange == false and self.actAsEW == false then
|
|
self:goDark()
|
|
end
|
|
end
|
|
|
|
function SkynetIADSSamSite:informOfContact(contact)
|
|
-- we make sure isTargetInRange (expensive call) is only triggered if no previous calls to this method resulted in targets in range
|
|
if self.targetsInRange == false and self:isTargetInRange(contact) then
|
|
self:goLive()
|
|
self.targetsInRange = true
|
|
end
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSSAMTrackingRadar = {}
|
|
SkynetIADSSAMTrackingRadar = inheritsFrom(SkynetIADSSAMSearchRadar)
|
|
|
|
function SkynetIADSSAMTrackingRadar:create(unit)
|
|
local instance = self:superClass():create(unit)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
return instance
|
|
end
|
|
|
|
end
|
|
do
|
|
|
|
SkynetIADSSAMLauncher = {}
|
|
SkynetIADSSAMLauncher = inheritsFrom(SkynetIADSSAMSearchRadar)
|
|
|
|
function SkynetIADSSAMLauncher:create(unit)
|
|
local instance = self:superClass():create(unit)
|
|
setmetatable(instance, self)
|
|
self.__index = self
|
|
instance.maximumFiringAltitude = 0
|
|
return instance
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:setupRangeData()
|
|
self.remainingNumberOfMissiles = 0
|
|
self.remainingNumberOfShells = 0
|
|
if self:isExist() then
|
|
local data = self:getDCSRepresentation():getAmmo()
|
|
local initialNumberOfMissiles = 0
|
|
local initialNumberOfShells = 0
|
|
--data becomes nil, when all missiles are fired
|
|
if data then
|
|
for i = 1, #data do
|
|
local ammo = data[i]
|
|
--we ignore checks on radar guidance types, since we are not interested in how exactly the missile is guided by the SAM site.
|
|
if ammo.desc.category == Weapon.Category.MISSILE then
|
|
--TODO: see what the difference is between Max and Min values, SA-3 has higher Min value than Max?, most likely it has to do with the box parameters supplied by launcher
|
|
--to simplyfy we just use the larger value, sam sites need a few seconds of tracking time to fire, by that time contact has most likely closed in on the SAM site.
|
|
local altMin = ammo.desc.rangeMaxAltMin
|
|
local altMax = ammo.desc.rangeMaxAltMax
|
|
self.maximumRange = altMin
|
|
if altMin < altMax then
|
|
self.maximumRange = altMax
|
|
end
|
|
self.maximumFiringAltitude = ammo.desc.altMax
|
|
self.remainingNumberOfMissiles = self.remainingNumberOfMissiles + ammo.count
|
|
initialNumberOfMissiles = self.remainingNumberOfMissiles
|
|
end
|
|
if ammo.desc.category == Weapon.Category.SHELL then
|
|
self.remainingNumberOfShells = self.remainingNumberOfShells + ammo.count
|
|
initialNumberOfShells = self.remainingNumberOfShells
|
|
end
|
|
--if no distance was detected we run the code for the search radar. This happens when all in one units are passed like the shilka
|
|
if self.maximumRange == 0 then
|
|
--this is to prevent infinite calls between launcher and search radar
|
|
if self.triedSensors <= 2 then
|
|
SkynetIADSSAMSearchRadar.setupRangeData(self)
|
|
end
|
|
end
|
|
end
|
|
-- conditions here are because setupRangeData() is called multiple times in the code to update ammo status, we set initial values only the first time the method is called
|
|
if self.initialNumberOfMissiles == 0 then
|
|
self.initialNumberOfMissiles = initialNumberOfMissiles
|
|
end
|
|
if self.initialNumberOfShells == 0 then
|
|
self.initialNumberOfShells = initialNumberOfShells
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:getInitialNumberOfShells()
|
|
return self.initialNumberOfShells
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:getRemainingNumberOfShells()
|
|
self:setupRangeData()
|
|
return self.remainingNumberOfShells
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:getInitialNumberOfMissiles()
|
|
return self.initialNumberOfMissiles
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:getRemainingNumberOfMissiles()
|
|
self:setupRangeData()
|
|
return self.remainingNumberOfMissiles
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:getRange()
|
|
return self.maximumRange
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:getMaximumFiringAltitude()
|
|
return self.maximumFiringAltitude
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:isWithinFiringHeight(target)
|
|
-- if no max firing height is set (radar quided AAA) then we use the vertical range, bit of a hack but probably ok for AAA
|
|
if self:getMaximumFiringAltitude() > 0 then
|
|
return self:getMaximumFiringAltitude() >= self:getHeight(target)
|
|
else
|
|
return self:getRange() >= self:getHeight(target)
|
|
end
|
|
end
|
|
|
|
function SkynetIADSSAMLauncher:isInRange(target)
|
|
if self:isExist() == false then
|
|
return false
|
|
end
|
|
return self:isWithinFiringHeight(target) and self:isInHorizontalRange(target)
|
|
end
|
|
|
|
end
|
|
|
|
--[[
|
|
SA-2 Launcher:
|
|
{
|
|
count=1,
|
|
desc={
|
|
Nmax=17,
|
|
RCS=0.39669999480247,
|
|
_origin="",
|
|
altMax=25000,
|
|
altMin=100,
|
|
box={
|
|
max={x=4.7303376197815, y=0.84564626216888, z=0.84564626216888},
|
|
min={x=-5.8387970924377, y=-0.84564626216888, z=-0.84564626216888}
|
|
},
|
|
category=1,
|
|
displayName="SA2V755",
|
|
fuseDist=20,
|
|
guidance=4,
|
|
life=2,
|
|
missileCategory=2,
|
|
rangeMaxAltMax=30000,
|
|
rangeMaxAltMin=40000,
|
|
rangeMin=7000,
|
|
typeName="SA2V755",
|
|
warhead={caliber=500, explosiveMass=196, mass=196, type=1}
|
|
}
|
|
}
|
|
}
|
|
--]]
|