Page 1 of 1

Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Fri Dec 24, 2021 2:56 pm
by SeaQueen
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.

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!

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Fri Dec 24, 2021 7:46 pm
by KLAB
Wow! Thanks. Merry Christmas.

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Sat Dec 25, 2021 9:38 am
by Parel803
looks impressive, will try to learn from it.
merry Christmas

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Sat Dec 25, 2021 12:20 pm
by SeaQueen
If you have any questions, please ask. I created a tree data structure that is intended to reflect the various echelons of the IADS network. It should be very flexible, and can reflect a wide variety of network topologies. On initialization or at regular intervals, it recursively descends the tree and performs various administrative actions (changing the values of variables) and then asks the SAM site to move or emit based on the administrative actions and the current picture. It's very simple, and it should be easy to customize to many different IADS behaviors.

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Sun Dec 26, 2021 7:42 pm
by BDukes
Very impressive. Thank you!

Mike

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Mon Dec 27, 2021 7:28 am
by SeaQueen
Be advised, I have found some bugs in this during subsequent testing. I'll probably put out a revised version soon.

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Mon Dec 27, 2021 1:10 pm
by BDukes
ORIGINAL: SeaQueen

Be advised, I have found some bugs in this during subsequent testing. I'll probably put out a revised version soon.

Waa I want my money back[8D]

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Mon Dec 27, 2021 9:33 pm
by SeaQueen
You’re welcome to fix it yourself. I don’t generally post code with the idea that it could be used without substantial customization.

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Tue Dec 28, 2021 11:04 am
by BDukes
ORIGINAL: SeaQueen

You’re welcome to fix it yourself. I don’t generally post code with the idea that it could be used without substantial customization.

SQ its free code. I can't complain and you're awesome!

Probably should have started with that.[:)] Internet don't do tone well[;)]

RE: Hierarchical IADS Code w/Shoot and Scoot Logic

Posted: Thu Dec 30, 2021 10:30 am
by SeaQueen
Thank you. Hopefully you've put it to good use by now. ;-)