Hierarchical IADS Code w/Shoot and Scoot Logic
Posted: Fri Dec 24, 2021 2:56 pm
				
				 I wrote this code to allow scenario designers to build a flexible model of a hierarchical IADS that would include the possibility of clipping the kill chain by striking various echelon command posts.  IADS elements will move periodically, following their emission period.  
 
This first part is intended to be executed on scenario load. It defines the IADS hierarchy and initializes the state table for its components.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
The next bit of code is intended to executed every minute. It manages whether whether the IADS components are setting up, tearing down, moving, or whether they could emit should a worthwhile target come close enough to make it worth lighting up one's organic radars.
 
 
 
 
 
 
 
 
This third part is intended to execute every 15 seconds. It recursively descends the hierarchy tree and determines if a unit component is in a state where it could emit, then adjusts its WCS and EMCON appropriately.
 
 
 
 
Merry Christmas!
			This first part is intended to be executed on scenario load. It defines the IADS hierarchy and initializes the state table for its components.
Code: Select all
 -- initialization
 
 asvRadarGuid = 'GWKW9V-0HME4FG8F0I72'
 bgdCPguid = 'GWKW9V-0HME4FGAIAD73'
 
 
 sa10rgtCPguid = 'GWKW9V-0HME4FG8F0HPC'
 sa10bnCPguid = 'GWKW9V-0HME4FG95CU8I'
 sa10bmRadarGuid = 'GWKW9V-0HME4FG8F0HPJ'
 sa10Bn1Guid='GWKW9V-0HME4FG8F0I36'
 sa10Bn2Guid='GWKW9V-0HME4FG8F0I0B'
 sa10Bn3Guid='GWKW9V-0HME4FG8F0HPO'
 
 sa21rgtCPguid = 'GWKW9V-0HME4FGAI6D4S'
 sa21bnCPguid = 'GWKW9V-0HME4FGAHSGSH'
 sa21bmRadarGuid = 'GWKW9V-0HME4FGAHTDR6'
 sa21Bn1Guid= 'GWKW9V-0HME4FGAHRUG7'
 sa21Bn2Guid= 'GWKW9V-0HME4FGAHS03V'
 sa21Bn3Guid= 'GWKW9V-0HME4FGAHS15G'
 
 pantsirCpyCP = 'GWKW9V-0HME4FGBBM6A5'
 pantsirCpyBMRadr = 'GWKW9V-0HME4FGBBPDRI'
 pantsirPlt1 = 'GWKW9V-0HME4FGBBM05M'
 pantsirPlt2 = 'GWKW9V-0HME4FGBBM1RT'
 pantsirPlt3 = 'GWKW9V-0HME4FGBBM2I5'
 Code: Select all
 pantsirBMMaxRange = 135 -- Max launch range of a FLATFACE-E Radar
 pantsirRange = 10 -- Max launch range of an SA-22
 
 sa10BMRMaxRange = 325 -- Max range for a BIG BIRD B
 sa10Range = 40 -- Max launch range of an SA-10
 
 sa21BMRMaxRange = 325 -- Max range for a BIG BIRD D
 sa21Range = 215 -- Max launch range of an SA-21
 
 -- weapons control states
 wcsFree = 0
 wcsTight = 1
 wcsHold = 2
 Code: Select all
 sa10AirDefenseRgt = {
 commandPost=sa10rgtCPguid, 
 equipment={sa10bmRadarGuid}, 
 rdrRng=0.5*sa10BMRMaxRange, 
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={ sa10AirDefenseBns }}
 Code: Select all
 sa10AirDefenseBns = {
 commandPost=sa10bnCPguid, 
 equipment={sa10Bn1Guid, 
 sa10Bn2Guid, sa10Bn3Guid}, 
 rdrRng=0.8*sa10Range, 
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={}}
 Code: Select all
 sa21AirDefenseRgt = {
 commandPost=sa21rgtCPguid, 
 equipment={sa21bmRadarGuid }, 
 rdrRng=0.75*sa21BMRMaxRange, 
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={ sa21AirDefenseBns }}
 Code: Select all
 sa21AirDefenseBns = {
 commandPost=sa21bnCPguid, 
 equipment={sa21Bn1Guid, sa21Bn2Guid, sa21Bn3Guid}, 
 rdrRng=0.8*sa21Range, 
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={}}
 Code: Select all
 pansirAirDefenseCpy = {
 commandPost=pantsirCpyCP, 
 equipment={pantsirCpyBMRadr}, 
 rdrRng=0.8*pantsirBMMaxRange, 
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={ pantsirAirDefensePlts }}
 Code: Select all
 pantsirAirDefensePlts =  {
 commandPost=pantsirCpyCP, 
 equipment={pantsirPlt1, pantsirPlt2, pantsirPlt3}, 
 rdrRng=0.8*pantsirRange, 
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={}}
 Code: Select all
 airDefenseBrigade = {
 commandPost=bgdCPguid , 
 equipment={}, 
 rdrRng=0,
 emitTime = 8, 
 tearDownTime = 2, 
 repositionTime = 2, 
 setUpTime = 2, 
 standByTime = 2, 
 subordinates={ sa21AirDefenseRgt, sa10AirDefenseRgt }}
 
 unitStates = {}
 Code: Select all
 function initializeUnitStates(unit, thisSide)
     initialized = false
 
     if(ScenEdit_GetUnit({side=mySide, guid=unit.commandPost}) ~= nil) then        
         if(unit.subordinates ~= nil) then
             for s, sub in ipairs(unit.subordinates) do                
                 initializeUnitStates(sub, thisSide)
             end 
         end        
         if(unit.equipment ~= {}) then
             for e, eqp in ipairs(unit.equipment) do
                 timeToCompleteEvolution = unit.emitTime + unit.tearDownTime + unit.repositionTime + unit.setUpTime + unit.standByTime
 Code: Select all
                 timeToCeaseEmission = unit.emitTime
                 timeToCompleteTearDown = timeToCeaseEmission + unit.tearDownTime
                 timeToCompleteRelocation = timeToCompleteTearDown + unit.repositionTime
                 timeToCompleteSetUp = timeToCompleteRelocation + unit.setUpTime
                 timeToStandBy = timeToCompleteSetUp + unit.standByTime
 Code: Select all
                 time = math.random(0, timeToCompleteEvolution )
                 if time < timeToCeaseEmission then 
                     unitStates[eqp] = { state="canEmit",  minutesInState = time }
                 elseif ( (time >= timeToCeaseEmission) and ( time < timeToCompleteTearDown )) then
                     unitStates[eqp] = { state="tearingDown", minutesInState = (time - timeToCeaseEmission) }
                 elseif ( ( time >= timeToCompleteTearDown ) and (time < timeToCompleteRelocation )) then
                     unitStates[eqp] = { state="repositioning", minutesInState = (time - timeToCompleteTearDown) }
                 elseif ( (time >= timeToCompleteRelocation ) and ( time < timeToCompleteSetUp )) then
                     unitStates[eqp] = { state="settingUp", minutesInState = (time - timeToCompleteRelocation) }
                 else
                     unitStates[eqp] = { state="standingBy", minutesInState = (time - timeToCompleteSetUp ) }                
                 end
             end
         end
         return true
     else
         return false -- return false if unit is improperly defined
     end
 end
 
 initializeUnitStates(airDefenseBrigade)
 The next bit of code is intended to executed every minute. It manages whether whether the IADS components are setting up, tearing down, moving, or whether they could emit should a worthwhile target come close enough to make it worth lighting up one's organic radars.
Code: Select all
 -- execute every minute
 
 function randomWaypoint(currentPosition, dist)
     math.randomseed( os.time() ) -- removes correlations (not)
 
     r = math.random(0, 359)
     print(r)
     newpos=World_GetPointFromBearing( {latitude=tostring(currentPosition.latitude), longitude=tostring(currentPosition.longitude), distance = dist, bearing = r} )
     print(newpos)
     --wpt = {latitude = newpos.latitude, longitude = newpos.longitude}
     wpt = {TypeOf = 'ManualPlottedCourseWaypoint', latitude = newpos.latitude, longitude = newpos.longitude}
     
     return wpt
 end
 Code: Select all
 function manageUnitEMCONandMobilityStates(unit, thisSide)
     if(ScenEdit_GetUnit({side=thisSide, guid=unit.commandPost}) ~= nil) then        
         if(unit.subordinates ~= nil) then
             for s, sub in ipairs(unit.subordinates) do                
                 manageUnitEMCONandMobilityStates(sub, thisSide)
             end 
         end
         if(next(unit.equipment) ~= nil) then
             timeToCompleteEvolution = unit.emitTime + unit.tearDownTime + unit.repositionTime + unit.setUpTime + unit.standByTime
             for e, eqp in ipairs(unit.equipment) do
                 if( unitStates[eqp] ~= nil) then
                     unitStates[eqp].minutesInState = unitStates[eqp].minutesInState + 1          
                     print(eqp..": state = "..unitStates[eqp].state..", minutesinstate = "..unitStates[eqp].minutesInState)
                     if unitStates[eqp].state == 'canEmit' then
                         ScenEdit_SetUnit({side=thisSide, guid=eqp, course={}, speed = 0, holdposition=true  })
                         if unitStates[eqp].minutesInState >= unit.emitTime then                            
                             unitStates[eqp].state = 'tearingDown'
                             unitStates[eqp].minutesInState = 0 
                             break
                         end                
 Code: Select all
                     elseif unitStates[eqp].state == 'tearingDown' then 
                         ScenEdit_SetUnit({side=thisSide, guid=eqp, course={}, speed = 0, holdposition=true  })
                         if unitStates[eqp].minutesInState >= unit.tearDownTime then
                             unitStates[eqp].state = 'repositioning'
                             unitStates[eqp].minutesInState = 0
                             break
                         end               
                     elseif unitStates[eqp].state == 'repositioning' then
                         repositionSpeed = 35 -- NM/hr
                         u = ScenEdit_GetUnit({side=thisside, guid=eqp})
                         if ( unitStates[eqp].minutesInState < unit.repositionTime) then   
                             newPosition = randomWaypoint( {latitude=u.latitude, longitude=u.longitude}, (repositionSpeed * unit.repositionTime / 60) )                            
                             --print(u.name..": latitude= "..newPosition.latitude..", longitude= "..newPosition.longitude)
                             ScenEdit_SetUnit({side=thisSide, guid=eqp, course={newPosition}, speed = repositionSpeed, holdposition=false  })                        
                             print(u.course)
 Code: Select all
                         else
                             if unitStates[eqp].minutesInState >= unit.repositionTime then                                
                                 unitStates[eqp].state = 'settingUp' 
                                 unitStates[eqp].minutesInState = 0                                                  
                                 break
                             end
                         end                    
                     elseif unitStates[eqp].state == 'settingUp' then
                         ScenEdit_SetUnit({side=thisSide, guid=eqp, course={}, speed = 0, holdposition=true  })
                         if unitStates[eqp].minutesInState >= unit.setUpTime then                            
                             unitStates[eqp].state = 'standingBy' 
                             unitStates[eqp].minutesInState = 0
                             break
                         end
                     elseif unitStates[eqp].state == 'standingBy' then
                         ScenEdit_SetUnit({side=thisSide, guid=eqp, course={}, speed = 0, holdposition=true  })
                         if unitStates[eqp].minutesInState >= timeToCompleteEvolution then
                             unitStates[eqp].state = 'canEmit'
                             unitStates[eqp].minutesInState = 0
                             break
                         end
                     end
                 end
             end
         end
     end
     return true
 end
 
 manageUnitEMCONandMobilityStates( airDefenseBrigade )
 This third part is intended to execute every 15 seconds. It recursively descends the hierarchy tree and determines if a unit component is in a state where it could emit, then adjusts its WCS and EMCON appropriately.
Code: Select all
 -- execute every 15 seconds
 
 function equipmentCanEmit(elementGuid)
     canEmit = false
     if(unitStates[elementGuid] ~=  nil) then
         if( unitStates[elementGuid].state == "canEmit" ) then
             canEmit = true
         end
     end
     return canEmit
 end
 Code: Select all
 function atLeastOneContactIsInRangeOfEquipment(list, unitGuid, range)
     cntctInRng = false
     for c, cntc in ipairs(contactList) do
         trk = ScenEdit_GetContact({side=mySide, guid=cntc.guid})      
         trkRng = Tool_Range(unitGuid, {latitude=trk.latitude, longitude=trk.longitude})
         print(trkRng..", "..range)
         if (trkRng <= range) then
             cntctInRng = true;
         end
     end
     return cntctInRng
 end
 Code: Select all
 function manageIADSEchelonEmconAndWCS(unit, thisSide)
     if(ScenEdit_GetUnit({side=mySide, guid=unit.commandPost}) ~= nil) then        
         if(unit.subordinates ~= nil) then
             for s, sub in ipairs(unit.subordinates) do                
                 manageIADSEchelonEmconAndWCS(sub, thisSide)
             end 
         end
         contactList = ScenEdit_GetContacts(thisSide)
         if(unit.equipment ~= {}) then
             for e, eqp in ipairs(unit.equipment) do
                 if( ScenEdit_GetUnit({side=thisSide, guid=eqp}) ~= nil ) then
                     u=ScenEdit_GetUnit({side=thisSide, guid=eqp})
                     if( atLeastOneContactIsInRangeOfEquipment(contactList, eqp, unit.rdrRng)  and equipmentCanEmit(eqp) ) then
                         print("Turning on radar.")
                         ScenEdit_SetEMCON('Unit', eqp, "Radar=Active;Sonar=Passive;OECM=Passive")
                         ScenEdit_SetDoctrine({side = thisSide, unitname=u.name }, { weapon_control_status_air = wcsTight  })
                     else
                         print("Turning off radar.")
                         ScenEdit_SetEMCON('Unit', eqp, "Radar=Passive;Sonar=Passive;OECM=Passive")
                         ScenEdit_SetDoctrine({side = thisSide, unitname=u.name }, { weapon_control_status_air = wcsHold  })
                     end        
                 end
             end
         end
         return true
 Code: Select all
     else
         -- execute alternative emcon/wcs management plan (currently none)
         return false -- return false if echelon command post is destroyed
     end
 end
 
 manageIADSEchelonEmconAndWCS(airDefenseBrigade, "RUS")
 
 
 Merry Christmas!
